Skip to content

Commit

Permalink
Add Dockerfile, docker-compose.yml, S3 uploaded media settings (confi…
Browse files Browse the repository at this point in the history
…gurable via the environment), Docker instructions, and a .travis.yml to build/test the Docker image.
  • Loading branch information
tobiasmcnulty committed Feb 19, 2017
1 parent 6eefd5f commit 6652c42
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 60 deletions.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Dockerfile
docker-compose.yml
Procfile
Vagrantfile
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ bakerydemo/media/*
bakerydemo/settings/local.py
bakerydemodb
__pycache__
.*
.vagrant/
/.vagrant/
/Vagrantfile.local
Expand Down
23 changes: 23 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
sudo: required
services:
- docker

env:
global:
- GREP_TIMEOUT=360

before_install:
- sudo apt-get update
- sudo apt-get install -qy -o Dpkg::Options::="--force-confold" docker-engine coreutils

script:
# Bring up the postgres, redis, and app containers
- docker-compose up --build -d

- timeout $GREP_TIMEOUT grep -m 1 'Running migrations' <(docker-compose logs --follow app 2>&1)
- timeout $GREP_TIMEOUT grep -m 1 'spawned uWSGI http 1' <(docker-compose logs --follow app 2>&1)
- docker-compose run app /venv/bin/python /code/manage.py check

after_script:
- docker-compose logs
- docker images
44 changes: 44 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM python:3.5-alpine

ADD requirements/ /requirements/
RUN set -ex \
&& apk add --no-cache --virtual .build-deps \
gcc \
g++ \
make \
libc-dev \
musl-dev \
linux-headers \
pcre-dev \
postgresql-dev \
libjpeg-turbo-dev \
&& pyvenv /venv \
&& /venv/bin/pip install -U pip \
&& LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "/venv/bin/pip install -r /requirements/production.txt" \
&& runDeps="$( \
scanelf --needed --nobanner --recursive /venv \
| awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
| sort -u \
| xargs -r apk info --installed \
| sort -u \
)" \
&& apk add --virtual .python-rundeps $runDeps \
&& apk del .build-deps
RUN apk add --no-cache postgresql-client
RUN mkdir /code/
WORKDIR /code/
ADD . /code/
EXPOSE 8000

# Add custom environment variables needed by Django or your settings file here:
ENV DJANGO_SETTINGS_MODULE=bakerydemo.settings.production DJANGO_DEBUG=off

# uWSGI configuration (customize as needed):
ENV UWSGI_VIRTUALENV=/venv UWSGI_WSGI_FILE=bakerydemo/wsgi_production.py UWSGI_HTTP=:8000 UWSGI_MASTER=1 UWSGI_WORKERS=2 UWSGI_THREADS=8 UWSGI_UID=1000 UWSGI_GID=2000

# Call collectstatic with dummy environment variables:
RUN DATABASE_URL=postgres://none REDIS_URL=none /venv/bin/python manage.py collectstatic --noinput

# start uWSGI, using a wrapper script to allow us to easily add more commands to container startup:
ENTRYPOINT ["/code/docker-entrypoint.sh"]
CMD ["/venv/bin/uwsgi", "--http-auto-chunked", "--http-keepalive"]
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
release: yes "yes" | python manage.py migrate
web: gunicorn bakerydemo.heroku_wsgi --log-file -
web: uwsgi --http-socket=:$PORT --master --workers=2 --threads=8 --die-on-term --wsgi-file=bakerydemo/wsgi_production.py
4 changes: 3 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"repository": "https://github.com/wagtail/bakerydemo",
"keywords": ["wagtail", "django"],
"env": {
"DJANGO_SETTINGS_MODULE": "bakerydemo.settings.heroku"
"DJANGO_DEBUG": "off",
"DJANGO_SETTINGS_MODULE": "bakerydemo.settings.production",
"DJANGO_SECURE_SSL_REDIRECT": "on"
},
"scripts": {
"postdeploy": "django-admin.py migrate && django-admin.py load_initial_data && echo 'from wagtail.wagtailimages.models import Rendition; Rendition.objects.all().delete()' | django-admin.py shell"
Expand Down
22 changes: 0 additions & 22 deletions bakerydemo/settings/heroku.py

This file was deleted.

68 changes: 68 additions & 0 deletions bakerydemo/settings/production.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import os
import dj_database_url
import random
import string

from .base import *

DEBUG = os.getenv('DJANGO_DEBUG', 'off') == 'on'

# DJANGO_SECRET_KEY *should* be specified in the environment. If it's not, generate an ephemeral key.
if 'DJANGO_SECRET_KEY' in os.environ:
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
else:
# Use if/else rather than a default value to avoid calculating this if we don't need it
print("WARNING: DJANGO_SECRET_KEY not found in os.environ. Generating ephemeral SECRET_KEY.")
SECRET_KEY = ''.join([random.SystemRandom().choice(string.printable) for i in range(50)])

# Make sure Django can detect a secure connection properly on Heroku:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Redirect all requests to HTTPS
SECURE_SSL_REDIRECT = os.getenv('DJANGO_SECURE_SSL_REDIRECT', 'off') == 'on'

# Accept all hostnames, since we don't know in advance which hostname will be used for any given Heroku instance.
# IMPORTANT: Set this to a real hostname when using this in production!
# See https://docs.djangoproject.com/en/1.10/ref/settings/#allowed-hosts
ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', '*').split(';')

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# BASE_URL required for notification emails
BASE_URL = 'http://localhost:8000'

db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)

# Simplified static file serving.
# https://warehouse.python.org/project/whitenoise/

MIDDLEWARE.append('whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

if 'AWS_STORAGE_BUCKET_NAME' in os.environ:
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID', '')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY', '')
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_AUTO_CREATE_BUCKET = True

INSTALLED_APPS.append('storages')
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
},
},
}
File renamed without changes.
33 changes: 33 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: '2'

services:
db:
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: changeme
restart: always
image: postgres:9.6
expose:
- "5432"
redis:
restart: always
image: redis:3.0
expose:
- "6379"
app:
environment:
DJANGO_SECRET_KEY: changeme
DATABASE_URL: postgres://app_user:changeme@db/app_db
REDIS_URL: redis://redis
build:
context: .
dockerfile: ./Dockerfile
links:
- db:db
- redis:redis
ports:
- "8000:8000"
depends_on:
- db
- redis
19 changes: 19 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh
set -e

until psql $DATABASE_URL -c '\l'; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done

>&2 echo "Postgres is up - continuing"

if [ "$1" = '/venv/bin/uwsgi' ]; then
/venv/bin/python manage.py migrate --noinput
fi

if [ "x$DJANGO_LOAD_INITIAL_DATA" = 'xon' ]; then
/venv/bin/python manage.py load_initial_data
fi

exec "$@"
83 changes: 50 additions & 33 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Run the following commands:

```bash
git clone git@github.com:wagtail/bakerydemo.git
cd wagtaildemo
cd bakerydemo
vagrant up
vagrant ssh
# then, within the SSH session:
Expand All @@ -38,9 +38,39 @@ interface at [http://localhost:8000/admin/](http://localhost:8000/admin/).

Log into the admin with the credentials ``admin / changeme``.

Setup without Vagrant
-----
Don't want to set up a whole VM to try out Wagtail? No problem.
Setup with Docker
-----------------

### Dependencies
* [Docker](https://docs.docker.com/engine/installation/)

### Installation
Run the following commands:

```bash
git clone git@github.com:wagtail/bakerydemo.git
cd bakerydemo
docker-compose up --build -d
docker-compose run app /venv/bin/python manage.py load_initial_data
```

The demo site will now be accessible at [http://localhost:8000/](http://localhost:8000/) and the Wagtail admin
interface at [http://localhost:8000/admin/](http://localhost:8000/admin/).

Log into the admin with the credentials ``admin / changeme``.

**Important:** This `docker-compose.yml` is configured for local testing only, and is not intended for production use.

### Debugging
To tail the logs from the Docker containers in realtime, run:

```bash
docker-compose logs -f
```

Local Setup
-----------
Don't want to set up a whole VM nor use Docker to try out Wagtail? No problem.

### Dependencies
* [PIP](https://github.com/pypa/pip)
Expand Down Expand Up @@ -89,53 +119,40 @@ update in the browser. Once finished, click `View` to see the public site.

Log into the admin with the credentials ``admin / changeme``.

To prevent the demo site from regenerating a new Django `SECRET_KEY` each time Heroku restarts your site, you should set
a `DJANGO_SECRET_KEY` environment variable in Heroku using the web interace or the [CLI](https://devcenter.heroku.com/articles/heroku-cli). If using the CLI, you can set a `SECRET_KEY` like so:

heroku config:set DJANGO_SECRET_KEY=changeme

To learn more about Heroku, read [Deploying Python and Django Apps on Heroku](https://devcenter.heroku.com/articles/deploying-python).

### Storing Wagtail Media Files on AWS S3

If you have deployed the demo site to Heroku, you may want to perform some additional setup. Heroku uses an
[ephemeral filesystem](https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem). In laymen's terms, this means
that uploaded images will disappear at a minimum of once per day, and on each application deployment. To mitigate this,
you can host your media on S3.
If you have deployed the demo site to Heroku or via Docker, you may want to perform some additional setup. Heroku uses an
[ephemeral filesystem](https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem), and Docker-based hosting
environments typically work in the same manner. In laymen's terms, this means that uploaded images will disappear at a
minimum of once per day, and on each application deployment. To mitigate this, you can host your media on S3.

This documentation assumes that you have an AWS account, an IAM user, and a properly configured S3 bucket. These topics
are outside of the scope of this documentation; the following [blog post](https://wagtail.io/blog/amazon-s3-for-media-files/)
will walk you through those steps.

Next, you will need to add `django-storages` and `boto3` to `requirements/heroku.txt`.

Then you will need to edit `settings/heroku.py`:

INSTALLED_APPS.append('storages')

You will also need to add the S3 bucket and access credentials for the IAM user that you created.

AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME', 'changeme')
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID', 'changeme')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY', 'changeme')
AWS_S3_CUSTOM_DOMAIN = '{}.s3.amazonaws.com'.format(AWS_STORAGE_BUCKET_NAME)

Next, you will need to set these values in the Heroku environment. The next steps assume that you have
the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) installed and configured. You will
execute the following commands to set the aforementioned environment variables:
This demo site comes preconfigured with a production settings file that will enable S3 for uploaded media storage if
``AWS_STORAGE_BUCKET_NAME`` is defined in the shell environment. All you need to do is set the following environment
variables. If using Heroku, you will first need to install and configure the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli). Then, execute the following commands to set the aforementioned environment variables:

heroku config:set AWS_STORAGE_BUCKET_NAME=changeme
heroku config:set AWS_ACCESS_KEY_ID=changeme
heroku config:set AWS_SECRET_ACCESS_KEY=changeme

Do not forget to replace the `changeme` with the actual values for your AWS account.

Finally, we need to configure the `MEDIA_URL` as well as inform Django that we want to use `boto3` for the storage
backend:

MEDIA_URL = 'https://{}/'.format(AWS_S3_CUSTOM_DOMAIN)'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
Do not forget to replace the `changeme` with the actual values for your AWS account. If you're using a different hosting
environment, set the same environment variables there using the method appropriate for your environment.

Commit these changes and push to Heroku and you should now have persistent media storage!
Once Heroku restarts your application or your Docker container is refreshed, you should have persistent media storage!

### Sending email from the contact form

The following setting in `base.py` and `heroku.py` ensures that live email is not sent by the demo contact form.
The following setting in `base.py` and `production.py` ensures that live email is not sent by the demo contact form.

`EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'`

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-r requirements/heroku.txt
-r requirements/production.txt
4 changes: 3 additions & 1 deletion requirements/heroku.txt → requirements/production.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-r base.txt
# Additional dependencies for Heroku deployment
dj-database-url==0.4.1
gunicorn==19.6.0
uwsgi==2.0.14
psycopg2==2.6.2
whitenoise==3.2.2
boto==2.45.0
django-storages==1.5.2

0 comments on commit 6652c42

Please sign in to comment.