Rohan Yeole - Homepage Rohan Yeole

Django on AWS Lambda: When Serverless Makes Sense and How to Set It Up

Apr 18, 20261 min read

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.micro RDS 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

FactorLambdaEC2
Cost at zero traffic$0~$8–15/mo
Cost at high, steady trafficHigherLower
Cold starts1–3s latencyNone
Background workersNot possibleCelery on same server
Database connectionsNeeds RDS ProxyDirect, persistent
Static filesS3 requiredNginx can serve
Server managementNoneRequired
Setup complexityMedium-highMedium

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.

Chat with me on WhatsApp