Production Django Stack
ready Django with Nginx, Gunicorn, PostgreSQL, Redis, and Celery for async tasks
Overview
Django is a high-level Python web framework that follows the model-template-view architectural pattern, originally developed at Lawrence Journal-World newspaper in 2003 and open-sourced in 2005. Known for its 'batteries included' philosophy, Django provides built-in admin interface, ORM, authentication, and security features that enable rapid development of secure, maintainable web applications. This production stack combines Django with Gunicorn as the WSGI server, NGINX as the reverse proxy, PostgreSQL for robust data persistence, Redis for caching and message brokering, and Celery for distributed task processing.
This architecture addresses the core challenges of running Django applications at scale: NGINX handles static file serving and SSL termination while reverse proxying dynamic requests to Gunicorn workers, PostgreSQL provides ACID-compliant data storage with advanced querying capabilities, Redis serves dual roles as Django's cache backend and Celery's message broker, while Celery workers process time-intensive tasks asynchronously to prevent request timeouts. The inclusion of Celery Beat enables scheduled task execution, making this stack suitable for applications requiring background job processing, periodic data synchronization, and email sending.
This configuration is ideal for startups moving from development to production, established companies deploying content management systems, e-commerce platforms, or SaaS applications that require reliable background task processing. The stack provides the horizontal scaling capabilities needed for growing user bases while maintaining the development velocity that makes Django attractive to Python teams.
Key Features
- Gunicorn WSGI server with configurable worker processes for handling concurrent Django requests
- NGINX reverse proxy with static file serving and request buffering for improved performance
- PostgreSQL ACID-compliant database with JSON support and full-text search capabilities
- Redis dual-purpose deployment serving as Django cache backend and Celery message broker
- Celery distributed task queue with separate worker containers for background job processing
- Celery Beat scheduler for periodic task execution and cron-like job scheduling
- Multi-container architecture enabling independent scaling of web workers and task processors
- Shared volume mounts for Django static files and media uploads across containers
Common Use Cases
- 1Content management systems requiring image processing and thumbnail generation
- 2E-commerce platforms with order processing, inventory updates, and email notifications
- 3SaaS applications needing user onboarding workflows and scheduled data exports
- 4News or blog sites with search indexing and scheduled content publication
- 5Financial applications requiring background report generation and data aggregation
- 6Social platforms with notification systems and periodic cleanup tasks
- 7Multi-tenant applications requiring tenant-specific background processing
Prerequisites
- Minimum 2GB RAM to accommodate PostgreSQL (1GB), Redis (512MB), and multiple Python processes
- Port 80 available for NGINX HTTP traffic (configurable via NGINX_PORT environment variable)
- Django application with properly configured settings.py for database and Redis connections
- Understanding of Celery task decorators and Django's collectstatic command
- Familiarity with Gunicorn worker configuration and Django WSGI deployment
- Basic knowledge of PostgreSQL user management and database creation
For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms
docker-compose.yml
docker-compose.yml
1services: 2 nginx: 3 image: nginx:alpine4 container_name: django-nginx5 restart: unless-stopped6 ports: 7 - "${NGINX_PORT:-80}:80"8 volumes: 9 - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro10 - ./static:/app/static:ro11 - ./media:/app/media:ro12 depends_on: 13 - web1415 web: 16 build: 17 context: ./app18 dockerfile: Dockerfile19 container_name: django-web20 restart: unless-stopped21 command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 422 volumes: 23 - ./app:/app24 - ./static:/app/static25 - ./media:/app/media26 environment: 27 - DEBUG=${DEBUG:-False}28 - SECRET_KEY=${SECRET_KEY}29 - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}30 - REDIS_URL=redis://redis:6379/031 - CELERY_BROKER_URL=redis://redis:6379/132 depends_on: 33 - db34 - redis3536 celery: 37 build: 38 context: ./app39 dockerfile: Dockerfile40 container_name: django-celery41 restart: unless-stopped42 command: celery -A config worker -l info43 volumes: 44 - ./app:/app45 environment: 46 - DEBUG=${DEBUG:-False}47 - SECRET_KEY=${SECRET_KEY}48 - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}49 - REDIS_URL=redis://redis:6379/050 - CELERY_BROKER_URL=redis://redis:6379/151 depends_on: 52 - db53 - redis5455 celery-beat: 56 build: 57 context: ./app58 dockerfile: Dockerfile59 container_name: django-celery-beat60 restart: unless-stopped61 command: celery -A config beat -l info62 volumes: 63 - ./app:/app64 environment: 65 - DEBUG=${DEBUG:-False}66 - SECRET_KEY=${SECRET_KEY}67 - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}68 - CELERY_BROKER_URL=redis://redis:6379/169 depends_on: 70 - celery7172 db: 73 image: postgres:16-alpine74 container_name: django-db75 restart: unless-stopped76 environment: 77 - POSTGRES_USER=${DB_USER}78 - POSTGRES_PASSWORD=${DB_PASSWORD}79 - POSTGRES_DB=${DB_NAME}80 volumes: 81 - postgres_data:/var/lib/postgresql/data8283 redis: 84 image: redis:7-alpine85 container_name: django-redis86 restart: unless-stopped87 volumes: 88 - redis_data:/data8990volumes: 91 postgres_data: 92 redis_data: .env Template
.env
1# Django Production Stack2NGINX_PORT=803DEBUG=False45# Security6SECRET_KEY=your-super-secret-key-change-in-production78# Database9DB_USER=django10DB_PASSWORD=django_password11DB_NAME=django_dbUsage Notes
- 1Create ./app/Dockerfile with your Django app
- 2Configure nginx.conf for static files and proxy
- 3Run migrations: docker compose exec web python manage.py migrate
- 4Collect static: docker compose exec web python manage.py collectstatic
- 5Celery handles async tasks, beat handles scheduled tasks
- 6Scale workers: docker compose up -d --scale celery=3
Individual Services(6 services)
Copy individual services to mix and match with your existing compose files.
nginx
nginx:
image: nginx:alpine
container_name: django-nginx
restart: unless-stopped
ports:
- ${NGINX_PORT:-80}:80
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./static:/app/static:ro
- ./media:/app/media:ro
depends_on:
- web
web
web:
build:
context: ./app
dockerfile: Dockerfile
container_name: django-web
restart: unless-stopped
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4
volumes:
- ./app:/app
- ./static:/app/static
- ./media:/app/media
environment:
- DEBUG=${DEBUG:-False}
- SECRET_KEY=${SECRET_KEY}
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
depends_on:
- db
- redis
celery
celery:
build:
context: ./app
dockerfile: Dockerfile
container_name: django-celery
restart: unless-stopped
command: celery -A config worker -l info
volumes:
- ./app:/app
environment:
- DEBUG=${DEBUG:-False}
- SECRET_KEY=${SECRET_KEY}
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
depends_on:
- db
- redis
celery-beat
celery-beat:
build:
context: ./app
dockerfile: Dockerfile
container_name: django-celery-beat
restart: unless-stopped
command: celery -A config beat -l info
volumes:
- ./app:/app
environment:
- DEBUG=${DEBUG:-False}
- SECRET_KEY=${SECRET_KEY}
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
- CELERY_BROKER_URL=redis://redis:6379/1
depends_on:
- celery
db
db:
image: postgres:16-alpine
container_name: django-db
restart: unless-stopped
environment:
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql/data
redis
redis:
image: redis:7-alpine
container_name: django-redis
restart: unless-stopped
volumes:
- redis_data:/data
Quick Start
terminal
1# 1. Create the compose file2cat > docker-compose.yml << 'EOF'3services:4 nginx:5 image: nginx:alpine6 container_name: django-nginx7 restart: unless-stopped8 ports:9 - "${NGINX_PORT:-80}:80"10 volumes:11 - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro12 - ./static:/app/static:ro13 - ./media:/app/media:ro14 depends_on:15 - web1617 web:18 build:19 context: ./app20 dockerfile: Dockerfile21 container_name: django-web22 restart: unless-stopped23 command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 424 volumes:25 - ./app:/app26 - ./static:/app/static27 - ./media:/app/media28 environment:29 - DEBUG=${DEBUG:-False}30 - SECRET_KEY=${SECRET_KEY}31 - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}32 - REDIS_URL=redis://redis:6379/033 - CELERY_BROKER_URL=redis://redis:6379/134 depends_on:35 - db36 - redis3738 celery:39 build:40 context: ./app41 dockerfile: Dockerfile42 container_name: django-celery43 restart: unless-stopped44 command: celery -A config worker -l info45 volumes:46 - ./app:/app47 environment:48 - DEBUG=${DEBUG:-False}49 - SECRET_KEY=${SECRET_KEY}50 - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}51 - REDIS_URL=redis://redis:6379/052 - CELERY_BROKER_URL=redis://redis:6379/153 depends_on:54 - db55 - redis5657 celery-beat:58 build:59 context: ./app60 dockerfile: Dockerfile61 container_name: django-celery-beat62 restart: unless-stopped63 command: celery -A config beat -l info64 volumes:65 - ./app:/app66 environment:67 - DEBUG=${DEBUG:-False}68 - SECRET_KEY=${SECRET_KEY}69 - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}70 - CELERY_BROKER_URL=redis://redis:6379/171 depends_on:72 - celery7374 db:75 image: postgres:16-alpine76 container_name: django-db77 restart: unless-stopped78 environment:79 - POSTGRES_USER=${DB_USER}80 - POSTGRES_PASSWORD=${DB_PASSWORD}81 - POSTGRES_DB=${DB_NAME}82 volumes:83 - postgres_data:/var/lib/postgresql/data8485 redis:86 image: redis:7-alpine87 container_name: django-redis88 restart: unless-stopped89 volumes:90 - redis_data:/data9192volumes:93 postgres_data:94 redis_data:95EOF9697# 2. Create the .env file98cat > .env << 'EOF'99# Django Production Stack100NGINX_PORT=80101DEBUG=False102103# Security104SECRET_KEY=your-super-secret-key-change-in-production105106# Database107DB_USER=django108DB_PASSWORD=django_password109DB_NAME=django_db110EOF111112# 3. Start the services113docker compose up -d114115# 4. View logs116docker compose logs -fOne-Liner
Run this command to download and set up the recipe in one step:
terminal
1curl -fsSL https://docker.recipes/api/recipes/production-django-stack/run | bashTroubleshooting
- 502 Bad Gateway from NGINX: Check if Gunicorn is running on port 8000 and web container is healthy
- Celery workers not processing tasks: Verify CELERY_BROKER_URL points to Redis and tasks are properly registered
- Static files not loading: Run 'docker compose exec web python manage.py collectstatic' and check NGINX volume mounts
- PostgreSQL connection refused: Ensure DATABASE_URL environment variable matches POSTGRES_* settings in db service
- High memory usage: Reduce Gunicorn workers from 4 to 2-3 or implement connection pooling for PostgreSQL
- Celery Beat duplicate tasks: Only run one celery-beat container and check for proper task scheduling configuration
Community Notes
Loading...
Loading notes...
Download Recipe Kit
Get all files in a ready-to-deploy package
Includes docker-compose.yml, .env template, README, and license
Components
nginxdjangogunicornpostgresrediscelery
Tags
#django#python#production#celery#nginx#postgres#redis
Category
Full Web StacksAd Space
Shortcuts: C CopyF FavoriteD Download