How to do it...
The basic structure already created separates aspects of our project into logical groups:
- All applications to be used in the project are stored under the apps directory, which allows them to be pulled in individually either from version control or other source locations.
- project and templates are also distinct, which makes sense since the settings and templates for one project switch be shared, whereas applications are commonly intended to be reusable.
- The static and media files are separated as well, allowing them to be deployed to separate static content containers (and servers) easily.
To make full use of these features, let's update the docker-compose.yml file with some enhancements:
# docker-compose.yml
version: '3'
services:
proxy:
image: 'jwilder/nginx-proxy:latest'
ports:
- '80:80'
volumes:
- '/var/run/docker.sock:/tmp/docker.sock:ro'
db:
image: 'mysql:5.7'
ports:
- '3306'
volumes:
- './config/my.cnf:/etc/mysql/conf.d/my.cnf'
- './mysql:/var/lib/mysql'
- './data:/usr/local/share/data'
environment:
- 'MYSQL_ROOT_PASSWORD'
- 'MYSQL_USER'
- 'MYSQL_PASSWORD'
- 'MYSQL_DATABASE'
app:
build: .
command: python3 manage.py runserver 0.0.0.0:8000
volumes:
- './project:/usr/src/app/myproject'
- './media:/usr/src/app/media'
- './static:/usr/src/app/static'
- './templates:/usr/src/app/templates'
- './apps/external:/usr/src/app/external'
- './apps/myapp1:/usr/src/app/myapp1'
- './apps/myapp2:/usr/src/app/myapp2'
ports:
- '8000'
links:
- db
environment:
- 'SITE_HOST'
- 'MEDIA_HOST'
- 'STATIC_HOST'
- 'VIRTUAL_HOST=${SITE_HOST}'
- 'VIRTUAL_PORT=8000'
- 'MYSQL_HOST=db'
- 'MYSQL_USER'
- 'MYSQL_PASSWORD'
- 'MYSQL_DATABASE'
media:
image: 'httpd:latest'
volumes:
- './media:/usr/local/apache2/htdocs'
ports:
- '80'
environment:
- 'VIRTUAL_HOST=${MEDIA_HOST}'
static:
image: 'httpd:latest'
volumes:
- './static:/usr/local/apache2/htdocs'
ports:
- '80'
environment:
- 'VIRTUAL_HOST=${STATIC_HOST}'
With these changes, there are some corresponding updates needed in the Django project settings as well. The end result should look similar to the following:
# project/settings.py
# ...
ALLOWED_HOSTS = []
if os.environ.get('SITE_HOST'):
ALLOWED_HOSTS.append(os.environ.get('SITE_HOST'))
# ...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
if os.environ.get('MYSQL_HOST'):
DATABASES['default'] = {
'ENGINE': 'django.db.backends.mysql',
'HOST': os.environ.get('MYSQL_HOST'),
'NAME': os.environ.get('MYSQL_DATABASE'),
'USER': os.environ.get('MYSQL_USER'),
'PASSWORD': os.environ.get('MYSQL_PASSWORD'),
}
# ...
# Logging
# https://docs.djangoproject.com/en/dev/topics/logging/
LOGGING = {
'version': 1,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/var/log/app.log',
'formatter': 'simple'
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
}
}
if DEBUG:
# make all loggers use the console.
for logger in LOGGING['loggers']:
LOGGING['loggers'][logger]['handlers'] = ['console']
# ...
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
if os.environ.get('STATIC_HOST'):
STATIC_DOMAIN = os.environ.get('STATIC_HOST')
STATIC_URL = 'http://%s/' % STATIC_DOMAIN
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
if os.environ.get('MEDIA_HOST'):
MEDIA_DOMAIN = os.environ.get('MEDIA_HOST')
MEDIA_URL = 'http://%s/' % MEDIA_DOMAIN
Furthermore, the my.cnf file is referenced in docker-compose.yml as a volume attached to the db service. Although there would be no error, specifically, if it were left out; a directory would be automatically created to satisfy the volume requirement. At a minimum, we can add an empty file under the config folder, or we might add options to MySQL right away, such as the following:
# config/my.cnf
[mysqld]
sql_mode=STRICT_TRANS_TABLES
Then, add a bin subdirectory in myproject_docker, inside of which we will add a dev script (or dev.sh, if the extension is preferred):
#!/usr/bin/env bash
# bin/dev
# environment variables to be defined externally for security
# - MYSQL_USER
# - MYSQL_PASSWORD
# - MYSQL_ROOT_PASSWORD
DOMAIN=myproject.local
DJANGO_USE_DEBUG=1 \
DJANGO_USE_DEBUG_TOOLBAR=1 \
SITE_HOST="$DOMAIN" \
MEDIA_HOST="media.$DOMAIN" \
STATIC_HOST="static.$DOMAIN" \
MYSQL_HOST="localhost" \
MYSQL_DATABASE="myproject_db" \
docker-compose $*
Make sure the script is executable by modifying the permissions, as in the following:
myproject_docker/$ chmod +x bin/dev
Finally, the development hosts need to be mapped to a local IP address, such as via /etc/hosts on macOS or Linux. Such a mapping for our project would look something like this:
127.0.0.1 myproject.local media.myproject.local static.myproject.local