Configuring Nginx on Ubuntu to reverse proxy a Django application running inside a Docker container might sound complex, but it’s a straightforward process once you break it down. In this guide, I’ll walk you through each step, assuming you’re familiar with Ubuntu, Nginx, Docker, and Django basics. Our goal? Set up Nginx to seamlessly forward HTTP requests to a Django app powered by a WSGI server (like Gunicorn) inside a Docker container. Let’s dive in!
Table of Contents
- Prerequisites
- Set Up Your Django App in a Docker Container
- Install Nginx on the Ubuntu Host
- Configure Nginx as a Reverse Proxy
- Handle Static Files
- Enable HTTPS using Certbot
Prerequisites
Before we get started, make sure you have the following in place:
- Ubuntu Server: A system running Ubuntu (22.04, or later) with admin access.
- Docker: Installed and ready on your Ubuntu machine.
- Django App in a Docker Container: A Django app running inside Docker, served by Gunicorn or a similar WSGI server.
- Nginx: Installed on the host Ubuntu system (we’ll cover this step).
- Domain or IP: A domain name (optional) or your server’s public IP to access the app.
Got everything? Great—let’s set it up!
Set Up Your Django App in a Docker Container
First, we need a Django app running inside a Docker container. Assume you have a Django application in a folder.
- Define Dependencies in
requirements.txt
:
django>=5.1.7,<5.2
gunicorn==23.0.0
mysqlclient>=2.2.7,<2.3
- Create a Dockerfile:
# Use the official Python runtime image
FROM python:3.13
# Create the app directory
RUN mkdir /app
# Set the working directory inside the container
WORKDIR /app
# Set environment variables
# Prevents Python from writing pyc files to disk
ENV PYTHONDONTWRITEBYTECODE=1
#Prevents Python from buffering stdout and stderr
ENV PYTHONUNBUFFERED=1
# Install system dependencies (gcc, mysql-client for MySQL database connection, curl for Node.js)
RUN apt update && apt install -y \
gcc \
default-libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*
# Upgrade pip
RUN pip install --upgrade pip
# Copy the Django project and install dependencies
COPY requirements.txt /app/
# run this command to install all dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the Django project to the container
COPY . /app/
# Expose the Django port
EXPOSE 8000
# Start the application using Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "my-django-app.wsgi:application"]
- Create a docker compose file
services:
django-app:
build:
context: .
dockerfile: Dockerfile
container_name: django-app
restart: always
volumes:
- ./staticfiles:/app/staticfiles
ports:
- "8000:8000"
environment:
- DB_HOST=db # Reference environment variable from .env file
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- SECRET_KEY=${SECRET_KEY}
- DEBUG=${DEBUG}
- DJANGO_ALLOWED_HOSTS=${DJANGO_ALLOWED_HOSTS}
- CSRF_TRUSTED_ORIGINS=${CSRF_TRUSTED_ORIGINS}
depends_on:
- db # Make sure the database service starts first
env_file:
- .env
command: >
sh -c "python manage.py wait_for_db &&
python manage.py migrate &&
python manage.py collectstatic --noinput &&
gunicorn --bind 0.0.0.0:8000 blogproject.wsgi:application"
db:
image: mysql:8.0.41
volumes:
- mysql_db:/var/lib/mysql # Persist the database data
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} # Use the root password from .env file
- MYSQL_DATABASE=${DB_NAME} # The database name from .env file
ports:
- "3306:3306" # MySQL default port
env_file:
- .env
volumes:
mysql_db: # Named volume for MySQL data persistence
- Create
.env
file
DB_NAME=mydb
DB_USER=db_user
DB_PASSWORD=db_password
MYSQL_ROOT_PASSWORD=db_root_password
SECRET_KEY=secret_key
DEBUG=False
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
CSRF_TRUSTED_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
- Update
settings.py
To use environment variables from .env
file
SECRET_KEY = os.environ.get("SECRET_KEY")
DEBUG = bool(os.environ.get("DEBUG", default=0))
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS","127.0.0.1").split(",")
CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS").split(",")
To serve static file correctly
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "static"
]
STATIC_ROOT = BASE_DIR / 'staticfiles'
- Build and Run:
docker compose up -d --build
This spins up your Django app on port 8000
inside the container, mapped to the same port on the host. Test it by running curl http://localhost:8000
.
Install Nginx on the Ubuntu Host
Next, let’s install Nginx on the host system—it’ll act as our reverse proxy, sitting outside the Docker container.
- Update your package index and install Nginx:
sudo apt update
sudo apt install nginx
- Check its status:
sudo systemctl status nginx
If it says active (running)
, you’re good.
- Open port 80 on nginx
sudo ufw app list
sudo ufw allow 'Nginx HTTP'
sudo ufw status
Open http://<server-ip>
in your browser—you should see Nginx’s default welcome page.
Configure Nginx as a Reverse Proxy
Now, let’s configure Nginx to forward incoming requests to your Django app.
- Create a Config File:
Open a new file in /etc/nginx/sites-available/
:
sudo nano /etc/nginx/sites-available/my-django-app
- Add the Configuration: Paste this, tweaking as needed:
server {
listen 80;
server_name your-domain.com www.your-domain.com;
location / {
proxy_pass http://127.0.0.1:8000;
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/ {
alias /var/www/my-django-app/static;
}
}
Here’s what’s happening:
listen 80
: Handles HTTP traffic.server_name
: Your domain or IP (use_
if no domain).proxy_pass
: Sends requests to the Django app at127.0.0.1:8000
.proxy_set_header
: Passes key headers so Django knows the client’s details.-
location /static/
: Serve static files. -
Enable It: Link it to
sites-enabled
:
sudo ln -s /etc/nginx/sites-available/my-django-app /etc/nginx/sites-enabled/
- Test and Restart:
sudo nginx -t
sudo systemctl restart nginx
If the test passes (syntax is ok
), Nginx is ready to proxy requests!
Handle Static Files
For performance, Nginx should serve Django’s static and media files directly, not through the app. When running docker compose up
, docker runs python manage.py collectstatic --noinput
automatically and collect static files in staticfiles
folder. We need to copy them to a host directory (e.g., /var/www/my-django-app/static/
):
sudo docker cp ~/django-app/staticfiles /var/www/my-django-app/static
- Set Permissions:
sudo chown -R www-data:www-data /var/www/my-django-app/static
sudo chmod -R 755 /var/www/my-django-app/static
Now Nginx handles these files efficiently.
Enable HTTPS using Certbot
For production, set up HTTPS using Certbot
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo ufw status
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
sudo ufw status
sudo certbot --nginx -d example.com -d www.example.com
sudo systemctl status snap.certbot.renew.service
This setup gives you a robust foundation for running Django with Nginx on Ubuntu.