AWS Elastic Beanstalk (EB) is the fastest managed path to get a Django app running in production on AWS: you hand it your code, and it provisions and operates the EC2 instances, an Application Load Balancer, an Auto Scaling group, and health monitoring for you. You stay in control of the application; AWS handles the undifferentiated infrastructure plumbing. In 2026 the workflow is the EB CLI (eb init, eb create, eb deploy, eb open) on a Python on Amazon Linux 2023 platform branch.
This guide is a modern, end-to-end walkthrough: choosing the right platform, preparing a Django 4.x/5.x project, deploying with the EB CLI, serving static files, running migrations, attaching a database, handling secrets safely, and hardening for production. It also shows where Elastic Beanstalk stops being the right tool — if you want full infrastructure-as-code control instead, see our companion guide, Deploying Django with a CloudFormation template.
What is AWS Elastic Beanstalk?
Elastic Beanstalk is a Platform-as-a-Service (PaaS). You upload an application bundle (or point it at your source), pick a platform such as Python, and EB orchestrates the underlying AWS resources to run it:
- EC2 instances running your Django app behind gunicorn.
- An Application Load Balancer (ALB) distributing traffic and terminating HTTPS.
- An Auto Scaling group that adds or removes instances based on load.
- Health monitoring, log streaming, rolling/immutable deployments, and easy rollbacks.
Crucially, EB is not a separate billable abstraction layer — there is no extra charge for Elastic Beanstalk itself; you pay only for the AWS resources it creates (EC2, ALB, RDS, etc.). Under the hood EB generates a CloudFormation stack, so everything it builds is visible and inspectable in the AWS console. That makes it a sensible default for small-to-medium Django apps where you want production-grade infrastructure without writing it yourself.
Prerequisites
Before you deploy, make sure you have:
- A working Django 4.x or 5.x project (Python 3.10+ — Python 2 is long dead and unsupported on every current EB platform).
- An AWS account and the AWS CLI configured with credentials (
aws configure). - The EB CLI installed. The recommended installer is the official script, or simply pip:
A quick note on credentials: configure the AWS CLI/EB CLI with an IAM user or SSO profile that has only the permissions it needs — never your root account. And inside the deployed app, never hardcode AWS access keys. EC2 instances launched by EB receive an instance profile (IAM role), so the AWS SDK (boto3) picks up temporary credentials automatically. If you are new to IAM, see our AWS IAM roles and policies guide.
# Install the EB CLI (pip is the simplest cross-platform method)
pip install awsebcli --upgrade
# Verify
eb --version # e.g. EB CLI 3.x
# Make sure the AWS CLI itself is configured (used for credentials/region)
aws configureChoose the right platform in 2026
This is the single biggest change since older tutorials. The legacy "Python 3.x running on 64bit Amazon Linux" (Amazon Linux AMI / "AL1") platform is RETIRED — you cannot create new environments on it. The current generation is Python on Amazon Linux 2023 (AL2023) (with Amazon Linux 2 still around but also winding down). Always create new environments on the AL2023 Python platform branch.
The platform generation also changes how you configure things:
- Old AL1 platforms leaned heavily on
.ebextensions/*.configwithcontainer_commandsand aWSGIPathoption. - AL2 / AL2023 platforms use a
Procfileto declare how to start your app (your own gunicorn process) and.platform/hooks (shell scripts) for lifecycle steps, while.ebextensionsstill works for option settings. On AL2023, EB runs nginx as the reverse proxy in front of your gunicorn process.
List the current platform branches so you pin the right one:
# See available Python platform branches (pick the latest AL2023 one)
eb platform list | grep -i python
# Example current branch name (yours may differ by version):
# Python 3.12 running on 64bit Amazon Linux 2023Prepare your Django project
A few changes make a Django project deploy cleanly on EB.
1. Pin dependencies in requirements.txt — EB installs from it automatically. Include gunicorn (the production WSGI server) and whitenoise (the simplest way to serve static files):
2. Production settings — read configuration from environment variables, turn off DEBUG, set ALLOWED_HOSTS, and wire up WhiteNoise so the app serves its own static files without extra EB proxy config.
# requirements.txt
Django>=5.0,<5.2
gunicorn>=22.0
whitenoise>=6.6
psycopg[binary]>=3.1 # PostgreSQL driver for RDS
boto3>=1.34 # only if your app calls AWS APIs# settings.py (production-relevant excerpts)
import os
# Never hardcode secrets. Read them from the environment (eb setenv / SSM).
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"
# EB gives the environment a *.elasticbeanstalk.com host; add your real domain too.
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # right after SecurityMiddleware
# ... the rest of your middleware ...
]
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
# Behind the ALB, trust the proxy's HTTPS header so Django builds correct URLs.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Database: read DATABASE_URL or individual RDS_* vars EB injects (see RDS section).Deploy with the EB CLI
With the project ready, deploying is four commands. Run them from your project root (the directory containing manage.py).
eb init— one-time setup: pick a region, an application name, and the AL2023 Python platform. It writes.elasticbeanstalk/config.yml.eb create— provisions a new environment (EC2, ALB, Auto Scaling, security groups) and does the first deploy.eb deploy— ships subsequent code changes to the existing environment.eb open— opens the live URL in your browser.
# 1. One-time init: choose region + the AL2023 Python platform
eb init -p "Python 3.12 running on 64bit Amazon Linux 2023" my-django-app --region us-east-1
# 2. Create a load-balanced environment and run the first deploy
eb create my-django-env --elb-type application
# 3. Deploy later changes
eb deploy
# 4. See it live
eb open
# Useful day-to-day
eb status # health + environment info
eb logs # stream/download logs
eb ssh # shell into an instance (debugging)Tell EB how to run the app: Procfile and config
On AL2023, declare how to start gunicorn with a Procfile at the project root. This replaces the old WSGIPath option setting:
# Procfile (no extension; project root)
web: gunicorn --bind 127.0.0.1:8000 --workers 3 myproject.wsgi:applicationFor environment-wide option settings (instance type, environment variables, the static-files proxy mapping, etc.), add YAML files under .ebextensions/. Keep the directory at the project root so it ships in the bundle:
# .ebextensions/01_options.config
option_settings:
aws:elasticbeanstalk:application:environment:
PYTHONPATH: "/var/app/current"
DJANGO_SETTINGS_MODULE: "myproject.settings"
# If you prefer EB's proxy to serve static files instead of WhiteNoise:
aws:elasticbeanstalk:environment:proxy:staticfiles:
/static: staticfilesFor lifecycle steps that need to run on each instance (or once per deploy), AL2023 supports .platform/ hooks — executable shell scripts. Use these instead of the legacy container_commands where you can; they are more predictable on AL2/AL2023.
Run database migrations on deploy
You want collectstatic and migrate to run during deployment — but migrate must run only once, not on every instance. EB's container_commands support a leader_only: true flag for exactly this: the command runs on a single "leader" instance per deploy.
# .ebextensions/02_django.config
container_commands:
01_collectstatic:
command: "source /var/app/venv/*/bin/activate && python manage.py collectstatic --noinput"
02_migrate:
command: "source /var/app/venv/*/bin/activate && python manage.py migrate --noinput"
leader_only: true # run migrations on ONE instance onlyThe equivalent on AL2023 can also be done with a .platform/hooks/postdeploy/ script (postdeploy hooks run after the app is deployed and health checks would consider it). For most Django apps the leader_only container_command above is the simplest reliable pattern. Note that leader_only is honored on rolling deploys; for safety with destructive migrations, prefer backward-compatible schema changes and a separate, deliberate migration step.
Serve static files
You have two solid options on EB:
- WhiteNoise (recommended for simplicity). Add the middleware and
CompressedManifestStaticFilesStorage(shown above), runcollectstatic, and Django serves compressed, far-future-cached static assets itself. No EB proxy config, works identically in local/dev/prod, and is plenty fast for typical apps because the assets are cached at the edge if you front the ALB with CloudFront. - The EB static-files proxy via the
aws:elasticbeanstalk:environment:proxy:staticfilesnamespace (shown in.ebextensions/01_options.config). This makes nginx serve/staticdirectly from yourstaticfilesdirectory, bypassing gunicorn.
For most projects, start with WhiteNoise — it is one line of middleware and removes a whole class of "static files 404 in production" issues. For very high static traffic, put CloudFront in front and/or offload to S3.
Attach a PostgreSQL database (RDS)
Django needs a real database in production. You can add an RDS instance from the EB console ("Configuration -> Database"), but there is an important caveat:
An RDS instance created inside the EB environment is tied to that environment's lifecycle — terminate the environment and the database (and its data) is destroyed with it.
For anything beyond a throwaway demo, provision a standalone RDS instance separately (its own CloudFormation/Terraform stack or the RDS console) and connect Django to it via environment variables. That way you can rebuild, blue/green, or delete the EB environment without risking your data.
When EB does manage the database, it injects RDS_HOSTNAME, RDS_PORT, RDS_DB_NAME, RDS_USERNAME, and RDS_PASSWORD into the environment. Read whichever variables apply in settings.py:
# settings.py — database config from environment
import os
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("RDS_DB_NAME", os.environ.get("DB_NAME")),
"USER": os.environ.get("RDS_USERNAME", os.environ.get("DB_USER")),
"PASSWORD": os.environ.get("RDS_PASSWORD", os.environ.get("DB_PASSWORD")),
"HOST": os.environ.get("RDS_HOSTNAME", os.environ.get("DB_HOST")),
"PORT": os.environ.get("RDS_PORT", "5432"),
"CONN_MAX_AGE": 60,
}
}Make sure the RDS security group allows inbound PostgreSQL (5432) from the EB environment's instance security group, and keep the database in private subnets with no public access.
Environment variables and secrets
Configuration belongs in the environment, not in your repo. Set non-secret variables with the EB CLI:
# Set environment variables on the running environment
eb setenv DJANGO_DEBUG=False \
DJANGO_ALLOWED_HOSTS=.elasticbeanstalk.com,yourdomain.com \
DJANGO_SETTINGS_MODULE=myproject.settingsFor secrets (the Django SECRET_KEY, database passwords, third-party API keys), prefer AWS Systems Manager Parameter Store (SecureString) or AWS Secrets Manager over plain eb setenv — values there are encrypted, audited, and rotatable, and they never appear in your environment configuration or git history. Grant the EB instance role read access to the specific parameters, and fetch them at startup (or via a .platform prebuild hook) with boto3. The golden rule stays the same: the app authenticates to AWS through its instance role — never with hardcoded keys.
Production hardening checklist
Before you call it production-ready:
DEBUG = Falseand a correctALLOWED_HOSTS— non-negotiable.- HTTPS everywhere. Request a free certificate from AWS Certificate Manager (ACM), attach it to the ALB's HTTPS (443) listener, and redirect HTTP to HTTPS. Set
SECURE_PROXY_SSL_HEADER(shown earlier) so Django knows requests are secure behind the proxy. - Health checks. Point the ALB health check at a lightweight URL (e.g. a
/healthz/view) and use Enhanced Health reporting so EB reacts to real app health, not just instance status. - Safe deploys. Use rolling or immutable deployment policies (immutable spins up a fresh set of instances and swaps, giving zero-downtime and easy rollback) instead of all-at-once for production.
- Secrets via SSM/Secrets Manager, credentials via the instance role — never committed.
- Logs and metrics. Stream logs to CloudWatch and watch ALB/Target Group metrics.
- Autoscaling. Tune the Auto Scaling group's min/max and scaling triggers; see autoscaling with Auto Scaling groups and a load balancer.
Elastic Beanstalk vs other ways to deploy Django
Elastic Beanstalk is the fastest managed path, but it is not the only one. Here is how it compares to the main alternatives in 2026:
| Option | Managed-ness | Control / flexibility | Best for |
|---|---|---|---|
| Elastic Beanstalk | High (PaaS) | Medium — EB owns the infra patterns | Teams that want production infra fast without writing IaC; classic web apps |
| EC2 + CloudFormation / CDK | Low (DIY) | Highest — every resource is yours to define | Bespoke architectures, strict compliance, full Infrastructure-as-Code |
| ECS / Fargate | Medium–High | High — container-native, no servers to patch (Fargate) | Dockerized apps, microservices, teams already on containers |
| App Runner | Highest | Low — least to configure | Simple containerized/web services that want the least ops of all |
A useful way to read this table: Elastic Beanstalk and App Runner optimize for speed and low ops, while CloudFormation/CDK optimizes for control and ECS/Fargate optimizes for container-native scale. If your priority is full infrastructure-as-code — version-controlled, repeatable, reviewable infrastructure — go the CloudFormation route instead; we cover it step by step in Deploying Django with a CloudFormation template. If you have already containerized with Docker, ECS/Fargate or App Runner are often the better long-term home.
How MicroPyramid helps
MicroPyramid has shipped and operated Django applications on AWS for 12+ years across 50+ projects for startups and enterprises. Whether you want the fastest route live on Elastic Beanstalk, a hardened Infrastructure-as-Code setup, or a move to containers, our AWS consulting services and Django development services cover architecture, deployment automation, and production support. Planning a move onto AWS or between accounts? Our cloud migration services build security, cost control, and zero-downtime deploys in from day one.
Frequently Asked Questions
Elastic Beanstalk vs CloudFormation for Django — which should I use?
Use Elastic Beanstalk when you want a managed, opinionated path to production with the least setup: it provisions and operates EC2, the load balancer, autoscaling and health for you. Use CloudFormation (or CDK/Terraform) when you need full Infrastructure-as-Code control — every resource defined, version-controlled and reviewable — for bespoke or compliance-heavy architectures. They are not mutually exclusive: EB itself generates a CloudFormation stack under the hood. See our companion guide, Deploying Django with a CloudFormation template.
Which EB platform should I pick in 2026?
Choose the Python on Amazon Linux 2023 (AL2023) platform branch. The old "Python running on 64bit Amazon Linux" (AL1/AMI) platform is retired and cannot host new environments, and Amazon Linux 2 is winding down. Run eb platform list | grep -i python to see the exact current branch name and pin it in eb init.
How do I run Django migrations on Elastic Beanstalk?
Add a container_commands entry in .ebextensions that activates the app virtualenv and runs python manage.py migrate --noinput with leader_only: true, so it executes on a single instance per deploy rather than every instance. On AL2023 you can alternatively use a .platform/hooks/postdeploy/ script. For destructive schema changes, prefer backward-compatible migrations and a deliberate, separate migration step.
How do I serve static files on Elastic Beanstalk?
The simplest approach is WhiteNoise: add WhiteNoiseMiddleware, set CompressedManifestStaticFilesStorage, run collectstatic, and Django serves compressed, cache-friendly assets itself. Alternatively, map the EB static-files proxy with the aws:elasticbeanstalk:environment:proxy:staticfiles option so nginx serves them. For high traffic, front the app with CloudFront and/or offload assets to S3.
Should I attach RDS to my EB environment?
Only for throwaway demos. An RDS instance created inside the EB environment is destroyed when that environment is terminated — taking your data with it. For anything real, provision a standalone RDS instance separately and connect Django to it via environment variables, so you can rebuild or replace the EB environment without losing the database.
Is Elastic Beanstalk still recommended in 2026?
Yes — Elastic Beanstalk is still actively supported and is a perfectly good, low-effort choice for straightforward Django web apps, especially for small teams that want production infrastructure without managing it. That said, many teams now reach for ECS/Fargate (if they have containerized) or App Runner (for the least ops), and for full control, CloudFormation/CDK. Pick EB when speed-to-production and low operational overhead matter most.