Docker + Django + Nginx
In last Chapter we successfully added MySQL to Django and docker container, but the code was still running locally and in development environment which is not stable for production use, in this chapter, we are going to wrap up our Django project and deploy on server with Docker + Django + MySQL + Nginx + Gunicorn.
Preparation
Local setting
Install gunicorn
Activate your local virtual environment,
$ source bin/activate
$ cd demo
$ pip install gunicorn
$ pip freeze > requirements.txt
You'll see gunicorn==19.9.0
or whatever version of gunicorn
was just added.
Docker-compose
Before deploying on server, let's do it locally. Edit the docker-compose.yml
we previously worked on.
version: "3"
services:
app:
restart: always
build: .
command: bash -c "python3 manage.py collectstatic --no-input && python3 manage.py migrate && gunicorn --timeout=30 --workers=4 --bind :8000 demo.wsgi:application"
volumes:
- .:/code
- static-volume:/code/collected_static
expose:
- "8000"
depends_on:
- db
networks:
- web_network
- db_network
db:
image: mysql:5.7
volumes:
- "./mysql:/var/lib/mysql"
ports:
- "3306:3306"
restart: always
environment:
- MYSQL_ROOT_PASSWORD=mypassword
- MYSQL_DATABASE=dockerDemo
networks:
- db_network
nginx:
restart: always
image: nginx:latest
ports:
- "8000:8000"
volumes:
- static-volume:/code/collected_static
- ./config/nginx:/etc/nginx/conf.d
depends_on:
- app
networks:
- web_network
networks:
web_network:
driver: bridge
db_network:
driver: bridge
volumes:
static-volume:
Looks a little bit complicated isn't it? Let's take a closer look at it.
- Here we have three containers defined - app
, db
and nginx
. The containers communicate between each other through defined ports;
- Two networks are defined - web_network
and db_network
. Only containers within the same network can communicate with each other. Networks are isolated to thers, and they cannot communicate each other even if sharing the same port;
- One data volume is defined, called static-volume
. The data volume is very suitable for multiple containers to share data, as you have found out both app
and nginx
are using it.
- Both expose
and ports
can expose the ports of the container. The difference is that expose
only exposes to other containers, while ports
exposes to other containers and the host machine..
Network
Docker allows users to define the working network for each container, only contains with in the same network can communicate. In our case, the nginx
container is in the web_network
network, and the db
container is in the db_network
network, so these two cannot communicate, and in fact they do not need to communicate as well. The app
container is in both web_network
and db_network
network, which works as a bridge to connecting these three containers.
Defining the network can isolate the network environment of the container, and it is convenient for O & M personnel to see the logical relationship of the network at a glance.
Volume
The volume that we have seen in previous chapters for mapping host and container directories is actually a type of the mount, the newly created static-volume in this case is called a volume.
static-volume: / code / collected_static.
Pathlink after the colon is directory in the container, the static-volume
before colon is not the host directory, but the name of the volumn. Essentially, the volumn also implements the mapping dictory between host machine and the container, but the volumn is managed by Docker, and you don’t even need to know the specific location where the volume is stored on the host machine.
Compared with mounting, the advantage of volumn is that it is managed by Docker, there is no mounting problem potentially causing by insufficient permissions, and there is no need to specify different paths on different servers; the disadvantage is that it is not suitable for mapping single configuration files.
Like the mounting, the life cycle of volumn is separated from the container, and the volumn still exist after the container is deleted. You can continue to use it next time building an image.
The volumn has a very important feature: - when the container is started, if an empty volume is mounted to a non-empty directory in the container, the files in this directory will be copied to the volumn; - If you mount a non-empty volumn to a directory in the container, the data in the volumn will be displayed in the directory in the container; - if there is data in the directory in the original container, then the original data will be hidden.
In other words, as long as the volume is initialized, the original collected_static directory of the container will be hidden and no longer used, and the newly added files only exist in the volume, not in the container.
In addition, the permanent storage of static static files (and media files) in Django project can be achieved by mounting or volume.
Local nginx setting
Let's modify the local Nginx configuration file (not on server, create a file with .conf
extension, name whatever you want or even just use the default
file), we are going to map the local nginx to the nginx
container, expose port 8000 of host machine to port 8000 of the nginx container.
upstream app {
ip_hash;
server app:8000;
}
server {
listen 8000;
server_name localhost;
location /static/ {
autoindex on;
alias /code/collected_static/;
}
location / {
proxy_pass http://app/; // Reverse proxy
}
}
Modify Django settings.py
STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static')
STATIC_URL = '/static/'
Go for launch
$ sudo docker-compose up
Visit 127.0.0.1:8000
on your browser, ta-da! A little rocket!
Deploy on server
Follow what we have done locally, setup the docker, docker-compose and python3 on server, clone the Django project to server with Git, modify the settings.py, /etc/nginx/whatEverName.conf (create this file or use the default
one), and load requirements.txt, docker-compose.yml and Dockerfile onto server in the same directery structure. Here we need to modify something in docker-compose.yml
because the default http port is 80:
version: "3"
services:
app:
...
command: bash -c "... demo.wsgi:application"
...
db:
...
nginx:
...
ports:
- "80:8000"
...
networks:
...
volumes:
...
/etc/nginx/whatEverName.conf
upstream domain_name {
ip_hash;
server app:8000;
}
server {
...
location / {
proxy_pass http://domain_name/;
}
}
Last but not least, modify the Django settings.py
DEBUG=False
$ sudo docker-compose build
$ sudo docker-compose up
Your Django project is deployed on the internet!