Django on AWS Lambda works — but most tutorials skip the real tradeoffs that matter in production. Cold starts, database connection limits, static file handling, and the 15-minute execution limit all affect whether Lambda is the right choice for your Django application.
This is the honest guide.
When Lambda Makes Sense for Django
Lambda is well-suited for Django when:
- Traffic is bursty or unpredictable — Lambda scales to zero and back up automatically. You pay nothing when there is no traffic.
- It is an API-only service — No static files served by Django, no admin interface needed at the Lambda layer.
- Requests are short-lived — Under 30 seconds per request, not running long-running background jobs.
- You want to avoid server management — No EC2 instance to patch, no systemd services to monitor.
Lambda is a poor fit for Django when:
- The application has persistent background workers — Celery workers cannot run on Lambda.
- You need Django admin — The admin works but cold starts make it painful.
- Database connections are heavy — Lambda can spin up many concurrent instances, each opening its own database connection. 1,000 Lambda instances = 1,000 database connections. PostgreSQL on an
db.t3.microRDS instance has a connection limit of ~87. - Response time consistency is critical — Cold starts add 1–3 seconds of latency to the first request after a period of inactivity.
Architecture: Django + Lambda + API Gateway
Client → API Gateway → Lambda (Django via Mangum) → RDS PostgreSQL
→ S3 (media files)
Static files must be on S3 or a CDN — Lambda's filesystem is ephemeral.
Setup with Mangum
Mangum is the ASGI adapter that makes Lambda speak Django:
pip install mangum
Create the Lambda handler:
# handler.py
import os
import django
from mangum import Mangum
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings.production")
django.setup()
from myproject.asgi import application
handler = Mangum(application, lifespan="off")
ASGI config:
# myproject/asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings.production")
application = get_asgi_application()
Production Settings for Lambda
# settings/production.py
import os
DEBUG = False
SECRET_KEY = os.environ["SECRET_KEY"]
# RDS database
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],
"PORT": "5432",
"CONN_MAX_AGE": 0, # Do NOT use persistent connections on Lambda
}
}
# S3 for static and media files
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage"
AWS_STORAGE_BUCKET_NAME = os.environ["AWS_BUCKET_NAME"]
AWS_S3_REGION_NAME = "ap-south-1"
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/"
Critical: Set CONN_MAX_AGE = 0 on Lambda. Persistent connections are created per Lambda instance — and each instance holds its connection even when idle. With hundreds of concurrent Lambda instances, you exhaust the database connection limit quickly.
Database Connection Pooling with RDS Proxy
The connection limit problem is solved by RDS Proxy:
Lambda instances (many) → RDS Proxy (pooled connections) → RDS PostgreSQL
RDS Proxy maintains a pool of persistent connections to the database and multiplexes Lambda connections through them. Each Lambda instance connects to RDS Proxy (cheap connection), and RDS Proxy manages the actual PostgreSQL connections.
Enable via the AWS Console on your RDS instance → Proxies → Create proxy. Then update DB_HOST to the proxy endpoint instead of the direct RDS endpoint.
Handling Cold Starts
Cold starts occur when Lambda spins up a new container for your function. For Django, this means importing Django, loading settings, and initializing apps. Cold start time: 1–3 seconds for a typical Django application.
Strategies to reduce cold starts:
Use Lambda Provisioned Concurrency — keeps a set number of Lambda instances warm at all times. Costs more but eliminates cold starts for those instances.
Reduce package size — smaller deployment packages initialize faster. Use --slim flag with Zappa or exclude dev dependencies.
Use a Lambda Layer — put dependencies (Django, DRF, etc.) in a Lambda Layer that is reused across invocations, reducing the unzipping overhead.
Minimize Django app loading — remove unused INSTALLED_APPS. Every app's AppConfig.ready() runs on every cold start.
Deployment with Zappa
Zappa simplifies Lambda deployment for Django/Flask:
pip install zappa
zappa init
zappa_settings.json:
{
"production": {
"app_function": "handler.handler",
"aws_region": "ap-south-1",
"s3_bucket": "my-zappa-deployments",
"runtime": "python3.12",
"environment_variables": {
"DJANGO_SETTINGS_MODULE": "myproject.settings.production"
},
"vpc_config": {
"SubnetIds": ["subnet-xxx"],
"SecurityGroupIds": ["sg-xxx"]
}
}
}
Put Lambda in the same VPC as RDS so it can connect without a public IP.
Deploy:
zappa deploy production
zappa manage production migrate
zappa manage production collectstatic --no-input
Static Files: Run collectstatic Before Deploy
Django's collectstatic must run as part of the deployment pipeline — not during a Lambda invocation (Lambda's filesystem is temporary and read-only):
# In your CI/CD pipeline:
python manage.py collectstatic --no-input
# Files are uploaded to S3 by django-storages automatically
zappa update production
Lambda vs EC2: The Honest Comparison
| Factor | Lambda | EC2 |
|---|---|---|
| Cost at zero traffic | $0 | ~$8–15/mo |
| Cost at high, steady traffic | Higher | Lower |
| Cold starts | 1–3s latency | None |
| Background workers | Not possible | Celery on same server |
| Database connections | Needs RDS Proxy | Direct, persistent |
| Static files | S3 required | Nginx can serve |
| Server management | None | Required |
| Setup complexity | Medium-high | Medium |
For a new Django API service with unpredictable traffic: Lambda is a reasonable choice. For a full Django application with admin, background tasks, and steady traffic: EC2 is simpler and cheaper.
If you need a Django application deployed to AWS — whether EC2, Lambda, or a hybrid with both — hire me as an AWS developer or as a Django developer.