AWS Elastic Beanstalk: Zero-Config PaaS for Web Applications (2026)

AWS Elastic Beanstalk — PaaS Deployment Guide

AWS Elastic Beanstalk is the forgotten gem of AWS's compute portfolio. While everyone debates ECS vs EKS, Beanstalk quietly deploys production Spring Boot APIs, Python Flask apps, and Node.js services for thousands of teams — with zero infrastructure code required. You upload a JAR, Beanstalk provisions the load balancer, Auto Scaling group, EC2 instances, and CloudWatch alarms. You get a fully managed PaaS on top of familiar AWS infrastructure, which means you can always reach through Beanstalk to the underlying resources when you need to. This guide covers everything: EB CLI workflows, the powerful .ebextensions configuration system, every deployment policy explained with real trade-offs, RDS integration patterns, Secrets Manager, and a complete CI/CD pipeline with CodePipeline.

Beanstalk vs ECS vs EC2 vs App Runner — Decision Guide

AWS offers four primary ways to run web applications, and choosing the wrong abstraction level costs you weeks of rework. The decision comes down to how much infrastructure control you need versus how much operational overhead you can accept.

FactorElastic BeanstalkECS FargateEC2 DirectApp Runner
Infrastructure managementAutomatic (but inspectable)Control plane managed, you define tasksFull manual controlFully managed, zero config
Deployment artifactZIP/JAR/WAR or DockerDocker image onlyAMI / user-data scriptsDocker image or source repo
Custom OS-level configYes, via .ebextensionsVia custom Docker imageFull accessNo
Built-in load balancerYes (ALB/NLB/Classic)Yes (ALB/NLB)Manual setupYes (built-in)
Auto ScalingYes, ASG-basedYes, ECS auto scalingManual or ASGAutomatic, request-based
Worker tier supportYes (SQS-driven worker tier)Via ECS tasksManualNo
Cost modelPay for EC2/ALB onlyPay per vCPU/memory secondPay for EC2/ALBHigher per-request, simpler billing
CI/CD integrationExcellent (CodePipeline native)GoodManualGitHub/ECR auto-deploy
Best forExisting JAR/WAR apps, .NET, teams new to AWSMicroservices, mixed workloads, containersLegacy lift-and-shift, max controlSimple APIs, startups, no DevOps team
When Elastic Beanstalk wins: You have a Spring Boot JAR, a Django app, or a Node.js server and you want it running on production-grade AWS infrastructure in under 30 minutes. You need .ebextensions to customize Nginx, install OS packages, or run migrations. You want CodePipeline to deploy on every git push. You are not running containers (or are running a single-container Docker app). Beanstalk's killer feature is that it manages AWS resource orchestration while letting you SSH into the instances and run eb ssh when things go wrong — unlike App Runner's black box.

Beanstalk is not the right choice when you have dozens of containerized microservices (use ECS/EKS), when you need fine-grained Kubernetes features like custom schedulers or service meshes (use EKS), or when you need a truly serverless auto-scaling-to-zero model (use Lambda or App Runner).

Supported Platforms and Runtimes

Elastic Beanstalk supports a wide range of managed platform branches. Each platform is versioned independently and receives AWS-managed security patches. As of 2026, the supported platform branches include:

PlatformSolution Stack ExampleNotes
Java (Corretto 21)64bit Amazon Linux 2023/... running Corretto 21Runs JAR directly; Tomcat variant for WAR files
Java (Tomcat 10)64bit Amazon Linux 2023/... running Tomcat 10 Corretto 21Legacy WAR deployments
Python 3.1164bit Amazon Linux 2023/... running Python 3.11WSGI via Gunicorn (default) or uWSGI
Node.js 2064bit Amazon Linux 2023/... running Node.js 20npm start or Procfile
Docker64bit Amazon Linux 2023/... running DockerSingle container; use ECS for multi-container
.NET Core on Linux64bit Amazon Linux 2023/... running .NET 8Deploy .zip of published .NET app
Go 1.2164bit Amazon Linux 2023/... running Go 1.21Procfile-driven
Ruby 3.264bit Amazon Linux 2023/... running Ruby 3.2Puma or Passenger
PHP 8.264bit Amazon Linux 2023/... running PHP 8.2Apache httpd
Important — AL2 vs AL2023: Always prefer Amazon Linux 2023 platform branches for new environments. Amazon Linux 2 branches are in long-term support. AL2023 uses dnf instead of yum — update your .ebextensions package manager references accordingly.

