I followed these steps to create and deploy defjoy.site
Deploying a Wagtail CMS application to production can seem daunting, but with the right tools and configuration, it becomes a straightforward process. In this comprehensive guide, I'll walk you through deploying a Wagtail application using Docker, Nginx, Gunicorn, PostgreSQL, and securing it with Let's Encrypt SSL certificates.
What We'll Build
By the end of this tutorial, you'll have:
- A production-ready Wagtail CMS running in Docker containers
- PostgreSQL database for persistent storage
- Nginx as a reverse proxy serving static files
- SSL/HTTPS enabled with Let's Encrypt certificates
- Automatic certificate renewal
Prerequisites
Before we begin, you'll need:
- A DigitalOcean Ubuntu 24.04 LTS server (or similar VPS)
- A domain name with DNS access
- Basic familiarity with Linux command line
- SSH access to your server
Step 1: DNS Configuration
First, configure your domain's DNS records. In your domain registrar's DNS settings (Namecheap, GoDaddy, etc.), add these records:
| Host | Type | Value |
|---|---|---|
@ |
A Record | YOUR_SERVER_IP |
www |
CNAME | yourdomain.com |
Note: DNS propagation can take up to 48 hours, but usually completes within a few hours.
Step 2: Setting Up SSH Keys
SSH keys provide secure authentication for both GitHub and your server.
Generate a new SSH key
ssh-keygen -t ed25519 -C "your_email@example.com"
Add the key to ssh-agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
Add to GitHub
Display your public key:
cat ~/.ssh/id_ed25519.pub
Copy the output and paste it into GitHub → Settings → SSH and GPG Keys → New SSH Key.
Test the connection
ssh -T git@github.com
You should see a success message confirming authentication.
Step 3: Installing Docker and Docker Compose
Docker will containerize our application, making deployment consistent and reproducible.
Update system packages
sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Add Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER
Important: Log out and back in (or reboot) for group changes to take effect.
Step 4: PostgreSQL Database Setup
PostgreSQL will serve as our production database.
Install PostgreSQL
sudo apt install -y python3-dev libpq-dev postgresql postgresql-contrib
Create database and user
Switch to the postgres user and access the PostgreSQL prompt:
sudo su - postgres
psql
Run these SQL commands:
CREATE DATABASE your_database_name;
CREATE USER your_db_user WITH PASSWORD 'your_secure_password';
ALTER ROLE your_db_user SET client_encoding TO 'utf8';
ALTER ROLE your_db_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE your_db_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE your_database_name TO your_db_user;
Grant schema permissions:
sudo -u postgres psql your_database_name
GRANT ALL ON SCHEMA public TO your_db_user;
ALTER SCHEMA public OWNER TO your_db_user;
GRANT CREATE, USAGE ON SCHEMA public TO your_db_user;
Exit the PostgreSQL prompt:
\q
exit
Configure PostgreSQL for Docker access
Edit the PostgreSQL configuration file:
sudo nano /etc/postgresql/16/main/postgresql.conf
Find and set:
listen_addresses = '*'
Edit the host-based authentication file:
sudo nano /etc/postgresql/16/main/pg_hba.conf
Add these lines at the end:
host all all 172.17.0.0/16 md5
host all all 172.18.0.0/16 md5
Restart PostgreSQL:
sudo systemctl restart postgresql
Verify PostgreSQL is listening
ss -tln | grep 5432
You should see 0.0.0.0:5432 in the output.
Step 5: Clone Your Project
Create the project directory and clone your repository:
sudo mkdir -p /opt/your-project-name
sudo chown -R $USER:$USER /opt/your-project-name
cd /opt/your-project-name
git clone git@github.com:yourusername/your-repo.git .
Step 6: Environment Configuration
Create a .env file in your project root:
nano .env
Add your environment variables:
DJANGO_SECRET_KEY=your-secret-key-here
DJANGO_SETTINGS_MODULE=your_project.settings.production
DB_NAME=your_database_name
DB_USER=your_db_user
DB_PASSWORD=your_secure_password
DB_HOST=host.docker.internal
DB_PORT=5432
Security tip: Generate a strong secret key using
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
Step 7: Dockerfile Configuration
Create a Dockerfile in your project root:
FROM python:3.12-slim-bookworm
RUN useradd wagtail
ENV PYTHONUNBUFFERED=1
ENV PORT=8000
RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
build-essential libpq-dev libjpeg62-turbo-dev zlib1g-dev libwebp-dev \
&& rm -rf /var/lib/apt/lists/*
RUN pip install "gunicorn==20.0.4"
COPY requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
WORKDIR /app
RUN chown -R wagtail:wagtail /app
COPY --chown=wagtail:wagtail . .
USER wagtail
EXPOSE 8000
CMD set -xe; \
python manage.py migrate --noinput; \
gunicorn your_project.wsgi:application --bind 0.0.0.0:8000 --workers 3
Step 8: Docker Compose Setup
Create docker-compose.yml:
services:
web:
build: .
restart: always
env_file:
- .env
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "8000:8000"
Build and run containers
docker compose build
docker compose up -d
Step 9: Static Files Management
Fix permissions for static and media directories:
sudo chown -R $USER:$USER staticfiles
sudo chown -R $USER:$USER media
Collect static files:
docker compose exec web python manage.py collectstatic --noinput
Step 10: Nginx Reverse Proxy Configuration
Install Nginx if not already installed:
sudo apt install -y nginx
Create the Nginx configuration file:
sudo nano /etc/nginx/sites-available/your_project.conf
Add this configuration:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location /static/ {
alias /opt/your-project-name/staticfiles/;
}
location /media/ {
alias /opt/your-project-name/media/;
}
client_max_body_size 20M;
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;
}
}
Enable the site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/your_project.conf \
/etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Step 11: SSL Certificate with Let's Encrypt
Install Certbot:
sudo apt install -y certbot python3-certbot-nginx
Once your site is accessible via HTTP, obtain an SSL certificate:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Follow the prompts to complete the setup.
Test automatic renewal
sudo certbot renew --dry-run
Step 12: Django Production Security Settings
Add these settings to your production.py settings file:
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Restart the Docker container:
docker compose restart web
Step 13: Verification
Test your application
curl -I https://yourdomain.com
Test static file serving
curl -I https://yourdomain.com/static/css/core.css
Both should return:
HTTP/1.1 200 OK
Troubleshooting Tips
Container logs
View logs if something isn't working:
docker compose logs -f web
Nginx logs
Check Nginx error logs:
sudo tail -f /var/log/nginx/error.log
Database connection issues
Test database connectivity from inside the container:
docker compose exec web python manage.py dbshell
Static files not loading
Ensure permissions are correct and paths match in your Nginx config and Django settings.
Conclusion
Congratulations! You now have a production-ready Wagtail CMS deployment with:
- ✅ Containerized application using Docker
- ✅ PostgreSQL database
- ✅ Nginx reverse proxy
- ✅ SSL/HTTPS encryption
- ✅ Automatic certificate renewal