Docker + Django + Nginx

Views: 207
Wrote on April 16, 2020, 11:14 p.m.

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!