You can list current solution stacks with the AWS CLI:

aws elasticbeanstalk list-available-solution-stacks \
  --query "SolutionStacks[?contains(@,'Corretto 21')]" \
  --output text

EB CLI: Init, Create, Deploy

The EB CLI is the primary tool for Elastic Beanstalk workflows. Install it with pip and use it from your project root. It creates a .elasticbeanstalk/config.yml file that tracks your application and environment settings.

# Install EB CLI
pip install awsebcli --upgrade --user

# Verify
eb --version
# EB CLI 3.21.x (Python 3.x)

# Initialise in your project root
cd my-spring-boot-app
eb init

# The CLI prompts:
# Select a default region: us-east-1
# Application name: my-spring-boot-app
# Select a platform: Java
# Select a platform branch: Corretto 21 running on 64bit Amazon Linux 2023
# Do you want to set up SSH? Yes
# Select a keypair or create new

After eb init, your .elasticbeanstalk/config.yml looks like this:

branch-defaults:
  main:
    environment: my-app-prod
    group_suffix: null
global:
  application_name: my-spring-boot-app
  branch: null
  default_ec2_keyname: my-keypair
  default_platform: Corretto 21 running on 64bit Amazon Linux 2023
  default_region: us-east-1
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: null
  repository: null
  sc: git
  workspace_type: Application

Now create your first environment. A Beanstalk environment is the combination of EC2 instances, a load balancer, Auto Scaling group, and all related resources.

# Create a Web Server environment (load balanced)
eb create my-app-prod \
  --tier webserver \
  --elb-type application \
  --instance-type t3.small \
  --min-instances 2 \
  --max-instances 6 \
  --envvars SPRING_PROFILES_ACTIVE=prod,DB_HOST=prod-db.cluster.us-east-1.rds.amazonaws.com

# This takes ~5 minutes. Beanstalk creates:
# - An ALB with target group
# - An Auto Scaling group (min 2, max 6)
# - EC2 instances running Corretto 21
# - Security groups for ALB and instances
# - CloudWatch alarms for CPU/network

# Create a Worker tier environment (SQS-driven background processing)
eb create my-app-worker \
  --tier worker \
  --instance-type t3.small

Deploying your Spring Boot application is a single command. Beanstalk uploads your artifact to S3, distributes it to instances, and restarts the application server.

# Build the Spring Boot JAR
./mvnw clean package -DskipTests

# Deploy
eb deploy my-app-prod

# Watch the event stream
eb events -f my-app-prod

# Check environment status
eb status my-app-prod

# Open in browser
eb open my-app-prod

# SSH into a running instance
eb ssh my-app-prod

# View live logs
eb logs my-app-prod
Tip — Deploying specific artifacts: By default, eb deploy packages your git working tree. To deploy a specific JAR, use eb deploy --label v1.2.3 --message "Release 1.2.3" combined with a .ebignore file that excludes source files and only packages target/myapp.jar.

Customizing Environments with .ebextensions

The .ebextensions directory is Beanstalk's secret superpower. Place YAML configuration files inside .ebextensions/ in your deployment ZIP, and Beanstalk's cfn-init processes them in alphabetical order on every EC2 instance during launch and deployment. You can install OS packages, write files, run shell commands, and set environment variables — without touching the underlying CloudFormation templates.

The five main keys in an .ebextensions config file are packages, files, commands, container_commands, and option_settings. The critical difference between commands and container_commands: commands run before the application is deployed; container_commands run after the new application version is staged but before it goes live — perfect for database migrations.

# .ebextensions/01-packages.config
packages:
  dnf:                          # Amazon Linux 2023 uses dnf
    git: []
    jq: []
    htop: []
  rpm:
    newrelic-infra: https://download.newrelic.com/infrastructure_agent/linux/rpm/x86_64/newrelic-infra.x86_64.rpm
# .ebextensions/02-nginx.config
# Override the default Nginx proxy config for Spring Boot
files:
  "/etc/nginx/conf.d/proxy.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      upstream springboot {
        server 127.0.0.1:8080;
        keepalive 32;
      }
      server {
        listen 80;
        gzip on;
        gzip_types text/plain application/json application/javascript text/css;
        gzip_min_length 1024;

        location / {
          proxy_pass http://springboot;
          proxy_http_version 1.1;
          proxy_set_header Connection "";
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_read_timeout 300s;
          proxy_connect_timeout 30s;
          client_max_body_size 100m;
        }

        location /health {
          proxy_pass http://springboot/actuator/health;
        }
      }

