- Deploying Django to AWS with Docker and Let's Encrypt In this tutorial, we'll deploy a Django app to AWS EC2 with Docker. The app will run behind an HTTPS Nginx proxy with Let's Encrypt SSL certificates. We'll use AWS RDS to serve our Postgres database along with AWS ECR to.
- This is a step-by-step tutorial that details how to configure Django to run on Docker with Postgres. For production environments, we'll add on Nginx and Gunicorn. We'll also take a look at how to serve Django static and media files via Nginx. Dependencies: Django v3.0.7; Docker v19.03.8; Python v3.8.3; Django on Docker Series.
Django is a Python-based web framework for model-template-view (MTV) based architectural patterns. It's open-source and is maintained by the Django Software Foundation. Django provides an easier path for developers to produce database-driven websites. There is lots of other cases where loaddata and dumpdata commands don't work. So pgdump and psql are good, but the downside of them is that you lose the database configuration that is stored in the project settings (or the environment if you are 12factor aware).
Getting your Django app on the internet is - pardon my language - a pain in the ass.
You need to daemonize your app with something like Supervisor, then you need to setup a production ready database, then you need to setup a web-server using the likes of Nginx or Apache. And when it's all done, you need to setup files and directories to collect and maintain logs from your app.
You could do all that, or you could use Docker.
Here's what you'll learn
In this tutorial you'll learn to deploy a Django app with Docker and docker-compose. By the end of the tutorial, you'll have a single command that you can use with any Django app to deploy it to a server.
To complete this tutorial, you will need:
Django Docker Logs
- Familiarity with using the command line.
- Familiarity with Python virtual environments. We will use a virtual environment to isolate the dependencies of our project. To install the python virtual environment module use:
pip install virtualenv.
- A basic understanding of YAML files.
Step 0 — Installing Dependencies
To follow this tutorial, you will need both Docker and Docker-Compose on your machine. Follow the steps below depending on your operating system.
Installing on Ubuntu
You can install Docker Engine on your system by using the convenience script provided by Docker.
To install Docker-Compose, first download the binaries from the Github repo. Then apply executable permissions to the binary.
Installing on a Mac
Docker Desktop for Mac already includes Docker-Compose. To install Docker Desktop on Mac, download the package from here and run the installer.
Note: If the above steps don't work for you, you can go to this link and find more information depending on your operating system.
Step 1 — Setting Up The Django Project
For following along, you can make a sample Django project or follow the steps with your existing project. I suggest making a sample app and then implementing it on your existing project so that you can tweak things to suit your purpose.
Create a new virtual environment.
Install Django and make a new Django project.
List out the requirements in a
djangoproject/settings.py file, change the
ALLOWED_HOSTS to the following:
Doing this allows us to access the Django app from outside the container.
Step 1 — Writing The Dockerfile
The first step to containerising our Django app is making a Dockerfile. In the
djangoproject directory, make a new file named
Dockerfile and put the following in it:
This Dockerfile is used to build the container for your Django app. Each line is a step of the build. We will make a lot of changes in this, but for now, it works.
Why you should copy the requirements.txt file before copying the code
While building the container, Docker caches stages of the build. So if you change your code but the
requirements.txtfile is the same, Docker doesn't have to install all the requirements again for building the container.
Step 2 — Writing the Docker-Compose File
To use Docker-Compose, we need to make a
docker-compose.yaml file. In this file, we will define three different containers as services and then connect them with each other. Then we can use a single command to run our containers together. These services are:
- the Django app container,
- the database container and
- the Nginx webserver container.
We will add each of these services to the
docker-compose.yaml file one by one, starting with the web app. Make a new
docker-compose.yaml file in the
deployment-project directory with the following contents:
Above, we defined a service for the web app. Let's it breakdown and see what each option does:
container_namedefines the hostname of the container. This can be used by other containers to refer to this container.
buildtakes the path of the Dockerfile directory.
commandtakes a command you can run in the container after start up. In this case, we run the Django server on all available interfaces at port
portstakes a list of ports to bind to the ports of your host machine. Above, we bind the port
8000of the container to the port
8000of your host. This means that you can access the Django app at
localhost:8000of your machine.
Let's test if everything works until now. Run the following command.
You should see the Django app running inside the container. The
--build flag is used to tell Docker to build the container.
Step 3 — Setting Up The Database
Let's set up the database now. We will be using a PostgreSQL database for our app. To set this up, we will have to complete the following steps:
- Add a database service to the docker-compose file.
- Define environment variables for the PostgreSQL database.
- Configure our Django app to use the database we just created. This includes connecting to the database, making migrations and migrating.
docker-compose.yaml file to look like the following:
Again, a breakdown of the changes:
imagetells docker which image to use. In this case, we tell it to pull whichever image is associated with the
latesttag for postgres.
restart: alwaysmakes sure the database always restarts when it exits. Other options for this are
env_fileis used to put the environment variables in a file and tell Docker to initialize the container with those variables. We will create this file soon.
volumesis used for data persistency. It would be a shame if our database was empty every time we started it and destroyed its data everytime we turned it off. Docker uses volumes to store data that must be persistent on disk. Above, we define a new volume and tell docker to save the contents of
/var/lib/postgresql/datainside that volume. Note that we have to define volumes in the
volumessection in the bottom.
depends_ontells Docker that the web service depends on the db service.
Now, lets create the
project.env file we mentioned above. Make a new file called
project.env in the
deployment-project directory with the following contents. We'll add more to this file later.
Next, we will configure the Django app to connect to the database. To connect to the database, Django needs to have a driver installed. For PostgreSQL, that driver is
You should install
psycopg2-binary instead of
psycopg2 if you want to install psycopg2 without install PostgreSQL on your system. Now, update the
Now we will configure the Django settings. First, make a new file called
keyconfig.py in the
djangoproject/djangoproject directory. We will save credentials and other sensitive information here. The purpose of using a keyconfig instead of directly loading environment variables in
settings.py is that you can separate parts of your config using classes. Add the following contents to the
djangoproject/djangoproject/settings.py and import the
keyconfig.py file like this:
Now, set the Secret key:
And the database:
Now, we want to
migrate everytime the container starts up. But the problem is that the database container can take longer to initialise and if we run the above commands before the database is up and running, it can result in an error. So we need a way to make sure the database has started. For this, we use an entrypoint script. In the
djangoproject directory, make a new file called
entrypoint.sh with the following contents.
The above script first waits for the database to start up. We do this by using
netcat to ping the server in a while loop. Then it makes migrations and performs the migrations. The
--noinput flag is used when we are using the command inside a script so that it does not prompt the user for input. Next, we need to make the file executable so that Docker can run it.
Now change the Dockerfile to use this script. Add the following lines at the end of the Dockerfile:
We need to install netcat as it is not installed by default. Once again, let's test if everything is running. Go to the
deployment-project directory and spin up the containers.
You can use the
-d flag to make the containers run in the background.
Step 4 — Using Nginx to Serve the Django app
In this step, we will set up another container to use Nginx to serve our django project. For this, we will do the following:
- Install and use gunicorn as our WSGI server
- Configure nginx as a reverse proxy for the gunicorn server.
- Add an nginx service to the docker-compose file.
Gunicorn is a production level WSGI server we will use to serve our Django project. To begin let's, install gunicorn.
To use gunicorn as our WSGI server, we use the following command:
Docker Django Mysql
In the above command,
djangoproject.wsgi is the name of the WSGI module to use. The
--bind takes address of the socker to bind to. In this case, we tell it to bind to port
8000 of all available interfaces. The
--workers flag takes the number of workers to be initialized by gunicorn, we set it to 4 here.
Once again, update the
Next, let's make a configuration file for the
deployment-project directory, make a directory called
nginx.conf file and add the following:
Next, we add the
nginx service to the docker-compose file and also use gunicorn for the Django app. Open the
docker-compose.yaml file and change it to the following:
- In the
volumessection in nginx, we mount the local
./nginxdirectory to the
/etc/nginx/conf.ddirectory in the container.
depends_on, we say that the
nginxservice depends on the
- Instead of using
portsin the web service, we use
exposeto expose the port
8000of the container to other containers on the network. But, in the nginx service, we use
portsto bind the port
80of the container to the port
1337of the host machine.
The difference between ports and expose
Use ports when you want to access the port from the host machine's localhost. Use expose when you want to expose the port to other containers in the network.
Again, test that everything is working by spinning up the containers.
Test that you can see the webserver by going to
localhost:1337 in your webbrowser.
Step 5 — The Dockerfile, Revisited
Before configuring the staticfiles, we will take another look at the Dockerfile and restructure it. Previously, we were running the processes inside the Django container as a root user. But, according to the Docker documentation, the best practice is to run your processes as non-privileged user within the containers. So, in this step, we will restructure the Dockerfile and also make a new user to run our app inside the container.
Make a new Dockerfile with the following contents.
Copy and install the requirements.
Copy the code and make the app user the owner of the directory.
Change the user to
app and run the entrypoint.
Test that everything works by spinning up the containers.
Verify that the server is running by going to
localhost:1337 in your browser.
Step 6 — Serving Static Files with Nginx
We need to configure nginx to serve our staticfiles because Django does not serve staticfiles in production. First, we configure Django to use a
staticfiles directory. Open
settings.py and add the following in the staticfiles section:
We tell Django that the staticfiles are going to be served at the
/static/ path. And that the files are going to be in the
Next, open the
nginx.conf file and add the following:
We tell Nginx to serve the contents of the
staticfiles directory at the
Django Docker File
Next, we need to edit the entrypoint script to collect the static files. Add the following at the end of the
To let Nginx access the staticfiles collected by Django, we will store them in a volume and add this volume to both the
Django Docker Compose
And add this volume to the volumes section:
Check that the staticfiles have loaded by spinning up the containers and going to the admin page at
localhost:1337/admin/ you should see the CSS loaded.
If you reached until here, you will have a ready to deploy Django app which will be up and running in just one command. You can use this configuration for your own Django apps and deploy them to a server with only a few extra steps.
The full code for this guide is available on Github here.