AWS Elastic Beanstalk
Deploy without managing infrastructure. Environment types, deployment policies and their downtime trade-offs, .ebextensions and the database mistake.
Beanstalk takes your application code and creates everything needed to run it. EC2 instances, an Auto Scaling group, a load balancer, security groups, CloudWatch alarms, from a single command. It is a platform-as-a-service layer over ordinary AWS resources.
The crucial property, and the reason it is still worth knowing: everything Beanstalk creates is a normal AWS resource in your account. You can inspect the Auto Scaling group, adjust the security groups, and see exactly what it built. That makes it a much gentler starting point than a fully opaque platform, and it means the exit path is understood rather than a rewrite.
Deploying
eb init orders-api --platform python-3.12 --region eu-west-1
eb create prod-env --instance-types t3.small --min-instances 2 --max-instances 8
eb deploy
eb open
eb status
eb health --refresh
eb logs --all
A Procfile tells Beanstalk how to start the application, and defining it explicitly avoids relying on platform defaults that change between versions:
web: gunicorn --bind 0.0.0.0:8000 --workers 3 app:application
Environment types
eb create worker-env --tier worker
Web server environments sit behind a load balancer and serve HTTP. Worker environments have no load balancer; instead a daemon on each instance reads from an SQS queue and POSTs each message to your application on localhost.
That worker pattern is genuinely useful. Background jobs scale independently of the web tier, and your application only needs to handle an HTTP POST rather than integrate an SQS client.
A single-instance environment (--single) has no load balancer, which saves the hourly charge and is right for development. It also means no zero-downtime deployments and no redundancy, so it is not a production configuration.
Deployment policies matter
This is the setting that determines whether a deploy causes an outage, and the default is not the safe one.
| Policy | Downtime | Extra cost | Rollback |
|---|---|---|---|
| All at once | Yes | None | Redeploy |
| Rolling | No | None | Redeploy |
| Rolling with additional batch | No | Brief | Redeploy |
| Immutable | No | Double, briefly | Automatic |
| Traffic splitting | No | Double, briefly | Automatic |
All at once is the default and takes the whole environment down during the deploy. Fine for development, wrong for anything users touch.
Rolling updates instances in batches, so capacity drops while a batch is out. Rolling with additional batch launches extra instances first, keeping full capacity throughout.
Immutable launches a complete new Auto Scaling group, waits for it to pass health checks, then swaps and terminates the old one. It costs double for a few minutes and it is the safest option. A failed deploy leaves the original instances untouched and rolls back automatically. For production it is the one to choose.
aws elasticbeanstalk update-environment --environment-name prod-env \
--option-settings \
Namespace=aws:elasticbeanstalk:command,OptionName=DeploymentPolicy,Value=Immutable \
Namespace=aws:elasticbeanstalk:command,OptionName=Timeout,Value=1800
Traffic splitting is a canary: a percentage of requests go to new instances for an evaluation period, and it rolls back automatically if health degrades.
Blue-green by swapping URLs
Beanstalk's built-in blue-green is a CNAME swap between two environments:
eb create prod-env-v2 --cname orders-api-v2
eb deploy prod-env-v2
# test prod-env-v2 thoroughly
eb swap prod-env --destination-name prod-env-v2
The swap is near-instant and reversible by swapping back. A far better rollback story than redeploying. Two caveats. It is DNS, so clients holding a cached record keep hitting the old environment until the TTL expires. And a database inside the environment does not come along, which leads to the next point.
Never put the database in the environment
Beanstalk offers to create an RDS instance inside the environment. Do not accept for anything you care about.
The database then shares the environment's lifecycle: terminate the environment and the database goes with it. It also cannot be shared with another environment, which breaks blue-green entirely. The new environment has no data.
Create RDS separately and pass the endpoint in:
eb setenv DATABASE_URL="postgresql://app@orders-db.abc123.eu-west-1.rds.amazonaws.com:5432/orders"
This is the most consequential Beanstalk mistake, and it is presented as a convenient default during setup.
Configuration with .ebextensions
YAML files in .ebextensions/ customise the environment:
# .ebextensions/01-options.config
option_settings:
aws:elasticbeanstalk:application:environment:
PYTHONPATH: /var/app/current
aws:autoscaling:asg:
MinSize: 2
MaxSize: 8
aws:elasticbeanstalk:environment:
LoadBalancerType: application
aws:elbv2:loadbalancer:
IdleTimeout: 120
aws:autoscaling:trigger:
MeasureName: CPUUtilization
UpperThreshold: 70
LowerThreshold: 30
packages:
yum:
postgresql-devel: []
container_commands:
01_migrate:
command: "python manage.py migrate --noinput"
leader_only: true
leader_only: true is essential for migrations. Without it every instance runs the migration simultaneously, which at best is wasteful and at worst corrupts data.
Files are processed in alphabetical order, so prefix them with numbers. On Amazon Linux 2 and later, platform hooks in .platform/hooks/ are the newer mechanism and are preferred for scripts.
Beware that .ebextensions can create arbitrary CloudFormation resources. Powerful, and a common way for a Beanstalk environment to become something nobody understands.
Health
eb health --refresh
instance-id status cause
i-0abc123 Degraded Following services are not running: web
i-0def456 Ok -
Enhanced health monitoring reads application response codes and latency rather than just EC2 status checks, and it is what makes Degraded meaningful. Enable it.
An environment that goes Severe immediately after a deploy is usually the application failing to bind to the expected port. Beanstalk's nginx proxies to port 8000 on most platforms, and an app listening on 3000 or on 127.0.0.1 fails health checks while appearing to run.
When to move on
Beanstalk suits a single application, a small team, and conventional web or worker workloads. It is the fastest route from code to a production-shaped deployment on AWS.
Move on when you need several services deployed independently (ECS or EKS), fine-grained infrastructure control (CDK or Terraform), or when you find yourself writing enough .ebextensions to constitute an infrastructure codebase, at which point the abstraction is costing more than it saves.
The exit is genuinely manageable, because the underlying resources were always ordinary AWS resources. That is a real argument for starting here rather than avoiding it.
Common errors
ERROR: Your requested instance type (t3.small) is not supported in your
requested Availability Zone
Constrain the environment to zones that offer the type.
Environment health has transitioned from Ok to Severe. 100.0 % of the
requests are failing with HTTP 5xx.
The application is not serving on the expected port, or crashed on start. eb logs --all and check the bind address.
Service:AmazonCloudFormation, Message:Stack named 'awseb-e-xyz-stack'
aborted operation
Read the CloudFormation events for the real cause; Beanstalk's message is a wrapper.
Deploy succeeds, old code still served. The version was not deployed to that environment, or a cached artefact. eb appversion lists what exists.
Quick reference
| Task | Command |
|---|---|
| Initialise | eb init <app> --platform <p> |
| Create | eb create <env> --instance-types t3.small |
| Deploy | eb deploy |
| Health | eb health --refresh |
| Logs | eb logs --all |
| Set env vars | eb setenv KEY=value |
| SSH | eb ssh |
| Blue-green swap | eb swap <env> --destination-name <other> |
| List versions | eb appversion |
| Terminate | eb terminate <env> |
Key takeaways
- Everything Beanstalk creates is a normal AWS resource, which makes the exit path manageable.
- Change the deployment policy. "All at once" is the default and causes downtime.
- Immutable deployments for production: automatic rollback, double cost for a few minutes.
- Never let Beanstalk create your database. It dies with the environment and blocks blue-green.
leader_only: trueon migration commands, or every instance runs them at once.eb swapis a CNAME swap, so cached DNS keeps some clients on the old environment.- Enable enhanced health monitoring, or health reflects EC2 status rather than your app.
- A
Severeenvironment after deploy is usually the wrong port or a localhost bind.
Check your understanding
A blue-green swap with no data
A team creates a second environment for blue-green, deploys to it, and finds it has an empty database. The original environment's RDS instance was created through Beanstalk during setup. What went wrong?
A deploy that took the site down
A production Beanstalk deploy caused about four minutes of complete unavailability. The environment has four instances behind a load balancer and default settings. What should change?
Frequently Asked Questions
What is Elastic Beanstalk and how does it differ from ECS or Lambda?
Beanstalk is a platform-as-a-service layer that provisions EC2 instances, an Auto Scaling group, a load balancer and monitoring from your application code, so you deploy code rather than infrastructure. ECS runs containers you have built and gives you far more control over networking, task placement and scaling. Lambda removes servers entirely and charges per invocation. Beanstalk sits between "just run my app" and "I want to configure everything", and unlike a closed platform, everything it creates is a standard AWS resource you can inspect and eventually take over.
Which Elastic Beanstalk deployment policy should I use?
Immutable for production. It launches an entirely new Auto Scaling group, waits for health checks to pass, then swaps traffic and terminates the old instances, so a failed deploy leaves the originals serving and rolls back automatically, at the cost of running double capacity for a few minutes. "All at once" is the default and takes the whole environment down, which is only acceptable in development. "Rolling with additional batch" is the middle ground, preserving full capacity without doubling it.
Should I let Beanstalk create my RDS database?
No, for anything beyond a throwaway environment. A database created inside the environment shares its lifecycle, so terminating the environment deletes it, and it cannot be shared with a second environment, which makes blue-green deployments impossible, since the new environment comes up with no data. Create RDS separately and pass the connection string in with eb setenv. Migrating away later means a snapshot, a restore outside Beanstalk and a repoint, so it is much easier to get right initially.
How does blue-green deployment work in Elastic Beanstalk?
You run two environments and exchange their CNAMEs with eb swap. Deploy to the idle environment, test it properly on its own URL, then swap, which is near-instant and reversible by swapping back, giving a far better rollback than redeploying. Two things to understand: it is DNS, so clients holding a cached record continue hitting the old environment until the TTL expires, and the database must live outside both environments or the new one has nothing to serve.
What are .ebextensions and when should I use them?
YAML configuration files in an .ebextensions/ directory that customise the environment. Option settings, packages to install, files to write, and commands to run during deployment. The one setting worth memorising is leader_only: true on container_commands that run migrations, since without it every instance runs them simultaneously. On Amazon Linux 2 and later, .platform/hooks/ is the newer mechanism and is preferred for scripts. Be wary of growing them into an infrastructure codebase, which is usually the signal to move to CDK or Terraform.
Why does my environment show Severe right after deploying?
Most often the application is not listening where Beanstalk expects. The platform's nginx proxies to a specific port, 8000 on most, 5000 on some, so an app bound to 3000, or bound to 127.0.0.1 rather than 0.0.0.0, fails every health check while the process itself is running fine. Run eb logs --all and check the bind address and port first. The other frequent cause is a crash during startup from a missing environment variable, which the application log will show directly.