commands:
  01-reload-nginx:
    command: "nginx -s reload || true"
    ignoreErrors: true
# .ebextensions/03-env.config
# Set JVM options via the environment
option_settings:
  aws:elasticbeanstalk:application:environment:
    SERVER_PORT: "8080"
    JAVA_TOOL_OPTIONS: "-Xms512m -Xmx1024m -XX:+UseG1GC -Djava.net.preferIPv4Stack=true"
  aws:elasticbeanstalk:environment:proxy:
    ProxyServer: nginx
  aws:autoscaling:asg:
    MinSize: "2"
    MaxSize: "6"
  aws:autoscaling:trigger:
    MeasureName: CPUUtilization
    Threshold: "70"
    LowerThreshold: "30"
    Period: "5"
    EvaluationPeriods: "2"
    Unit: Percent
# .ebextensions/04-migrations.config
# Run Flyway migrations before the new version goes live
container_commands:
  01-run-migrations:
    command: |
      java -jar /var/app/staging/target/myapp.jar \
        --spring.profiles.active=migrate \
        --spring.main.web-application-type=none
    leader_only: true    # Only run on one instance in the ASG
# .ebextensions/05-cron.config
# Add a cron job to the ec2-user crontab
files:
  "/etc/cron.d/cleanup":
    mode: "000644"
    owner: root
    group: root
    content: |
      # Purge temp files every hour
      0 * * * * ec2-user find /tmp -name "upload_*" -mmin +60 -delete

commands:
  01-restart-crond:
    command: "systemctl restart crond"
AL2023 migration note: If your existing .ebextensions use yum: under packages:, change it to dnf: for Amazon Linux 2023 platform branches. The syntax is otherwise identical.

Deployment Policies: All at Once to Blue/Green

Beanstalk's deployment policies control how a new application version is distributed across the running instances. Choosing the right policy balances deployment speed against availability risk — a critical decision for production environments.

PolicyDowntime?Extra capacity?Rollback speedBest for
All at onceYes (brief)NoAnother full deploymentDev/test, non-critical
RollingNo (reduced capacity)NoAnother rolling deploymentNon-peak production
Rolling with additional batchNo (full capacity)Yes (1 batch)Another rolling deploymentProduction, cost-sensitive
ImmutableNo (full capacity)Yes (doubles fleet)Terminate new ASG (fast)Production, zero-risk deploys
Blue/Green (swap URL)NoYes (full new env)Swap URL back (instant)Critical production, major releases

All at Once deploys the new version to every instance simultaneously. Every instance is taken out of service, updated, and brought back. This causes a brief outage equal to your application startup time. For a Spring Boot app this is typically 20-40 seconds. Only use this for development environments.

Rolling deploys to one batch of instances at a time (default batch size: 30%). During deployment, you are running at reduced capacity. For a 6-instance fleet with 30% batch size, Beanstalk updates 2 instances at a time, so you always have at least 4 healthy instances. The trade-off is that both old and new versions serve traffic simultaneously during the rollout.

Rolling with additional batch launches one extra batch of new instances before starting the rolling update. This maintains 100% of your normal capacity throughout the deployment. The extra instances are terminated after the deployment completes. This is the recommended policy for most production web tiers.

Immutable launches a completely new Auto Scaling group with the new version. Once all new instances pass health checks, Beanstalk moves them to the original ASG and terminates the old instances. This doubles your fleet cost during deployment but provides the fastest, safest rollback — just terminate the new ASG. Combined with Beanstalk's health checks, immutable deployments ensure traffic never switches to the new version unless it is fully healthy.

Blue/Green is not a single-environment policy but an environment-swap strategy. You maintain two complete environments (blue = current, green = new), deploy to green, run smoke tests, then call eb swap to atomically exchange the CNAME DNS records. Rollback is a second swap call — instant at the DNS level.

# Set deployment policy via EB CLI
eb deploy my-app-prod --staged

