Deploy Django on AWS With the CDK
Deploy Django on AWS with the Cloud Development Kit in Python: an ApplicationLoadBalancedFargateService, RDS PostgreSQL, Secrets Manager, S3 media, and a migration task, with cdk diff and cdk deploy in CI.
Problem statement
Running Django on ECS Fargate involves a VPC, an ALB, a cluster, task definitions, IAM roles, security groups, RDS, secrets, and log groups. Defining all of that by hand in CloudFormation YAML is thousands of lines; doing it in the console is unrepeatable. The AWS CDK lets you write it in Python with high-level constructs: one ApplicationLoadBalancedFargateService creates the load balancer, service, task definition, and wiring, while you focus on the Django-specific parts.
npm install -g aws-cdk, thencdk init app --language pythonininfra/andcdk bootstraponce per account and region.- Create a
Vpc, aDatabaseInstance(PostgreSQL) with generated credentials, and an S3 bucket for media. - Use
ecs_patterns.ApplicationLoadBalancedFargateServicewith your Django image, secrets from Secrets Manager, and a/health/health check. - Grant the task role access to the bucket and the database security group.
- Run migrations as a one-off Fargate task in the deploy pipeline before updating the service.
- Use
cdk diffin pull requests andcdk deployon merge.
Step-by-step solution
1. Initialise and bootstrap
mkdir infra && cd infra
cdk init app --language python
pip install -r requirements.txt
cdk bootstrap aws://123456789012/eu-west-1
2. Define the stack
# infra/infra/django_stack.py
from aws_cdk import Stack, Duration, RemovalPolicy, aws_ec2 as ec2, aws_ecs as ecs, \
aws_ecs_patterns as patterns, aws_rds as rds, aws_s3 as s3, aws_secretsmanager as sm
from constructs import Construct
class DjangoStack(Stack):
def __init__(self, scope: Construct, id: str, **kw):
super().__init__(scope, id, **kw)
vpc = ec2.Vpc(self, "Vpc", max_azs=2, nat_gateways=1)
db = rds.DatabaseInstance(
self, "Db", vpc=vpc,
engine=rds.DatabaseInstanceEngine.postgres(version=rds.PostgresEngineVersion.VER_16),
instance_type=ec2.InstanceType("t4g.small"),
credentials=rds.Credentials.from_generated_secret("app"),
database_name="app", multi_az=False,
removal_policy=RemovalPolicy.SNAPSHOT, deletion_protection=True,
)
media = s3.Bucket(self, "Media", block_public_access=s3.BlockPublicAccess.BLOCK_ALL)
app_secret = sm.Secret.from_secret_name_v2(self, "AppSecret", "prod/django")
svc = patterns.ApplicationLoadBalancedFargateService(
self, "Web", vpc=vpc, cpu=512, memory_limit_mib=1024, desired_count=2,
task_image_options=patterns.ApplicationLoadBalancedTaskImageOptions(
image=ecs.ContainerImage.from_asset("../"),
container_port=8000,
environment={"DJANGO_SETTINGS_MODULE": "config.settings.production",
"AWS_STORAGE_BUCKET_NAME": media.bucket_name},
secrets={
"SECRET_KEY": ecs.Secret.from_secrets_manager(app_secret, "SECRET_KEY"),
"DB_PASSWORD": ecs.Secret.from_secrets_manager(db.secret, "password"),
"DB_HOST": ecs.Secret.from_secrets_manager(db.secret, "host"),
},
),
public_load_balancer=True,
)
svc.target_group.configure_health_check(path="/health/", healthy_http_codes="200")
svc.target_group.set_attribute("deregistration_delay.timeout_seconds", "30")
db.connections.allow_default_port_from(svc.service)
media.grant_read_write(svc.task_definition.task_role)
ContainerImage.from_asset("../") builds the Dockerfile and pushes it to an ECR repository the CDK manages. Add a certificate and domain with the certificate and domain_name properties plus a Route 53 zone.
3. Settings that read the injected values
DATABASES = {"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "app", "USER": "app",
"PASSWORD": os.environ["DB_PASSWORD"], "HOST": os.environ["DB_HOST"],
"CONN_MAX_AGE": 60, "OPTIONS": {"sslmode": "require"},
}}
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
4. Migrations
Run migrations with the same task definition and a command override before rolling out:
aws ecs run-task --cluster $CLUSTER --task-definition $TASKDEF --launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SG]}" \
--overrides '{"containerOverrides":[{"name":"web","command":["python","manage.py","migrate","--noinput"]}]}'
Export the cluster, task definition family, subnets, and security group as CfnOutputs so the pipeline can find them. Wait for the task to stop with exit code 0 before deploying.
5. CI workflow
- run: npx cdk diff # on pull requests
- run: npx cdk deploy --require-approval never # on main
Give CI an IAM role through GitHub OIDC with permission to assume the CDK bootstrap roles. Review cdk diff output for replacements of stateful resources.
Verification check: cdk diff shows no changes after deploy, the ALB DNS name (or your domain) serves the app over HTTPS, the target group lists two healthy tasks, and the migration task's logs in CloudWatch end with OK.
Explanation
The CDK is a framework that synthesises CloudFormation templates from code. Its constructs come in levels: L1 maps one-to-one to CloudFormation resources, L2 adds sensible defaults and helper methods (grant_read_write, connections), and L3 patterns combine many resources into a common architecture. For Django on Fargate, the L3 pattern covers the generic web-service plumbing, leaving the application-specific integration explicit and short. CloudFormation then applies changes transactionally and rolls back failed updates.
Edge cases / notes
- Construct IDs determine logical IDs; renaming one can replace the resource. Never rename the database construct.
- Celery workers use
QueueProcessingFargateServiceor a plainFargateServicewithout a load balancer. - Costs. NAT gateways are a significant fixed cost; one is enough for staging.
- Context and environments. Use separate stacks per environment with explicit account and region.
Next steps
- Understand the target platform in Deploy Django on AWS ECS Fargate.
- A multi-cloud alternative in Python: Provision Django Infrastructure With Pulumi.
FAQ
Do I need Node.js to use the CDK with Python?
Yes, the CDK CLI runs on Node.js, while your stack code is Python.
How do migrations run with a CDK deployment?
As a one-off ECS task with a command override, run by the pipeline before the service update.
Can the CDK build my Docker image?
Yes. ContainerImage.from_asset builds the Dockerfile and pushes it to a managed ECR repository.
CDK or Terraform?
CDK is AWS-only and synthesises CloudFormation; Terraform is multi-cloud. Both are good for Django infrastructure.