This project is inspired from https://github.com/devartis/djangodocker repositories (and its guides) and https://github.com/cnr-ibba/dockerfiles/tree/master/compose/django The aim is to configure a CI environment in order to perform unittest, passing secret variables as suggested by Travis CI encryption-keys documentation using a complex docker-composed environment.
This guide describe how to build a running Django+MySQL running with docker-compose. First of all copy all this directory in another location since you will modify something to develop your application. Next, rename the whole directory with a project name. In this example, all names are set to mysite
. Replace all mysite
occurences with your project name. All docker container instances will have such name as a prefix in their name. There are some directory that will be mounted as data volume: this will make easier to develop the code or re-spawn the docker application on another location using the same volume directory (as it would be for mysql-data directory). For this model, al MySQL data file will be placed in mysql-data
directory inside this project; All django data will be placed in django-data
directory inside this project. When such directories are created for the first time, there are created and mounted as a root directories. After permission can be fixed according user need. However processes like ngnix and uswgi work with a different user from root, and so files have to be accessed in read mode
Before to start take a look in the docker-compose.yml
file. This is the configuration file used by docker-compose
commands. All docker-compose
commands need to be instantiated in this directory or by specifying the path of docker-compose.yml
file, as stated by the docker compose documentation. For such compose project, 3 containers will be instantiated:
db
: in this container will be placed the mysql 5.6 server. This container will be listen foruwsgi
django containeruwsgi
: this will be the django container in which uwsgi server will run. This container will be linked withdb
containernginx
: this container will have nginx installed. All static file will be served by nginx by sharing static content as docker volumes between containers. Django files will be served by the appropriate container via fastCGI plugin.
Those name will be placed in each container /etc/hosts
files, so you can ping uwsgi
host inside nginx
container, for instance, no matter what ip addresses will be given to the container by docker. When starting a project for the first time, data directory and initial configuration have to be defined. Data directories will be placed inside this directory, in order to facilitate docker-compose
commands once docker images where configured for the application. When data directories are created for the first time, the ownership of such directories is the same of the service running in the container. You can change it as you prefer, but remember that service run under a non privileged user, so you have to define at least the read privileged for files, and the read-execute privileges for directories.
Before building and instantiating containers, you need to modify the MYSQL_ROOT_PASSWORD
variables according your needs (see docker-compose.yml
). Then you can build and run containers by typing:
$ docker-compose up -d db
Once MySQL is istantiated for the first time or mysql-data
directory is created
or derived from another container, MySQL can be run with docker-compose
standard
commands. Once MySQL data files are created (you can inspecy MySQL logs with
docker-compose logs
), connect to MySQL database and create database and users
needed from MySQL connections. The MySQL container defined in docker-compose.yml
,
never expose a port outside, but you may want to expose it to access data with a MySQL client. You can get a mysql client to this container by
linking a new container, for instance:
$ docker run -it --link <mysql_running_container>:mysql -e MYSQL_ROOT_PASSWORD="my-secret-pw" \
--rm mysql:5.6 sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" \
-uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD"'
The --link <mysql_running_container>:mysql
specifies the MySQL running container
and an alias for this. You can get the running container names using docker-compose ps
or docker ps
command. Now create a database and a user for the django instance
with permission to work on such database
mysql> CREATE DATABASE mysite ;
mysql> GRANT ALL PRIVILEGES ON mysite.* TO django@'%' IDENTIFIED BY 'django' ;
mylsq> exit;
With the docker run command, you can do a mysite
database dump:
$ docker run -it --link <mysql_running_container>:mysql -v $PWD:/data/ -e MYSQL_ROOT_PASSWORD="my-secret-pw" \
--rm mysql:5.6 sh -c 'exec mysqldump -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" \
-uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD" mysite > /data/mysite_dump.sql'
Note as variables like MYSQL_ROOT_PASSWORD="my-secret-pw"
are traslated with the
prefix MYSQL_ENV_
inside running container. The dump will be write in the /data
volumes directory, which is your current $PWD
directory. The ownership of dump
file is the same of the $USER
in the running container.
With the docker run command, you can import a .sql
file by adding its path as
a docker volume, for instance, if you are in mysite_dump.sql
directory:
$ docker run -it --link <mysql_running_container>:mysql -v $PWD:/data/ -e MYSQL_ROOT_PASSWORD="my-secret-pw" \
--rm mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" \
-uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD" mysite < /data/mysite_dump.sql'
You can access to mysql container using phpmyadmin, simply type
$ docker-compose ps
To get container name on which MySQL is running, then you can use the official phpmyadmin image, for example:
$ docker run --name phpmyadmin -d --link <mysql_running_container>:db -p 8080:80 phpmyadmin/phpmyadmin:latest
More information on phpmyadmin image could be found here or in official phpmyadmin documentation
You need to derive the db
container name, for example with docker-compose ps
.
Then you can launch adminer like this:
$ docker run --name adminer -d --link <mysql_running_container>:db -p 8080:8080 adminer
You could choose to pass -p 8080
only in order to get a random port in which adminer
is listening, you will need to determine the port with docker ps -a
and then use
a browser to point to adminer instance, for example http://<your_host>:<your_port
.
More information could be found in adminer - docker hub
Django script will be served using the uwsgi server. You can get more informaton here:
Take a look in to the uwsgi directory:
$ tree uwsgi
uwsgi
├── Dockerfile
└── requirements.txt
requirements.txt
is a list of python modules which will be installed when the image is builded for the first time. All python modules needed by the application needs to be specified here one by line.
For such example, we suppose that the django project name will be mysite
as stated
by the django tutorial.
The mysite_uwsgi.ini
in django-data
directory contains uwsgi configuration for this application.
The default path of the application is the mysite
directory of the django tutorial.
If you want to modify the project directories, remember to modify docker-compose.yml
and to rename mysite_uwsgi.ini
commands according your needs.
Now you can build the django image from its Dockerfile
or build it automatically
with docker-compose
. In this case, we initialize a new project and we build a new
docker image if it not exists (since we have not specified a destination directory,
a mysite
project directory is created and placed under /var/uwsgi/
.
Inside mysite, we will have manage.py
):
$ docker-compose run --rm uwsgi django-admin.py startproject mysite
This will build the django image and runs the django-admin.py
script. If there
are prerequisites, for example containers linked to that, they will be started before
the uwsgi
django container. After that, the container stops and we return to the
shell environment. You may want to fix file permissins in order to edit files, for
example:
$ sudo chown -R ${USER}:${USER} django-data
Next, you may need to set a list of strings representing the host/domain
names that this Django site can serve: you will modify the ALLOWED_HOSTS
as described here. You may need also to unset the
DEBUG
option:
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['*']
Now we need to set up the database connection. You may want
change default ownership to edit files. Replace the DATABASES = ...
definition
in django-data/mysite/mysite/settings.py
accordingly your project database settings:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysite',
'USER': 'django',
'PASSWORD': 'django',
'HOST': 'db',
'PORT': 3306,
}
}
# configuration for postgres Database
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql',
# 'NAME': 'postgres',
# 'USER': 'postgres',
# 'PASSWORD': 'my-secret-pw',
# 'HOST': 'db',
# 'PORT': 5432,
# }
# }
Note that the db
host is the same name used in docker-compose.yml
. User, database
and password are the same specified in the example above. Remember to set the timezone:
docker compose will export HOST /etc/localtime in read only mode, but it's better
to set timezone also in django in order to have correct times:
TIME_ZONE = 'Europe/Rome'
We have to set also the static file positions. It's better to prepend the django project names in order to write rules to serve static files via nginx. Here is an example for mysite project:
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/mysite/static/'
MEDIA_URL = '/mysite/media/'
# collect all Django static files in the static folder
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
The STATIC_URL
variable will tell to django (uwsgi) how to define links to static
files, and the STATIC_ROOT
variable will set the position in which static files
(as the admin .css files) will be placed. The MEDIA_ROOT
and MEDIA_URL
variables
have the same behaviour. You may want to create a /static
and /media
directory inside mysite
, in order to place media files. Then you have to call
the collectstatic
command in order to place the static files in their directories:
$ mkdir django-data/mysite/static/
$ mkdir django-data/mysite/media/
$ docker-compose run --rm uwsgi python mysite/manage.py collectstatic
As stated here autoreload could be CPU intensive and must be used only in developmente environment: "Some plugins (most notably Python and Perl) have code auto-reloading facilities. Although they might sound very appealing, you MUST use them only under development as they are really heavy-weight. For example the Python –py-autoreload option will scan your whole module tree at every check cycle."
To avoid the necessity of specify relative path every time manage.py
is called,
you may want to change the working_directory in docker-compose.yml
file to point
on your application directory, for example:
# set working dir for uwsgi
working_dir: /var/uwsgi/mysite
This allows to call manage.py
from the mysite
project directory
You may want to run the following commands to create the necessary django tables
if django database is empty. The path of manage.py
is not specified, since we
changed the working_dir
in docker-compose.yml
:
$ docker-compose run --rm uwsgi python manage.py migrate
More info could be found here
You need to create a user who can login to the admin site. Run the following command:
$ docker-compose run --rm uwsgi python manage.py createsuperuser
A user and password for the admin user will be prompted. Ensure to track such credentials
The django-data
directory need to be create if does't exists. Then you have to
create a directory in which put the manage.py
. Static files needs
to be placed inside the directory, or links or static urls needs
to be modified in order to be served correctly. For instance, to place a django
project into a mysite
directory:
$ mkdir -p django-data/mysite
$ cp -r /a/django/project/ django-data/mysite/
$ mkdir django-data/mysite/media
$ mkdir django-data/mysite/static
Let's take a look inside nginx directory:
$ tree nginx
nginx/
├── conf.d
│ └── default.conf
├── Dockerfile
└── nginx.conf
The nginx.conf
contains general configuration for NGINX. If you need to run d
different processes or listen for a different number of connection, you may modify
this file. The default.conf
located in conf.d
is the file which need to be modified
accordingly to your project location. You can modify this file without building
nginx image, since this directory is imported as a volume in nginx container.
In this sample configuration, the served application is supposed to be under mysite
location. Django static files are served via nginx by sharing volumes between
django uwsgi
container. This container expose the standard 80 port outside, but
docker-compose.yml
could bind this port to a different one
Now start database and django (in daemonized mode):
$ docker-compose up -d
You can inspect docker logs with
$ docker-compose logs
Container could be stopped and restarted via docker-compose
compose. Even if
container are dropped, all files in data volumes directories remains and don't
need to be reconfigured as the first instance. You can also run management commands
with Docker. To migrate django database, for example, you can run:
$ docker-compose run --rm uwsgi python mysite/manage.py migrate
Or
$ docker-compose run --rm uwsgi python manage.py migrate
If you have set the working_dir
in docker-compose.yml
properly.
You can serve docker compose using HOST NGINX, for instance, via proxy_pass. Place the followin code inside NGINX server environment. Remember to specify the port exported by your docker NGINX instance:
location /mysite/ {
# set variable in location
set $my_port 10080;
# Add info to webpages
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:$my_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port $my_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_header Set-Cookie;
# Subitting a request to docker service
proxy_pass http://<your_host>:$my_port;
proxy_redirect http://$host:$my_port/ $scheme://$http_host/;
}