# Or configure in .ebextensions:
# .ebextensions/deployment.config
option_settings:
  aws:elasticbeanstalk:command:
    DeploymentPolicy: RollingWithAdditionalBatch
    BatchSizeType: Fixed
    BatchSize: "2"
    Timeout: "600"
    IgnoreHealthCheck: "false"
  aws:elasticbeanstalk:healthreporting:system:
    SystemType: enhanced

# Blue/Green: create green env, deploy, swap
eb create my-app-green \
  --cname my-app-green \
  --template my-app-prod

eb deploy my-app-green

# Run smoke tests against green URL...

# Swap CNAMEs (atomic, no DNS propagation needed — Beanstalk manages Route 53)
eb swap my-app-prod --destination_name my-app-green

# Rollback if needed
eb swap my-app-prod --destination_name my-app-green
Health check during rolling deployments: Beanstalk considers an instance healthy when it returns HTTP 200 from the health check URL (default: /). For Spring Boot apps, configure the health check path to /actuator/health and ensure it returns fast (<3 seconds). A slow health endpoint will stall rolling deployments.

Environment Variables and Secrets Manager Integration

Beanstalk environment variables are injected as OS environment variables into every instance. You can set them via the console, EB CLI, or .ebextensions. Never commit secrets to .ebextensions files — use AWS Secrets Manager or SSM Parameter Store instead.

# Set environment variables via EB CLI
eb setenv \
  SPRING_PROFILES_ACTIVE=prod \
  DB_PASSWORD=CHANGE_ME \
  REDIS_HOST=my-cache.abc.cfg.use1.cache.amazonaws.com

# List current environment variables
eb printenv my-app-prod

# Unset a variable
eb setenv MY_VAR= my-app-prod

For secrets, the recommended pattern is to store values in AWS Secrets Manager and retrieve them in a startup hook or directly in your Spring Boot app using the AWS SDK or the Spring Cloud AWS Secrets Manager starter.

# .ebextensions/06-secrets.config
# Retrieve a secret from SSM Parameter Store at instance startup
# and export it as an environment variable for the app
files:
  "/opt/elasticbeanstalk/hooks/appdeploy/pre/00-fetch-secrets.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash
      export AWS_DEFAULT_REGION=us-east-1

      DB_PASS=$(aws ssm get-parameter \
        --name "/myapp/prod/db_password" \
        --with-decryption \
        --query "Parameter.Value" \
        --output text)

      # Inject into the EB environment
      /opt/elasticbeanstalk/bin/get-config environment \
        | jq --arg p "$DB_PASS" '. + {DB_PASSWORD: $p}' > /tmp/env.json

      # Write to a sourced file
      echo "export DB_PASSWORD=\"$DB_PASS\"" >> /etc/profile.d/app-secrets.sh
      chmod 600 /etc/profile.d/app-secrets.sh

For Spring Boot, the cleanest approach is Spring Cloud AWS Secrets Manager, which auto-populates @Value annotations from Secrets Manager at application startup:

# src/main/resources/application-prod.properties
spring.config.import=aws-secretsmanager:/myapp/prod/db-credentials
# Beanstalk's instance IAM role must have secretsmanager:GetSecretValue

# src/main/resources/application.properties
spring.datasource.url=jdbc:postgresql://${DB_HOST}:5432/${DB_NAME}
spring.datasource.username=${db.username}   # injected from Secrets Manager JSON key "username"
spring.datasource.password=${db.password}   # injected from Secrets Manager JSON key "password"
<!-- pom.xml — Spring Cloud AWS Secrets Manager starter -->
<dependency>
  <groupId>io.awspring.cloud</groupId>
  <artifactId>spring-cloud-aws-starter-secrets-manager</artifactId>
  <version>3.1.1</version>
</dependency>
IAM permissions for Secrets Manager: Beanstalk instances use the aws-elasticbeanstalk-ec2-role IAM instance profile. Add an inline policy granting secretsmanager:GetSecretValue on the specific secret ARNs your application needs. Never grant secretsmanager:* on *.

RDS Integration: Coupled vs Decoupled Database

Elastic Beanstalk can provision an RDS instance as part of your environment (coupled) or you can connect to a separately-managed RDS instance (decoupled). The difference has major production implications.

Coupled RDS (configured in the Beanstalk console under Database settings) creates an RDS instance whose lifecycle is tied to the environment. If you delete or rebuild the environment, the RDS instance is deleted with it. Beanstalk automatically injects the connection details as environment variables: RDS_HOSTNAME, RDS_PORT, RDS_DB_NAME, RDS_USERNAME, RDS_PASSWORD. This is convenient for development but dangerous in production.

