Docker how to Django + uwsgi/gunicorn + nginx?

I can't get my head around on what's the "correct" way to deploy a Django project that uses uwsgi/gunicorn (I haven't decide yet what to use, probably uwsgi since it has better performances, suggestions?) and nginx using docker.

I saw that some people put everything in the same container. I'm not an expert of docker, but the container should just do one (1) thing. So, having Django + nginx seems to be 2 rather than 1.

Now, my idea of the deployment is:

  • a container with Django and uwsgi. At the end of the Dockerfile i run the uwsgi script. This container exposes the port 8000
  • a container with nginx that is linked to django one. This exposes port 80 and proxies the requests to django.

Is there any other way to do it? Is there a tutorial that covers this case a bit deeper. I need for a solid production, not just to test some code on my pc.


Solution 1:

I'm currently building a django app the way you want.

I use docker-compose to do this. This is my docker-compose.yml

version: '2'
services:
  nginx:
    container_name: nginx-container
    links:
      - uwsgi
    build: ./nginx
    ports:
      - "8888:80"
    volumes_from:
      - uwsgi
  uwsgi:
    container_name: uwsgi-container
    build: ./uwsgi
    expose:
      - "8001"
    volumes:
      - ./code/conf:/opt/conf
      - ./code/app:/opt/app

Dockerfile for uWSGI:

FROM python:3.5
RUN ["pip", "install", "uwsgi"]
CMD ["uwsgi", "--master", "--ini", "/opt/conf/uwsgi.ini"]

Dockerfile for nginx:

FROM nginx
RUN ["rm", "/etc/nginx/conf.d/default.conf"]
RUN ["ln", "-s", "/opt/conf/nginx.conf", "/etc/nginx/conf.d/"]
CMD ["nginx", "-g", "daemon off;"]

And this is my directory tree:

├── README.md
├── code
│   ├── app
│   └── conf
├── collectstatic.sh
├── docker-compose.yml
├── install.sh
├── migrate.sh
├── nginx
│   └── Dockerfile
├── restart.sh
├── upgrade.sh
├── uwsgi
│   └── Dockerfile

So I build my images with docker-compose build then launch new containers in background with docker-compose up -d then I can perform some setup tasks like installing django, generate secret key or whatever you want to have your container ready.

nginx and uwsgi containers uses shared volumes to read config files AND to communicate with a file socket.

Well, not sure that is the best way, but it's a work in progress.

Solution 2:

I found this answer from Michael Hampton:

"This only works if the processes are in the same host, VM or container, because it tries to make a connection to the same machine. When they are in different containers, it does not work.

You need to alter your nginx configuration so that it uses the internal IP address of the uwsgi container."

And definitely is something you have to keep in mind if you will have Nginx in a different container, also you have to set the nginx.conf, pointing your statics file directory as alias preventing a security issue.

I hope this code work for everybody, it took me a couple of hours to figure about how to compose Gunicorn, docker, and Nginx:

# nginx.conf
  upstream djangoA {
       server $DOCKER_CONTAINER_SERVICE:9000 max_fails=3 fail_timeout=0;
       # In my case looks like: web:9000
  }
Server {
    include mime.types;
    # The port your site will be served on      
    listen 80;
    # the domain name it will serve for
    server_name $YOUR_SERVER_NAME;# substitute your machine's IP address or FQDN
    charset utf-8;
    #Max upload size
    client_max_body_size 512M;   # adjust to taste
    location /site_media {
       alias $DIRECTORY_STATIC_FILES/site_media;#your Django project's media   files have to be inside of the container have nginxs, you can copy them with volumes.
    expires 30d;
    }

    location / {
      try_files $uri @proxy_to_app;
    }

   # Finally, send all non-media requests to the Django server.
   location @proxy_to_app {
     proxy_set_header X-Real-IP $remote_addr;
     proxy_redirect off;
     proxy_set_header Host $host;
     proxy_pass http://djangoA;
   }
}

And for the docker-compose:

#production.yml
version: '2'

services:
  db:
    extends:
      file: base.yml
      service: db

  nginx:
    image: nginx:latest
  volumes:
      - ./nginx:/etc/nginx/conf.d/
      - ./$STATIC_FILE_ROOT/site_media:/$STATIC_FILE_ROOT/site_media
  ports:
      - "80:80"
    depends_on:
      - web


  web:
    extends:
      file: base.yml
      service: web
    build:
      args:
        - DJANGO_ENV=production
    command: bash -c "python manage.py collectstatic --noinput && chmod 775 -R project/site_media/static && gunicorn project.wsgi:application"
    volumes:
      - ./$DIRECTORY_APP:/$DIRECTORY_APP
    ports:
      - "9000:9000"
    depends_on:
      - db

volumes:
  db_data:
    external: true

Solution 3:

I finally managed to build my docker-compose for Django, Gunicorn, Nginx and Postgres thanks (mostly) to this tutorial.

Basically, you have one container for Nginx, one container for Django+Gunicorn, and one container (or more if you want multiple databases) for Postgres.

You could also split Django+Gunicorn in two containers, but I think it makes more sense to have them both in one container, because one Gunicorn process always runs one Django wsgi application. I don't think you can have either N Gunicorn running 1 Django, 1 Gunicorn running N Django, or N Gunicorn running N Django, so there is no scaling possibilities here, except scaling N Gunicorn+Django containers.

Volumes are used to keep databases' data (and optionnaly Django's static/media files) permanently.

You were asking for a tutorial or other ways to do it, so I will not post my docker-compose.yml contents here (because it would also require the Dockerfile, the Nginx conf file, the Gunicorn conf file, the databases env files, and many snippets of Python code like DATABASES settings), but I will try to make a tutorial when I have time and post the link in a edit/comment.

Meanwhile, the link I provided above is a very good starting point.

EDIT: you can now take a look at this docker setup example and the related blog post.