I am working on a Django project that is in the deployment phase. I am trying to deploy it on a VPS. While reading about deployments on the internet, I found that the most common way to deploy Django is using Gunicorn and Nginx. I have a data analytics background so I rely on internet research to learn and configure things related to web server. Nginx is a high-performance, open-source software widely used as web server, reverse proxy, load balancer and HTTP cache. In my case, the main function is to handle traffic from port 80 / 443 and redirect it to the port where the Django project is running. Once Nginx was configured, I was able to access my page but it was under the HTTP. To use the HTTPS we have to pay for a certificate or use a service like Let’s Encrypt to create one and use it for free. We can do it through Certbot. At this point, it wasn’t clear to me how my Docker Compose file and my Dockerfile should be structured.
After some research, I found this discussion on Reddit. The solution proposed by WobbyGoneCrazy was a lifesaver. I was able to accomplish the task following exactly what he suggested. Here I will expand on it a little more with information about how I achieved it.
Create a Nginx Dockerfile
The key point here is to have Certbot running in the same container where the Nginx is running. To do this, we have to create a custom Dockerfile for our Nginx service that includes Certbot. It should look like the file below:
FROM python:3.9-slim
RUN apt-get update -qq && apt-get -y install apache2-utils certbot python3-certbot-nginx
CMD ["nginx", "-g", "daemon off;"]
Create nginx.conf file
The nginx.conf file is the configuration file for the nginx service. Here, we can configure things like file compression, redirection and how the static files are served. You can probably do a lot more with Nginx as you gain more experience. I will show my configuration and highlight things that I consider important. Here is what my final file looks:
# Main context
events {
# Event-related settings (e.g., worker connections)
worker_connections 1024;
}
http {
# HTTP-related settings and global configurations for all servers
server {
listen 80;
gzip on;
gzip_types text/css application/javascript text/xml;
gzip_proxied any;
server_name example.com # Replace with your domain or IP
location / {
proxy_pass http://web:port; # Forward requests to the Django app
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
include /etc/nginx/mime.types; # Include MIME types
alias /app/static/; # Adjust path if necessary
expires 30d; # Cache for 30 days
access_log off;
}
}
}
The worker_connections
in the event
block sets the maximum number of simultaneous traffic that each worker process can handle.
The HTTP block contains global configurations related to the HTTP protocol and define virtual servers. The configured server listens on port 80.
The following block is responsible for compressing HTTP responses. We can configure which types of content can be compressed.
gzip on;
gzip_types text/css application/javascript text/xml;
gzip_proxied any;
The server_name
defines the domain names and IP addresses that this server will handle. We can set this configuration with multiple values separated by spaces.
The proxy_pass at the location “/” defines the redirection that occurs when the user makes requests to the root of the server. As we are using Docker, we can use the name of the service in this configuration. For a standard Django application, the port should be 8000.
The location
static
defines how static files are served. The key point here is the alias
configuration. It acts as a mapping for the actual static file location (inside the Django application container).
location /static/ {
include /etc/nginx/mime.types; # Include MIME types
alias /app/static/; # Adjust path if necessary
expires 30d; # Cache for 30 days
access_log off;
}
In this step, ir is important to keep in mind your project folder structure because the alias path may vary. I will show an example of what my folder structure looks like so you can map it to your own folder structure:
-.docker/ # my docker files are stored here
-- nginx/
-- app /
- app / # web app code. My static folder are inside this folder.
- docker-compose.yml
- nginx.conf
Create a Docker Compose
The docker compose defines configurations for the services. Pay attention to the service names because they are in the same network and you can use them in the Nginx configuration (as shown in the proxy_pass of the location “/” block ). Other key points are the volumes created in the Nginx service. They will made available the nginx configuration file, the static folder of you Django app and folders to store Certbot HTTPS related files. Below is an example of how my docker-compose looks like:
services:
web:
build: .docker/app
container_name: web
tty: true
command: gunicorn --bind 0.0.0.0:8000 mysite.wsgi
volumes:
- ./app:/app
ports:
- 8000:8000
env_file:
- .env
networks:
- mysite-network
nginx:
build: .docker/nginx
container_name: nginx
ports:
- 80:80 # Map host's port 80 to container's port 80
- 443:443
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # Mount custom Nginx configuration
- ./app/static:/app/static
- ./data/certbot/letsencrypt:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
depends_on:
- web
env_file:
- .env
networks:
- mysite-network
networks:
mysite-network:
driver: bridge
Use Certbot to get the free HTTPS certificate
Use docker compose up
to build and start your services. With the services running, use docker exec -it <nginx_container_id> bash
to access a terminal in this container. In the opened container terminal, use certbot –nginx to generate your HTTPS certificate. There will be a wizard-like process where the Certbot will ask additional information to complete the task (email, domain information and so on). Aswer the questions and wait the process to finish. At the end of this step you will be able to access your application using HTTPS.
Configure automatic renew
The HTTPS certificate is valid for a 3-month period. It is highly recommended to set up an automatic renewal process. Use crontab -e
to access the crontab file on your server and add a line like the following to schedule a weekly automatic renewal 22 23 * * THU -l -c 'docker exec nginx-container certbot renew'
I hope you find it useful!