Decoupled RDS (strongly recommended for production) means your RDS cluster exists independently of Beanstalk. You create it with Terraform or the AWS Console, then pass the connection details as Beanstalk environment variables or via Secrets Manager. Terminating your Beanstalk environment has no effect on the database.

# Terraform: decoupled RDS Aurora Serverless v2 for production
resource "aws_rds_cluster" "app" {
  cluster_identifier      = "myapp-prod"
  engine                  = "aurora-postgresql"
  engine_version          = "15.4"
  engine_mode             = "provisioned"
  database_name           = "myapp"
  master_username         = "appuser"
  manage_master_user_password = true   # Secrets Manager integration

  serverlessv2_scaling_configuration {
    min_capacity = 0.5
    max_capacity = 8
  }

  vpc_security_group_ids = [aws_security_group.rds.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name
  skip_final_snapshot    = false
  deletion_protection    = true

  tags = { Environment = "prod" }
}

resource "aws_rds_cluster_instance" "app" {
  cluster_identifier = aws_rds_cluster.app.id
  instance_class     = "db.serverless"
  engine             = aws_rds_cluster.app.engine
  engine_version     = aws_rds_cluster.app.engine_version
}

Pass the connection details to Beanstalk after provisioning:

eb setenv \
  DB_HOST=$(terraform output -raw rds_endpoint) \
  DB_NAME=myapp \
  DB_PORT=5432 \
  DB_SECRET_ARN=$(terraform output -raw rds_secret_arn) \
  my-app-prod

Migration strategy when moving from coupled to decoupled RDS:

  1. Create a snapshot of the coupled RDS instance from the Beanstalk console.
  2. Restore the snapshot to a new standalone RDS cluster (outside Beanstalk).
  3. Update Beanstalk environment variables to point at the new cluster endpoint.
  4. Deploy and verify application connectivity.
  5. Remove the coupled database configuration from Beanstalk (this deletes the old coupled instance — your data is now on the standalone cluster).

Custom Platform Hooks: Procfile and Buildfile

Beyond .ebextensions, Beanstalk's Amazon Linux 2023 platforms support Procfile and Buildfile for controlling how the application is started and how the build is performed on the instance.

Procfile defines the process that Beanstalk should run. For Java, Beanstalk can run the JAR directly — but a Procfile lets you customise JVM flags, wrap the process with a custom script, or run multiple processes.

# Procfile (in project root, included in deployment ZIP)
web: java $JAVA_TOOL_OPTIONS -jar target/myapp.jar --server.port=8080
worker: java -jar target/myapp.jar --spring.profiles.active=worker

Buildfile specifies a build command that runs on the instance before the Procfile process is started. This is useful for compiled assets or on-instance Maven/Gradle builds (uncommon but supported).

# Buildfile (in project root)
build: ./scripts/build-assets.sh

Platform hooks are shell scripts placed in predefined hook directories. They run at specific lifecycle events and give you finer control than .ebextensions:

project-root/
├── .platform/
│   └── hooks/
│       ├── prebuild/          # Before application is built/staged
│       │   └── 01-set-timezone.sh
│       ├── predeploy/         # Before new version goes live (after staging)
│       │   └── 01-run-migrations.sh
│       └── postdeploy/        # After new version is live
│           └── 01-warm-cache.sh
#!/bin/bash
# .platform/hooks/predeploy/01-run-migrations.sh
# Only run on the first (leader) instance

TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/instance-id)

# Use a DynamoDB lock or SSM parameter to elect leader
LOCK=$(aws ssm put-parameter \
  --name "/myapp/migration-lock" \
  --value "$INSTANCE_ID" \
  --type String \
  --overwrite \
  --query "Version" \
  --output text 2>/dev/null || echo "locked")

if [ "$LOCK" = "locked" ]; then
  echo "Migration already running on another instance, skipping."
  exit 0
fi

cd /var/app/staging
java -jar target/myapp.jar --spring.profiles.active=migrate --spring.main.web-application-type=none

# Release lock
aws ssm delete-parameter --name "/myapp/migration-lock"
Platform hooks vs container_commands: Platform hooks (in .platform/hooks/) are the modern AL2023 replacement for container_commands. They are shell scripts with proper exit code handling and run in a defined order. Use platform hooks for new AL2023 environments; continue using container_commands for AL2 compatibility.

