Deploying Django app on AWS ECS using Docker(GUNICORN, NGINX)

In this article we will learn how to package Django app into Docker. And then running this Docker image on ECS.

ECS is an EC2 cluster service offered by AWS, is highly scalable and easy to manage docker images.

Prerequisites:

  1. Installed Docker (https://docs.docker.com/engine/installation/)
  2. AWS account with AWS client installed on local machine(optional)
  3. Basic understanding of Gunicorn and Nginx

Setting up Docker Image

First check Docker is installed properly:

$ docker info

For running Docker container, we have generally three steps:

  1. Create Dockerfile (think it as a requirement.txt or Gemfile or build.sbt)
  2. Build Docker Image using Dockerfile
  3. Run Docker image on a container

So let’s start creating our Dockerfile. First we will make a directory where we will have our Dockerfile and other config files.

$ mkdir gunicorn_docker
$ cd gunicorn_docker
$ mkdir code

code directory will have the django app.

Now create a file with name Dockerfile. And paste the code given below:

############################################################
# Dockerfile to run a Django-based web application
# Based on an AMI
############################################################
# Set the base image to use to Ubuntu
FROM ubuntu:14.04

# Set the file maintainer (your name - the file's author)
MAINTAINER Rohit Khatana
# Set env variables used in this Dockerfile (add a unique prefix, such as DOCKYARD)
# Local directory with project source
ENV DOCKYARD_SRC=code/django_app
# Directory in container for all project files
ENV DOCKYARD_SRVHOME=/srv
# Directory in container for project source files
ENV DOCKYARD_SRVPROJ=$DOCKYARD_SRVHOME/$DOCKYARD_SRC

# Update the default application repository sources list
RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y python python-pip
RUN apt-get install -y python-dev
RUN apt-get install -y libmysqlclient-dev
RUN apt-get install -y git
RUN apt-get install -y vim
RUN apt-get install -y mysql-server
RUN apt-get install -y nginx
# Create application subdirectories
WORKDIR $DOCKYARD_SRVHOME
RUN mkdir media static logs
#read
VOLUME ["$DOCKYARD_SRVHOME/media/", "$DOCKYARD_SRVHOME/logs/"]
# Copy application source code to SRCDIR
COPY $DOCKYARD_SRC $DOCKYARD_SRVPROJ
# Install Python dependencies
RUN pip install -r $DOCKYARD_SRVPROJ/requirement.txt
# Port to expose
EXPOSE 8000
# Copy entrypoint script into the image
WORKDIR $DOCKYARD_SRVPROJ
COPY ./docker-entrypoint.sh /
COPY ./django.conf /etc/nginx/sites-available/
RUN ln -s /etc/nginx/sites-available/django_nginx.conf /etc/nginx/sites-enabled
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
ENTRYPOINT ["/docker-entrypoint.sh"]

Read the above file carefully, this file is self-explanatory.

But let’s have a step-by-step walk-through:

First, we define the base image, then we define the maintainer (author name). After this we define the environment variable for source code (DOCKYARD_SRC), which will then be used to create the source project. DOCKYARD_SRVHOME variable will be used for binding volume for media and log files.

Then we update the OS and install the basic dependencies for the project.

WORKDIR command is used for changing the current directory to respected directory.

Then we expose the port on which docker container will listen. And finally we copy docker-entrypoint.sh and django_nginx.conf (Nginx config for the current Django app). and then symlink the new conf with Nginx’s sites-enabled.

And most importantly we add daemon off; in nginx.conf, as docker expects the last process to be long running.

ENTRYPOINT command is used to register all necessary scripts which is required to run first before running any other command.

So usually entrypoint scripts have a one time setup, like creating static files, migration etc.

Now as we have our Dockerfile setup, we will create our docker-entrypoint.sh which will have Django migration, collect static commands, run gunicorn in background and finally starts Nginx.

#!/bin/bash
python manage.py migrate # Apply database migrations
python manage.py collectstatic --clear --noinput # clearstatic files
python manage.py collectstatic --noinput # collect static files
# Prepare log files and start outputting logs to stdout
touch /srv/logs/gunicorn.log
touch /srv/logs/access.log
tail -n 0 -f /srv/logs/*.log &
echo Starting nginx 
# Start Gunicorn processes
echo Starting Gunicorn.
exec gunicorn cv_django_merchandising.wsgi:application \
--name cv_django_merchandising \
--bind unix:django_app.sock \
--workers 3 \
--log-level=info \
--log-file=/srv/logs/gunicorn.log \
--access-logfile=/srv/logs/access.log &
exec service nginx start

Now we will look at our django_nginx.conf file

server {
listen 80;
server_name ~^(.+)$;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /srv/code/django_app;
}
location / {
include proxy_params;
proxy_pass http://unix:/srv/code/django_app/django_app.sock;
}
}

This is the normal Nginx configuration for Django in which we have defined the listen port for Nginx, then whitelisted every domain. Then we define our location for Django static file, which will be served by Nginx. And finally we bind the root with django_app.sock file for reverse proxy.

Now our directory should look like this:

$ ls gunicorn_docker
code/django_app
Dockerfile
docker-entrypoint.sh
django_nginx.conf

Finally we will now build our django_nginx image:

$ docker build -t author/gunicorn-docker .

This will build the docker image with the name: author/gunicorn-docker. Make sure you are in the gunicorn_docker folder, as build command expects Dockerfile in the current directory.

Sit tight, it will take some time, as first it will download the ubuntu image and then will install all the necessary packages and finally copy the source code into docker image. Then it will install the requirement.txt …

When it is done (image building process):

For listing the images:

$ docker images

And now you can run the docker image:

$ docker run --detach=false --publish=8000:80 author/gunicorn-docker

At this time, docker will invoke the ENTRYPOINT scripts. So at this time database migration, static file creation, gunicorn process creation and finally starting nginx will be done.

Now in your browser you can start using this django app which is running in docker container.

You can also run the docker container in detach mode: (first kill the above running process)

$ docker run --detach=true --publish=8000:80 author/gunicorn-docker

You can see the running container by using this command:

$ docker ps

If you want to go inside the container:

$ docker exec -it container-id bash

Now you have your working docker image.

If you want to deploy this image on AWS ECS you have to follow these simple steps:

  1. Install the aws command line client and login
  2. $ aws ecr get-login --region ap-southeast-1
  3. Build docker image and tag it with the latest repository
  4. $ cd gunicorn_docker
  5. $ docker build -t author/gunicorn-docker .
  6. $ docker tag author/gunicorn-docker:latest some-id.dkr.ecr.ap-southeast-1.amazonaws.com/author/gunicorn-docker:latest
  7. Push to AWS repository
  8. $ docker push some-id.dkr.ecr.ap-southeast- 1.amazonaws.com/author/gunicorn-docker:latest
  9. Register TASK definition with ECS (you can easily create the task file on aws console)
  10. $ aws ecs register-task-definition — cli-input-json file://gunicorn_docker_task.json
  11. Now you can run this task using AWS console.

Congratulations on finishing the setup of Docker in ECS. In addition, you also know how to run Gunicorn app with Nginx!