Skip to main content

Deployment Guide

The Django-CFG sample project includes production-ready Docker configuration and deployment best practices. This guide covers Docker setup, environment configuration, and deployment procedures.

Docker Setup

Dockerfile

Production-ready Dockerfile:

# Dockerfile
FROM python:3.11-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV DJANGO_SETTINGS_MODULE=api.settings

# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

# Create app directory
WORKDIR /app

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy project
COPY . .

# Collect static files
RUN python manage.py collectstatic --noinput

# Create non-root user
RUN adduser --disabled-password --gecos '' appuser
RUN chown -R appuser:appuser /app
USER appuser

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/cfg/status/ || exit 1

# Start application
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "api.wsgi:application"]

Docker Compose

Multi-container setup with Docker Compose:

# docker-compose.yml
version: '3.8'

services:
web:
build: .
ports:
- "8000:8000"
environment:
- DJANGO_SETTINGS_MODULE=api.settings
- IS_PROD=true
volumes:
- ./media:/app/media
- ./static:/app/static
depends_on:
- db
- redis
command: gunicorn --bind 0.0.0.0:8000 --workers 4 api.wsgi:application

db:
image: postgres:15
environment:
POSTGRES_DB: djangocfg_sample
POSTGRES_USER: djangocfg
POSTGRES_PASSWORD: secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"

redis:
image: redis:7-alpine
volumes:
- redis_data:/data
ports:
- "6379:6379"

rearq:
build: .
command: rearq main:rearq worker
environment:
- DJANGO_SETTINGS_MODULE=api.settings
- IS_PROD=true
depends_on:
- db
- redis
volumes:
- ./media:/app/media

nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./static:/app/static
- ./media:/app/media
- ./docker/nginx/ssl:/etc/nginx/ssl
depends_on:
- web

volumes:
postgres_data:
redis_data:

Nginx Configuration

Nginx as reverse proxy:

# docker/nginx/nginx.conf
upstream django {
server web:8000;
}

server {
listen 80;
server_name myapp.com www.myapp.com;

# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;

# SSL configuration
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;

# Max upload size
client_max_body_size 50M;

# Static files
location /static/ {
alias /app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}

# Media files
location /media/ {
alias /app/media/;
expires 7d;
add_header Cache-Control "public";
}

# Django application
location / {
proxy_pass http://django;
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;
proxy_redirect off;
}
}

Production Configuration

Environment Variables

Set production environment variables:

# .env.production
SECRET_KEY=your-super-secret-key-change-in-production
DEBUG=false
DJANGO_ENV=prod
IS_PROD=true

# Database
DATABASE_URL=postgresql://user:password@db:5432/djangocfg_sample
DB_HOST=db
DB_PORT=5432
DB_NAME=djangocfg_sample
DB_USER=djangocfg
DB_PASSWORD=secure_password

# Redis
REDIS_URL=redis://redis:6379/0

# Email (SendGrid)
SENDGRID_API_KEY=SG.xxxxxxxxxxxxx
EMAIL_FROM=noreply@myapp.com

# Twilio
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your-auth-token
TWILIO_VERIFY_SERVICE_SID=VAxxxxxxxxxxxxx
TWILIO_PHONE_NUMBER=+1234567890

# Telegram
TELEGRAM_BOT_TOKEN=123456:ABC-DEF
TELEGRAM_CHAT_ID=@your_channel

# Domain
DOMAIN=myapp.com
ALLOWED_HOSTS=myapp.com,www.myapp.com

# Security (SSL handled by nginx reverse proxy)
# SECURE_SSL_REDIRECT not needed - Django-CFG defaults to reverse proxy mode
SESSION_COOKIE_SECURE=true
CSRF_COOKIE_SECURE=true

Production YAML Configuration

# api/environment/config.prod.yaml
secret_key: "<from-yaml-config>" # Loaded from environment
debug: false
is_prod: true

# Database configuration
database:
url: "postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"

# Application settings
app:
name: "Django CFG Sample"
domain: "${DOMAIN}"

security_domains: ["${DOMAIN}", "www.${DOMAIN}"]

# Email configuration
email:
sendgrid_api_key: "${SENDGRID_API_KEY}"
from_email: "${EMAIL_FROM}"

# Twilio configuration
twilio:
account_sid: "${TWILIO_ACCOUNT_SID}"
auth_token: "${TWILIO_AUTH_TOKEN}"
verify_service_sid: "${TWILIO_VERIFY_SERVICE_SID}"
phone_number: "${TWILIO_PHONE_NUMBER}"

# Telegram configuration
telegram:
bot_token: "${TELEGRAM_BOT_TOKEN}"
chat_id: "${TELEGRAM_CHAT_ID}"

# Cache configuration (Redis)
cache:
backend: "django_redis.cache.RedisCache"
location: "${REDIS_URL}"

# Static files (served by nginx)
static:
url: "/static/"
root: "/app/static/"

# Media files (served by nginx)
media:
url: "/media/"
root: "/app/media/"

# Security settings
# Note: SSL/TLS handled by nginx reverse proxy
# secure_ssl_redirect not needed - Django-CFG defaults to reverse proxy mode
security:
session_cookie_secure: true
csrf_cookie_secure: true
secure_browser_xss_filter: true
secure_content_type_nosniff: true
secure_hsts_seconds: 31536000

# Logging
logging:
level: "INFO"
format: "json"

See Configuration for complete configuration details.

Deployment Procedures

Initial Deployment

Deploy for the first time:

# 1. Clone repository
git clone https://github.com/yourusername/myapp.git
cd myapp

# 2. Set environment variables
cp .env.production .env
# Edit .env with your production values