CI/CD with CodePipeline and Terraform

A complete CI/CD pipeline for Elastic Beanstalk connects CodeCommit (or GitHub) → CodeBuild (build + test) → Elastic Beanstalk (deploy). CodePipeline orchestrates the stages and handles artifact storage in S3.

# terraform/codepipeline.tf
# S3 bucket for pipeline artifacts
resource "aws_s3_bucket" "pipeline_artifacts" {
  bucket        = "myapp-pipeline-artifacts-${data.aws_caller_identity.current.account_id}"
  force_destroy = true
}

resource "aws_s3_bucket_versioning" "pipeline_artifacts" {
  bucket = aws_s3_bucket.pipeline_artifacts.id
  versioning_configuration { status = "Enabled" }
}

# CodeBuild project
resource "aws_codebuild_project" "build" {
  name          = "myapp-build"
  service_role  = aws_iam_role.codebuild.arn
  build_timeout = 20

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "aws/codebuild/standard:7.0"
    type                        = "LINUX_CONTAINER"
    image_pull_credentials_type = "CODEBUILD"

    environment_variable {
      name  = "MAVEN_OPTS"
      value = "-Xmx1024m"
    }
  }

  source {
    type      = "CODEPIPELINE"
    buildspec = "buildspec.yml"
  }
}

# CodePipeline
resource "aws_codepipeline" "app" {
  name     = "myapp-prod-pipeline"
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    location = aws_s3_bucket.pipeline_artifacts.bucket
    type     = "S3"
  }

  stage {
    name = "Source"
    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["source_output"]
      configuration = {
        ConnectionArn    = aws_codestarconnections_connection.github.arn
        FullRepositoryId = "myorg/myapp"
        BranchName       = "main"
        DetectChanges    = "true"
      }
    }
  }

  stage {
    name = "Build"
    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["source_output"]
      output_artifacts = ["build_output"]
      configuration = {
        ProjectName = aws_codebuild_project.build.name
      }
    }
  }

  stage {
    name = "Deploy"
    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "ElasticBeanstalk"
      version         = "1"
      input_artifacts = ["build_output"]
      configuration = {
        ApplicationName = "my-spring-boot-app"
        EnvironmentName = "my-app-prod"
      }
    }
  }
}

Your buildspec.yml compiles the JAR and packages it with .ebextensions and .platform directories:

# buildspec.yml (project root)
version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto21
  pre_build:
    commands:
      - echo "Running tests..."
      - ./mvnw test
  build:
    commands:
      - echo "Building JAR..."
      - ./mvnw package -DskipTests
      - echo "Build completed: $(ls target/*.jar)"
  post_build:
    commands:
      - echo "Packaging deployment bundle..."
      - mkdir -p deploy
      - cp target/myapp-*.jar deploy/myapp.jar
      - cp -r .ebextensions deploy/ 2>/dev/null || true
      - cp -r .platform deploy/ 2>/dev/null || true
      - cp Procfile deploy/ 2>/dev/null || true
      - cd deploy && zip -r ../myapp-deploy.zip .

artifacts:
  files:
    - myapp-deploy.zip
  discard-paths: yes

cache:
  paths:
    - /root/.m2/**/*
Zero-downtime pipeline tip: Add a manual approval action between Build and Deploy for production. This gives your team a gate to review smoke test results from a staging environment before production deploy. CodePipeline supports approval actions natively — approvers receive an SNS notification and click "Approve" in the console or via the CLI.

Monitoring, Enhanced Health, and Managed Updates

Elastic Beanstalk has two health reporting modes: basic and enhanced. Enhanced health reporting (strongly recommended for production) installs the Beanstalk health agent on every instance, which reports application-level metrics — request counts, latency percentiles, HTTP status code distributions — in real time to the Beanstalk console and CloudWatch.

# .ebextensions/monitoring.config
option_settings:
  aws:elasticbeanstalk:healthreporting:system:
    SystemType: enhanced
    HealthCheckSuccessThreshold: Ok

  aws:elasticbeanstalk:cloudwatch:logs:
    StreamLogs: true
    DeleteOnTerminate: false
    RetentionInDays: 30

  aws:elasticbeanstalk:cloudwatch:logs:health:
    HealthStreamingEnabled: true
    DeleteOnTerminate: false
    RetentionInDays: 7

  # Custom CloudWatch alarm: p99 latency > 2s
  aws:elasticbeanstalk:application:
    Application Healthcheck URL: /actuator/health

