Deploy Django to AWS Lambda
Deploy Django to AWS Lambda with Zappa or a container image and the Lambda Web Adapter: package size limits, static files on S3, database connections with RDS Proxy, cold starts, and when serverless Django is a poor fit.
Problem statement
Running Django on AWS Lambda means paying only for requests, scaling to zero, and never patching a server. It is attractive for low-traffic sites, internal tools, and spiky APIs. But Lambda is not a server: each request may land on a fresh execution environment, there is no persistent disk, connections to databases multiply with concurrency, the deployment package has size limits, and the first request after idle pays a cold start. Django works on Lambda when you design around those constraints.
- Choose a packaging method: Zappa (zip-based, manages API Gateway for you) or a container image with the AWS Lambda Web Adapter running Gunicorn-free Django via any WSGI server.
- Keep the package small; container images allow up to 10 GB, zip packages 250 MB unzipped.
- Put static files on S3 behind CloudFront (
collectstaticto S3), and media on S3. - Connect to PostgreSQL through RDS Proxy to avoid exhausting connections, with
CONN_MAX_AGE = 0or short reuse. - Run migrations with a one-off invocation (
zappa manage prod migrate) in your pipeline. - Use provisioned concurrency or a scheduled warm-up if cold starts matter.
Step-by-step solution
1. Option A: Zappa
pip install zappa
zappa init
A minimal zappa_settings.json:
{
"prod": {
"django_settings": "config.settings.production",
"aws_region": "eu-west-1",
"runtime": "python3.12",
"s3_bucket": "myapp-zappa-deployments",
"memory_size": 1024,
"timeout_seconds": 30,
"vpc_config": { "SubnetIds": ["subnet-a", "subnet-b"], "SecurityGroupIds": ["sg-lambda"] },
"environment_variables": { "DJANGO_SETTINGS_MODULE": "config.settings.production" }
}
}
zappa deploy prod
zappa update prod # later deploys
zappa manage prod migrate
Zappa creates the Lambda function and API Gateway, and translates API Gateway events into WSGI requests.
2. Option B: container image with Lambda Web Adapter
The Lambda Web Adapter lets an ordinary HTTP server run inside Lambda. Add it to a normal Django image:
FROM public.ecr.aws/docker/library/python:3.12-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
ENV PORT=8000 AWS_LWA_READINESS_CHECK_PATH=/health/
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "1", "--threads", "4"]
Push to ECR, create a Lambda function from the image, and front it with a Function URL or API Gateway. The same image can also run on ECS or App Runner, which keeps your options open.
3. Static and media files on S3
Lambda's filesystem is read-only except /tmp, which is temporary. Use django-storages:
STORAGES = {
"default": {"BACKEND": "storages.backends.s3.S3Storage", "OPTIONS": {"bucket_name": "myapp-media"}},
"staticfiles": {"BACKEND": "storages.backends.s3.S3Storage", "OPTIONS": {"bucket_name": "myapp-static", "location": "static"}},
}
Run collectstatic in CI to upload assets, and serve them through CloudFront. WhiteNoise also works with images, but serving assets through Lambda costs invocations.
4. Database connections
Each concurrent Lambda environment opens its own database connection. A burst to 300 concurrent requests means 300 connections, which exceeds many RDS instance limits. Put RDS Proxy between Lambda and PostgreSQL, keep CONN_MAX_AGE low (0 to 60), and enable CONN_HEALTH_CHECKS. Lambda in a VPC needs a NAT gateway or VPC endpoints to reach the internet and AWS APIs.
5. Cold starts and timeouts
A cold start imports Django and your apps, typically 1-3 seconds depending on dependencies and memory size (more memory also means more CPU). Reduce import-time work, avoid heavy libraries at module level, and use provisioned concurrency for latency-sensitive endpoints. API Gateway REST APIs cap integration time at 29 seconds by default; move long work to SQS-triggered functions or Celery elsewhere.
Verification check: invoke the URL and check the response and duration in CloudWatch Logs; confirm static assets come from CloudFront, a write works through RDS Proxy, and the proxy's connection count stays bounded during a short load test.
Explanation
Lambda runs code in short-lived execution environments that AWS creates and freezes on demand. A WSGI adapter (Zappa's handler or the Web Adapter) turns each invocation into a normal HTTP request for Django, so application code barely changes. What changes is everything Django usually assumes about a server: local disk for media, a long-lived process for connection reuse, and a warm interpreter. Externalising files to S3, pooling connections in RDS Proxy, and controlling cold starts address those assumptions.
Edge cases / notes
- Background tasks. Use SQS with a second Lambda function, or Zappa's async tasks, instead of Celery workers.
- Scheduled jobs. EventBridge schedules can invoke management commands (
zappasupportsevents). - Binary dependencies must be built for Amazon Linux and the target architecture (x86_64 or arm64).
- Admin uploads of large files hit the 6 MB synchronous payload limit; upload directly to S3 with presigned URLs.
Next steps
- A container service without cold starts: Deploy Django on AWS App Runner.
- Serve assets at the edge with Serve Django Static Files From CloudFront.
FAQ
Can Django run on AWS Lambda?
Yes, with Zappa or a container image using the Lambda Web Adapter, as long as files and connections are externalised.
How do I handle database connections from Lambda?
Use RDS Proxy to pool connections and keep Django's connection reuse short.
Are cold starts a problem?
They add one to a few seconds on the first request to a new environment. Provisioned concurrency removes them for a cost.
Is Lambda cheaper than a server for Django?
For low or spiky traffic, often yes. For steady high traffic, containers or servers are usually cheaper.