Deploy Django on AWS App Runner

Deploy a containerised Django app on AWS App Runner: build an image in ECR, configure the port and health check, connect to RDS through a VPC connector, run migrations, serve static files, and know when ECS fits better.

Problem statement

AWS App Runner runs a container or source repository as a managed web service: it builds or pulls the image, provides HTTPS, scales instances with traffic, and deploys new versions automatically. For a Django team that wants AWS without managing load balancers, ECS task definitions, and auto scaling groups, it is the shortest path. Django still needs the usual pieces handled correctly: a production image, a database in a VPC, migrations, static files, secrets, and a health check that passes.

Django on App RunnerImage in ECRGunicorn, port 8000App Runner serviceHTTPS, autoscalingVPC connectorprivate RDS accessSecrets Managerenv at runtimeApp Runner manages HTTPS and scaling; your image, database, and secrets stay your design.
  1. Build a production Django image with Gunicorn binding 0.0.0.0:8000, push it to ECR.
  2. Create an App Runner service from the ECR image, port 8000, with an instance role and an access role for ECR.
  3. Add a VPC connector so the service reaches RDS PostgreSQL in private subnets.
  4. Inject secrets from Secrets Manager or SSM Parameter Store as environment variables.
  5. Set the health check to a lightweight /health/ path that allows the App Runner host.
  6. Run migrations as a separate step (CodeBuild or an ECS one-off task), and serve static files with WhiteNoise or S3/CloudFront.

Step-by-step solution

1. Build and push the image

FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN SECRET_KEY=build DJANGO_SETTINGS_MODULE=config.settings.production python manage.py collectstatic --noinput
USER 1000
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3", "--access-logfile", "-"]
aws ecr create-repository --repository-name myapp
docker build -t myapp . && docker tag myapp:latest <acct>.dkr.ecr.<region>.amazonaws.com/myapp:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <acct>.dkr.ecr.<region>.amazonaws.com
docker push <acct>.dkr.ecr.<region>.amazonaws.com/myapp:latest

2. Create the service

In the App Runner console (or with the CLI or Terraform), create a service from the ECR image, set the port to 8000, choose CPU and memory (1 vCPU / 2 GB is a reasonable start), and enable automatic deployments on new image pushes if you want push-to-deploy. App Runner gives you an HTTPS URL such as https://abc123.us-east-1.awsapprunner.com and can attach a custom domain with a managed certificate.

Before building a new production service on App Runner, check AWS's current notes on the service's availability for new accounts; AWS has moved some services into maintenance mode in recent years. If it is not available to you, ECS on Fargate offers the same container model with more configuration.

what App Runner manages and what you designCustom domain + HTTPSmanaged by App RunnerApp Runner instancesautoscaled containers from ECRVPC connectoregress into your private subnetsRDS PostgreSQLprivate, security group allowsconnectorS3 + Secrets Managermedia files and credentialsEverything below the instances is still your responsibilitydjango-deployment.com
With a VPC connector, outbound internet access needs a NAT gateway in those subnets.

3. Connect to RDS with a VPC connector

Create a VPC connector in the App Runner networking settings, choose private subnets and a security group, and allow that security group in the RDS security group on port 5432. Outbound traffic from the service then goes through your VPC; if the app calls external APIs, those subnets need a NAT gateway.

4. Configure environment and secrets

Set plain environment variables (DJANGO_SETTINGS_MODULE, ALLOWED_HOSTS) in the service configuration, and reference secrets from Secrets Manager or SSM for SECRET_KEY and DATABASE_URL. Give the service's instance role permission to read those secrets and to access S3 for media.

ALLOWED_HOSTS must include the App Runner default domain and your custom domain. App Runner terminates TLS, so add:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_TRUSTED_ORIGINS = ["https://example.com"]

5. Health check

Configure an HTTP health check on /health/ with a short timeout. The request uses the instance's address as the host, so either allow it or exempt the health path from host validation, and exempt it from SECURE_SSL_REDIRECT.

6. Migrations and static files

App Runner has no release-phase hook. Run python manage.py migrate as a pipeline step before updating the service: a CodeBuild job in the same VPC, or a one-off ECS task using the same image. Static files are collected into the image at build time and served with WhiteNoise, or uploaded to S3 behind CloudFront for heavy traffic. Store media uploads in S3; instance filesystems are ephemeral.

a release that migrates first1Build image with collectstatictests pass in CI2Push to ECRtag with the commit SHA3Run migrate as a one-off taskCodeBuild or ECS in the VPC4Update the App Runner servicenew image tag5Health check passes, traffic shiftsautomaticBackward-compatible migrations let old instances keep serving during the switchdjango-deployment.com
Tag images by commit so a rollback is just pointing back to the previous tag.

Verification check: the service status is Running, the custom domain loads over HTTPS, /health/ returns 200 in the App Runner logs, and a test upload lands in S3.

Explanation

App Runner packages several AWS building blocks behind one service: a managed load balancer with TLS, container instances on Fargate-like infrastructure, autoscaling based on concurrent requests, and deployment automation. Django runs inside as any containerised WSGI app. The trade-off is less control than ECS: no sidecars, limited networking options, no release hooks, and fewer instance sizes. For a single web service with a database, that simplicity is often worth it; for workers, schedulers, and complex networking, ECS fits better.

Edge cases / notes

  • Celery workers cannot run as App Runner services (they serve no HTTP); run them on ECS Fargate.
  • Concurrency setting. App Runner scales on concurrent requests per instance; set it near your Gunicorn worker and thread capacity.
  • Cold starts. Minimum instances of at least 1 avoids scaling to zero for production.
  • Cost. You pay for provisioned memory even when idle and for active vCPU; compare with a small ECS service.

Next steps

FAQ

Can App Runner connect to a private RDS database?

Yes, through a VPC connector in private subnets with security group access to RDS.

How do I run migrations on App Runner?

As a separate pipeline step, such as a CodeBuild job or ECS one-off task, before updating the service.

Can I run Celery on App Runner?

Not as a worker service. Run Celery on ECS Fargate or EC2.

What port should Gunicorn use?

The port configured in the service, commonly 8000, bound to 0.0.0.0.

Keep going