Add custom CloudWatch alarms for your business metrics:

# CloudWatch alarm: p99 application latency > 2 seconds
aws cloudwatch put-metric-alarm \
  --alarm-name "myapp-prod-p99-latency" \
  --metric-name "InstancesOk" \
  --namespace "AWS/ElasticBeanstalk" \
  --dimensions Name=EnvironmentName,Value=my-app-prod \
  --statistic Average \
  --period 60 \
  --threshold 2000 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 3 \
  --alarm-actions arn:aws:sns:us-east-1:123456789:myapp-alerts

# Enhanced health dashboard via EB CLI
eb health my-app-prod --refresh

Managed Platform Updates automatically apply minor and patch platform updates to your environment during a weekly maintenance window. You set the window; Beanstalk applies the update using an immutable deployment to avoid downtime.

# .ebextensions/managed-updates.config
option_settings:
  aws:elasticbeanstalk:managedactions:
    ManagedActionsEnabled: true
    PreferredStartTime: "Sun:04:00"    # UTC

  aws:elasticbeanstalk:managedactions:platformupdate:
    UpdateLevel: minor                 # "patch" or "minor"
    InstanceRefreshEnabled: true

Enhanced health introduces Beanstalk-specific health colours: Green (all OK), Yellow (degraded — some requests failing or high latency), Red (severely degraded — many requests failing), and Grey (no data). When an environment goes Yellow during a rolling deployment, Beanstalk halts the deployment and waits for you to investigate. This automatic deployment brake has saved countless production incidents.

# View enhanced health breakdown
eb health my-app-prod

# Environment  my-app-prod                     Health   Green
# Stats          p99   p90   p75   p50  Requests  2xx   4xx  5xx
#   Duration  120ms  85ms  62ms  41ms      4200  98.2% 1.5% 0.3%
#
# Instance ID         State   Health  CPUUser  Requests  Last Deploy
# i-0abc123def456789  InService Ok    22%      1400      17m
# i-0def456abc123789  InService Ok    19%      1400      17m
# i-0123abc789def456  InService Ok    24%      1400      17m

Frequently Asked Questions

Q: Is Elastic Beanstalk free?

Elastic Beanstalk itself has no service charge. You pay only for the underlying AWS resources it provisions: EC2 instances, ALB, data transfer, RDS, etc. The cost profile is identical to provisioning those resources yourself, making Beanstalk purely a management convenience layer.

Q: Can I use Elastic Beanstalk with Docker?

Yes. The Docker platform branch runs a single container defined by a Dockerrun.aws.json v1 file or a standard Dockerfile. For multi-container applications (an app server + a Redis sidecar, for example), Beanstalk supports the Multi-container Docker platform using Dockerrun.aws.json v2, which uses ECS under the hood. However, for serious multi-container workloads, migrating to standalone ECS is usually cleaner.

Q: How do I handle long-running background jobs in Beanstalk?

Use the Worker tier. A Worker environment runs your application alongside a daemon that polls an SQS queue. When a message arrives, the daemon makes an HTTP POST to http://localhost/ (or a configurable path) with the message body. Your app processes it and returns HTTP 200 to acknowledge. The SQS visibility timeout and dead-letter queue are configured in .ebextensions under aws:elasticbeanstalk:sqsd: option settings.

Q: Can I run Elastic Beanstalk in a private VPC without public internet access?

Yes. Configure the environment to use internal ALB (aws:elb:loadbalancer: CrossZone: true + set scheme to internal) and place EC2 instances in private subnets. Instances need NAT Gateway access to reach the Elastic Beanstalk service endpoints, S3 (for artifacts), and Systems Manager. Alternatively, use VPC Interface Endpoints for all these services to avoid NAT costs entirely.

Q: How do I zero-downtime restart without a new deployment?

Use eb ssh to connect and run sudo systemctl restart web, or use the "Restart App Server" action in the Beanstalk console. For a rolling restart (instance by instance), use eb restart from the EB CLI — this performs a rolling instance replacement using the current application version, which restarts the JVM without requiring a new artifact upload.