# 3. Build and start containers
docker-compose up -d --build

# 4. Run migrations
docker-compose exec web python manage.py migrate

# 5. Create superuser
docker-compose exec web python manage.py createsuperuser

# 6. Collect static files
docker-compose exec web python manage.py collectstatic --noinput

# 7. Check status
docker-compose ps
curl http://localhost/cfg/status/

Updating Deployment

Deploy updates:

# 1. Pull latest code
git pull origin main

# 2. Rebuild containers
docker-compose up -d --build

# 3. Run new migrations
docker-compose exec web python manage.py migrate

# 4. Collect static files
docker-compose exec web python manage.py collectstatic --noinput

# 5. Restart services
docker-compose restart web rearq

# 6. Verify deployment
curl http://localhost/cfg/status/

Zero-Downtime Deployment

Deploy without downtime:

# 1. Build new image
docker-compose build web

# 2. Start new containers
docker-compose up -d --scale web=2 --no-recreate

# 3. Run migrations
docker-compose exec web python manage.py migrate

# 4. Stop old containers
docker-compose stop old_web

# 5. Remove old containers
docker-compose rm -f old_web

# 6. Scale back to 1
docker-compose up -d --scale web=1

Database Management

Database Backup

Backup PostgreSQL database:

# Manual backup
docker-compose exec db pg_dump -U djangocfg djangocfg_sample > backup.sql

# Automated backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.sql"

docker-compose exec -T db pg_dump -U djangocfg djangocfg_sample > $BACKUP_FILE

# Upload to S3
aws s3 cp $BACKUP_FILE s3://myapp-backups/

# Keep only last 30 days
find . -name "backup_*.sql" -mtime +30 -delete

Database Restore

Restore from backup:

# Restore from backup file
docker-compose exec -T db psql -U djangocfg djangocfg_sample < backup.sql

# Restore from S3
aws s3 cp s3://myapp-backups/backup_20231201_120000.sql - | \
docker-compose exec -T db psql -U djangocfg djangocfg_sample

Running Migrations

# Run all migrations
docker-compose exec web python manage.py migrate

# Run specific app migrations
docker-compose exec web python manage.py migrate blog

# Show migration status
docker-compose exec web python manage.py showmigrations

# Create new migrations
docker-compose exec web python manage.py makemigrations

Monitoring

Health Checks

Monitor application health:

# Check health endpoint
curl http://localhost/cfg/status/

# Response
{
"status": "healthy",
"checks": {
"database": {"status": "healthy"},
"cache": {"status": "healthy"},
"email": {"status": "healthy"}
}
}

Log Monitoring

View application logs:

# View all logs
docker-compose logs -f

# View specific service
docker-compose logs -f web
docker-compose logs -f rearq

# View last 100 lines
docker-compose logs --tail=100 web

Container Monitoring

Monitor container resources:

# View container stats
docker stats

# View specific service
docker stats djangocfg_web_1

# View disk usage
docker system df

Security Best Practices

1. Secure Secret Keys

# ✅ Good: Environment variable
SECRET_KEY=$(openssl rand -hex 50)

# ❌ Bad: Hard-coded in code
SECRET_KEY = "insecure-key"

2. Use HTTPS

# ✅ Good: Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;

# ❌ Bad: Allow HTTP
listen 80;

3. Set Security Headers

# ✅ Good: Security headers
add_header Strict-Transport-Security "max-age=31536000";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";

4. Limit File Upload Size

# ✅ Good: Limit upload size
client_max_body_size 50M;

# ❌ Bad: Unlimited uploads

5. Run as Non-Root User

# ✅ Good: Non-root user
USER appuser

# ❌ Bad: Run as root
USER root

Performance Optimization

Gunicorn Workers

Optimize worker count:

# Formula: (2 * CPU_CORES) + 1
# For 2 CPU cores: 5 workers
gunicorn --workers 5 --bind 0.0.0.0:8000 api.wsgi:application

Database Connection Pooling

Configure connection pooling:

# api/config.py
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name=env.database.name,
user=env.database.user,
password=env.database.password,
host=env.database.host,
port=env.database.port,
options={
"connect_timeout": 10,
"options": "-c statement_timeout=30000",
"pool_size": 20,
"max_overflow": 0,
}
),
}

Redis Caching

Enable Redis caching:

# api/config.py
cache: CacheConfig = CacheConfig(
backend="django_redis.cache.RedisCache",
location=env.redis.url,
options={
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {
"max_connections": 50,
"retry_on_timeout": True,
},
}
)

Deployment Checklist

Before deploying to production:

  • Set DEBUG=false
  • Generate secure SECRET_KEY
  • Configure allowed hosts
  • Set up SSL/HTTPS
  • Configure production database
  • Set up Redis for caching
  • Configure email service (SendGrid)
  • Set up Twilio for SMS
  • Configure Telegram notifications
  • Enable security headers
  • Set up automated backups
  • Configure log aggregation
  • Set up monitoring/alerts
  • Test health checks
  • Run security audit
  • Document deployment process

Troubleshooting

Container Won't Start

# Check logs
docker-compose logs web

# Check container status
docker-compose ps

# Rebuild container
docker-compose up -d --build --force-recreate web

Database Connection Issues

# Check database is running
docker-compose ps db

# Test database connection
docker-compose exec db psql -U djangocfg djangocfg_sample

# Check environment variables
docker-compose exec web env | grep DB_

Static Files Not Loading

# Collect static files
docker-compose exec web python manage.py collectstatic --noinput

# Check nginx logs
docker-compose logs nginx

# Verify static file path
docker-compose exec web ls -la /app/static/

Proper deployment ensures your Django-CFG application runs reliably in production!