Quick take — A reusable Terraform module for aws_elastic_beanstalk_environment that codifies platform versions, autoscaling, enhanced health, and rolling deploys so teams ship Elastic Beanstalk apps consistently. New here? Jump to the Quickstart below to deploy it in minutes; read on for how it works and when to reach for it.
Quickstart (copy-paste)
Minimal, runnable configuration — drop this in a .tf file and fill in the "..." placeholders (each required input is commented):
provider "aws" {
region = "us-east-1"
}
module "elastic_beanstalk" {
source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-aws-elastic-beanstalk?ref=v1.0.0"
application_name = "..." # Elastic Beanstalk application name (parent of all envir…
solution_stack_name = "..." # Full 64bit solution stack string.
vpc_id = "..." # VPC the environment launches into.
instance_subnet_ids = ["...", "..."] # Subnets for the EC2 instances / ASG.
instance_profile_name = "..." # IAM instance profile attached to instances.
service_role_arn = "..." # Beanstalk service role ARN (enhanced health, managed up…
}
Then terraform init && terraform apply. Every other input has a sensible default — see Inputs below to override behaviour.
What this module is
AWS Elastic Beanstalk is a managed orchestration layer that takes your application bundle — a JAR, a Docker image reference, a Node app, a .zip of PHP — and provisions the EC2 instances, Auto Scaling group, Elastic Load Balancer, security groups, and CloudWatch alarms needed to run it, while leaving you full access to those underlying resources. You hand it a platform (for example 64bit Amazon Linux 2023 v4.3.1 running Corretto 21) and a solution stack, and it manages the EC2 fleet, deploy strategy, and health reporting for you. It sits one rung below a full PaaS and one rung above raw EC2: more guardrails than rolling your own ASG, more escape hatches than App Runner or Fargate.
The catch is that almost everything meaningful about a Beanstalk environment is configured through option settings — hundreds of namespace / name / value triples spread across namespaces like aws:autoscaling:asg, aws:elasticbeanstalk:environment, aws:elbv2:listener, and aws:elasticbeanstalk:healthreporting:system. Configured by hand in the console, these drift constantly and are nearly impossible to review. This module wraps aws_elastic_beanstalk_environment (plus its parent aws_elastic_beanstalk_application and a versioned aws_elastic_beanstalk_application_version) and exposes the option settings that actually matter in production — instance type, scaling bounds, VPC placement, load balancer type, enhanced health, managed platform updates, and rolling deployment policy — as typed, validated variables. The result is a reviewable, repeatable environment definition instead of a screenshot of a console form.
When to use it
- You want a load-balanced, autoscaling web tier but do not want to assemble the ALB, target group, ASG, launch template, and health checks yourself.
- You are running traditional web frameworks or Dockerized apps (Spring Boot, Rails, Django, Express, single-container Docker) and want managed deploys with rolling/immutable strategies rather than building a CI-driven ECS/EKS pipeline.
- You need consistent environments across
dev/staging/prodwhere the only differences are instance size, scaling bounds, and the application version being promoted. - You want managed platform patching (Beanstalk’s managed updates apply minor platform versions in a maintenance window) instead of rebuilding AMIs.
- Skip it when you need fine-grained pod scheduling, service meshes, or multi-container orchestration — reach for EKS or ECS. Skip it for purely event-driven or request/response-scaled workloads where Lambda or App Runner fit better and cost less at low traffic.
Module structure
terraform-module-aws-elastic-beanstalk/
├── versions.tf # provider + Terraform version pins
├── main.tf # application, application version, environment + option settings
├── variables.tf # typed, validated inputs
└── outputs.tf # ids, CNAME, endpoint URL, ASG/ELB attributes
versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
main.tf
locals {
# Beanstalk environment names are 4-40 chars; many orgs encode app+env+region.
environment_name = coalesce(var.environment_name, "${var.application_name}-${var.environment}")
# Single instance vs LoadBalanced changes which namespaces apply.
is_load_balanced = var.environment_type == "LoadBalanced"
base_settings = [
# --- VPC placement ---
{
namespace = "aws:ec2:vpc"
name = "VPCId"
value = var.vpc_id
},
{
namespace = "aws:ec2:vpc"
name = "Subnets"
value = join(",", var.instance_subnet_ids)
},
{
namespace = "aws:ec2:vpc"
name = "AssociatePublicIpAddress"
value = tostring(var.associate_public_ip_address)
},
# --- Launch / instance profile ---
{
namespace = "aws:autoscaling:launchconfiguration"
name = "IamInstanceProfile"
value = var.instance_profile_name
},
{
namespace = "aws:autoscaling:launchconfiguration"
name = "InstanceType"
value = var.instance_type
},
{
namespace = "aws:autoscaling:launchconfiguration"
name = "EC2KeyName"
value = var.ec2_key_name
},
{
namespace = "aws:autoscaling:launchconfiguration"
name = "RootVolumeType"
value = "gp3"
},
# --- Environment type & service role ---
{
namespace = "aws:elasticbeanstalk:environment"
name = "EnvironmentType"
value = var.environment_type
},
{
namespace = "aws:elasticbeanstalk:environment"
name = "ServiceRole"
value = var.service_role_arn
},
# --- Enhanced health reporting ---
{
namespace = "aws:elasticbeanstalk:healthreporting:system"
name = "SystemType"
value = var.enhanced_health ? "enhanced" : "basic"
},
# --- Managed platform updates ---
{
namespace = "aws:elasticbeanstalk:managedactions"
name = "ManagedActionsEnabled"
value = tostring(var.managed_updates_enabled)
},
{
namespace = "aws:elasticbeanstalk:managedactions"
name = "PreferredStartTime"
value = var.managed_updates_start_time
},
{
namespace = "aws:elasticbeanstalk:managedactions:platformupdate"
name = "UpdateLevel"
value = var.managed_updates_level
},
# --- Deployment policy (rolling / immutable / all-at-once) ---
{
namespace = "aws:elasticbeanstalk:command"
name = "DeploymentPolicy"
value = var.deployment_policy
},
{
namespace = "aws:elasticbeanstalk:command"
name = "BatchSizeType"
value = "Percentage"
},
{
namespace = "aws:elasticbeanstalk:command"
name = "BatchSize"
value = tostring(var.deployment_batch_size_percentage)
},
]
# Autoscaling + ELB settings only make sense for LoadBalanced environments.
load_balanced_settings = local.is_load_balanced ? [
{
namespace = "aws:autoscaling:asg"
name = "MinSize"
value = tostring(var.min_instances)
},
{
namespace = "aws:autoscaling:asg"
name = "MaxSize"
value = tostring(var.max_instances)
},
{
namespace = "aws:elasticbeanstalk:environment"
name = "LoadBalancerType"
value = var.load_balancer_type
},
{
namespace = "aws:ec2:vpc"
name = "ELBSubnets"
value = join(",", coalescelist(var.elb_subnet_ids, var.instance_subnet_ids))
},
{
namespace = "aws:elbv2:loadbalancer"
name = "SecurityGroups"
value = join(",", var.elb_security_group_ids)
},
# Scale on average CPU.
{
namespace = "aws:autoscaling:trigger"
name = "MeasureName"
value = "CPUUtilization"
},
{
namespace = "aws:autoscaling:trigger"
name = "Unit"
value = "Percent"
},
{
namespace = "aws:autoscaling:trigger"
name = "UpperThreshold"
value = tostring(var.scale_up_cpu_threshold)
},
{
namespace = "aws:autoscaling:trigger"
name = "LowerThreshold"
value = tostring(var.scale_down_cpu_threshold)
},
] : []
# Optional HTTPS listener on 443.
https_settings = (local.is_load_balanced && var.https_certificate_arn != null) ? [
{
namespace = "aws:elbv2:listener:443"
name = "ListenerEnabled"
value = "true"
},
{
namespace = "aws:elbv2:listener:443"
name = "Protocol"
value = "HTTPS"
},
{
namespace = "aws:elbv2:listener:443"
name = "SSLCertificateArns"
value = var.https_certificate_arn
},
{
namespace = "aws:elbv2:listener:443"
name = "SSLPolicy"
value = var.ssl_policy
},
] : []
# Application environment variables -> aws:elasticbeanstalk:application:environment
app_env_settings = [
for k, v in var.app_environment_variables : {
namespace = "aws:elasticbeanstalk:application:environment"
name = k
value = v
}
]
all_settings = concat(
local.base_settings,
local.load_balanced_settings,
local.https_settings,
local.app_env_settings,
)
}
resource "aws_elastic_beanstalk_application" "this" {
name = var.application_name
description = var.application_description
dynamic "appversion_lifecycle" {
for_each = var.application_version_retention != null ? [1] : []
content {
service_role = var.service_role_arn
max_count = var.application_version_retention
delete_source_from_s3 = true
}
}
tags = var.tags
}
# Bundle stored in S3 and promoted as an immutable, named version.
resource "aws_elastic_beanstalk_application_version" "this" {
count = var.source_bundle_bucket != null && var.source_bundle_key != null ? 1 : 0
name = "${var.application_name}-${var.application_version_label}"
application = aws_elastic_beanstalk_application.this.name
description = "Version ${var.application_version_label}"
bucket = var.source_bundle_bucket
key = var.source_bundle_key
tags = var.tags
}
resource "aws_elastic_beanstalk_environment" "this" {
name = local.environment_name
application = aws_elastic_beanstalk_application.this.name
solution_stack_name = var.solution_stack_name
tier = var.tier
cname_prefix = var.cname_prefix
version_label = try(aws_elastic_beanstalk_application_version.this[0].name, null)
dynamic "setting" {
for_each = local.all_settings
content {
namespace = setting.value.namespace
name = setting.value.name
value = setting.value.value
}
}
tags = var.tags
# The platform's managed updates may bump solution_stack_name out of band;
# avoid perpetual diffs by ignoring drift on that attribute if desired.
lifecycle {
ignore_changes = [version_label]
}
}
variables.tf
variable "application_name" {
type = string
description = "Name of the Elastic Beanstalk application (parent of all environments)."
validation {
condition = length(var.application_name) >= 1 && length(var.application_name) <= 100
error_message = "application_name must be between 1 and 100 characters."
}
}
variable "application_description" {
type = string
description = "Human-readable description for the Beanstalk application."
default = "Managed by Terraform"
}
variable "environment" {
type = string
description = "Logical environment suffix (e.g. dev, staging, prod) used to derive the environment name."
default = "dev"
}
variable "environment_name" {
type = string
description = "Explicit Beanstalk environment name. If null, derived as <application_name>-<environment>."
default = null
validation {
condition = var.environment_name == null || can(regex("^[a-zA-Z0-9][a-zA-Z0-9-]{2,38}[a-zA-Z0-9]$", var.environment_name))
error_message = "environment_name must be 4-40 chars, alphanumeric and hyphens, not starting/ending with a hyphen."
}
}
variable "solution_stack_name" {
type = string
description = "Full Beanstalk solution stack, e.g. '64bit Amazon Linux 2023 v4.3.1 running Corretto 21'."
validation {
condition = can(regex("^64bit ", var.solution_stack_name))
error_message = "solution_stack_name should be a full 64bit solution stack string from `aws elasticbeanstalk list-available-solution-stacks`."
}
}
variable "tier" {
type = string
description = "Environment tier: 'WebServer' for HTTP apps or 'Worker' for SQS-backed background processing."
default = "WebServer"
validation {
condition = contains(["WebServer", "Worker"], var.tier)
error_message = "tier must be either 'WebServer' or 'Worker'."
}
}
variable "environment_type" {
type = string
description = "'LoadBalanced' (ALB/NLB + ASG) or 'SingleInstance' (one EC2, no LB)."
default = "LoadBalanced"
validation {
condition = contains(["LoadBalanced", "SingleInstance"], var.environment_type)
error_message = "environment_type must be 'LoadBalanced' or 'SingleInstance'."
}
}
variable "cname_prefix" {
type = string
description = "Optional CNAME prefix for the *.<region>.elasticbeanstalk.com endpoint. Null lets AWS generate one."
default = null
}
# --- Networking ---
variable "vpc_id" {
type = string
description = "VPC the environment is launched into."
}
variable "instance_subnet_ids" {
type = list(string)
description = "Subnets (typically private) for the EC2 instances / ASG."
validation {
condition = length(var.instance_subnet_ids) > 0
error_message = "At least one instance subnet is required."
}
}
variable "elb_subnet_ids" {
type = list(string)
description = "Subnets (typically public) for the load balancer. Defaults to instance_subnet_ids if empty."
default = []
}
variable "associate_public_ip_address" {
type = bool
description = "Whether EC2 instances get public IPs. Keep false for private-subnet deployments."
default = false
}
# --- Compute / IAM ---
variable "instance_type" {
type = string
description = "EC2 instance type for the environment's instances."
default = "t3.small"
}
variable "instance_profile_name" {
type = string
description = "Name of the IAM instance profile attached to EC2 instances (e.g. aws-elasticbeanstalk-ec2-role)."
}
variable "service_role_arn" {
type = string
description = "ARN of the Beanstalk service role used for enhanced health and managed updates."
}
variable "ec2_key_name" {
type = string
description = "EC2 key pair name for SSH access. Empty string disables SSH key injection."
default = ""
}
# --- Autoscaling (LoadBalanced only) ---
variable "min_instances" {
type = number
description = "Minimum size of the Auto Scaling group."
default = 2
validation {
condition = var.min_instances >= 1
error_message = "min_instances must be at least 1."
}
}
variable "max_instances" {
type = number
description = "Maximum size of the Auto Scaling group."
default = 4
validation {
condition = var.max_instances >= 1
error_message = "max_instances must be at least 1."
}
}
variable "scale_up_cpu_threshold" {
type = number
description = "Average CPU percent above which the ASG scales out."
default = 70
}
variable "scale_down_cpu_threshold" {
type = number
description = "Average CPU percent below which the ASG scales in."
default = 30
}
variable "load_balancer_type" {
type = string
description = "Load balancer type for LoadBalanced environments."
default = "application"
validation {
condition = contains(["application", "network", "classic"], var.load_balancer_type)
error_message = "load_balancer_type must be 'application', 'network', or 'classic'."
}
}
variable "elb_security_group_ids" {
type = list(string)
description = "Security groups attached to the load balancer (LoadBalanced only)."
default = []
}
# --- HTTPS listener ---
variable "https_certificate_arn" {
type = string
description = "ACM certificate ARN. When set (and LoadBalanced), enables an HTTPS listener on 443."
default = null
}
variable "ssl_policy" {
type = string
description = "TLS security policy for the HTTPS listener."
default = "ELBSecurityPolicy-TLS13-1-2-2021-06"
}
# --- Deployments ---
variable "deployment_policy" {
type = string
description = "How new versions roll out: AllAtOnce, Rolling, RollingWithAdditionalBatch, or Immutable."
default = "Rolling"
validation {
condition = contains(["AllAtOnce", "Rolling", "RollingWithAdditionalBatch", "Immutable"], var.deployment_policy)
error_message = "deployment_policy must be AllAtOnce, Rolling, RollingWithAdditionalBatch, or Immutable."
}
}
variable "deployment_batch_size_percentage" {
type = number
description = "Percentage of instances updated per batch during a rolling deploy."
default = 50
validation {
condition = var.deployment_batch_size_percentage > 0 && var.deployment_batch_size_percentage <= 100
error_message = "deployment_batch_size_percentage must be between 1 and 100."
}
}
# --- Health & managed updates ---
variable "enhanced_health" {
type = bool
description = "Enable enhanced health reporting (requires a service role). Strongly recommended."
default = true
}
variable "managed_updates_enabled" {
type = bool
description = "Enable Beanstalk managed platform updates in a maintenance window."
default = true
}
variable "managed_updates_start_time" {
type = string
description = "Weekly maintenance window start, in 'Day:HH:MM' UTC format (e.g. 'Sun:03:00')."
default = "Sun:03:00"
}
variable "managed_updates_level" {
type = string
description = "Highest update level applied automatically: 'minor' or 'patch'."
default = "minor"
validation {
condition = contains(["minor", "patch"], var.managed_updates_level)
error_message = "managed_updates_level must be 'minor' or 'patch'."
}
}
# --- Application version / source bundle ---
variable "application_version_label" {
type = string
description = "Version label appended to the application version name (e.g. a git SHA or semver)."
default = "initial"
}
variable "source_bundle_bucket" {
type = string
description = "S3 bucket holding the deployable source bundle. Null skips creating an application version."
default = null
}
variable "source_bundle_key" {
type = string
description = "S3 key of the deployable source bundle (zip/jar/war). Null skips creating an application version."
default = null
}
variable "application_version_retention" {
type = number
description = "Max application versions to retain via lifecycle policy. Null disables the lifecycle rule."
default = 10
}
# --- App config ---
variable "app_environment_variables" {
type = map(string)
description = "Key/value pairs injected as application environment variables."
default = {}
}
variable "tags" {
type = map(string)
description = "Tags applied to the application, version, and environment."
default = {}
}
outputs.tf
output "application_name" {
description = "Name of the Elastic Beanstalk application."
value = aws_elastic_beanstalk_application.this.name
}
output "environment_id" {
description = "ID of the Elastic Beanstalk environment."
value = aws_elastic_beanstalk_environment.this.id
}
output "environment_name" {
description = "Name of the Elastic Beanstalk environment."
value = aws_elastic_beanstalk_environment.this.name
}
output "cname" {
description = "Fully qualified CNAME of the environment (the *.elasticbeanstalk.com hostname)."
value = aws_elastic_beanstalk_environment.this.cname
}
output "endpoint_url" {
description = "Load balancer / instance endpoint URL for the environment."
value = aws_elastic_beanstalk_environment.this.endpoint_url
}
output "autoscaling_groups" {
description = "Auto Scaling group names backing the environment."
value = aws_elastic_beanstalk_environment.this.autoscaling_groups
}
output "load_balancers" {
description = "Load balancer names associated with the environment (empty for SingleInstance)."
value = aws_elastic_beanstalk_environment.this.load_balancers
}
output "instances" {
description = "EC2 instance IDs currently running in the environment."
value = aws_elastic_beanstalk_environment.this.instances
}
output "application_version_label" {
description = "The application version label deployed to the environment, if a source bundle was provided."
value = try(aws_elastic_beanstalk_application_version.this[0].name, null)
}
How to use it
module "elastic_beanstalk" {
source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-aws-elastic-beanstalk?ref=v1.0.0"
application_name = "orders-api"
environment = "prod"
solution_stack_name = "64bit Amazon Linux 2023 v4.3.1 running Corretto 21"
environment_type = "LoadBalanced"
cname_prefix = "orders-api-prod"
# Networking
vpc_id = module.network.vpc_id
instance_subnet_ids = module.network.private_subnet_ids
elb_subnet_ids = module.network.public_subnet_ids
elb_security_group_ids = [aws_security_group.alb.id]
# Compute / IAM
instance_type = "t3.medium"
instance_profile_name = aws_iam_instance_profile.beanstalk_ec2.name
service_role_arn = aws_iam_role.beanstalk_service.arn
# Autoscaling
min_instances = 3
max_instances = 8
# Safe production rollout
deployment_policy = "Immutable"
deployment_batch_size_percentage = 50
enhanced_health = true
# HTTPS
https_certificate_arn = aws_acm_certificate.orders_api.arn
# Deployable artifact built in CI and uploaded to S3
source_bundle_bucket = aws_s3_bucket.artifacts.id
source_bundle_key = "orders-api/${var.git_sha}.jar"
application_version_label = var.git_sha
app_environment_variables = {
SPRING_PROFILES_ACTIVE = "prod"
JAVA_TOOL_OPTIONS = "-XX:MaxRAMPercentage=75"
DB_HOST = module.orders_db.endpoint
}
tags = {
Team = "payments"
Environment = "prod"
CostCenter = "cc-4471"
}
}
# Downstream: point a friendly Route 53 record at the environment's CNAME.
resource "aws_route53_record" "orders_api" {
zone_id = aws_route53_zone.public.zone_id
name = "orders.example.com"
type = "CNAME"
ttl = 300
records = [module.elastic_beanstalk.cname]
}
With Terragrunt
Terragrunt keeps this module DRY across environments — define the backend and provider once in a root config, then a thin terragrunt.hcl per environment supplies only the inputs that differ.
1. Root config — live/terragrunt.hcl (inherited by every module):
remote_state {
backend = "s3"
generate = { path = "backend.tf", if_exists = "overwrite" }
config = {
# ...s3 state bucket/container + key per path...
}
}
2. Module config — live/prod/elastic_beanstalk/terragrunt.hcl:
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-aws-elastic-beanstalk?ref=v1.0.0"
}
inputs = {
application_name = "..."
solution_stack_name = "..."
vpc_id = "..."
instance_subnet_ids = ["...", "..."]
instance_profile_name = "..."
service_role_arn = "..."
}
3. Deploy one environment, or roll out all modules together:
cd live/prod/elastic_beanstalk && terragrunt apply # this module
terragrunt run-all apply # every module under live/prod
Why Terragrunt here: the backend and provider live in one place instead of being copy-pasted into every module; inputs is overridden per environment (dev / stage / prod) without forking the module; and run-all orchestrates dependencies across modules. Reach for it once you have more than one environment or more than a handful of modules — for a single stack, the plain Quickstart above is enough.
Inputs
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| application_name | string | — | yes | Elastic Beanstalk application name (parent of all environments). |
| application_description | string | “Managed by Terraform” | no | Description for the application. |
| environment | string | “dev” | no | Logical env suffix used to derive the environment name. |
| environment_name | string | null | no | Explicit environment name; derived from app + environment if null. |
| solution_stack_name | string | — | yes | Full 64bit solution stack string. |
| tier | string | “WebServer” | no | “WebServer” or “Worker”. |
| environment_type | string | “LoadBalanced” | no | “LoadBalanced” or “SingleInstance”. |
| cname_prefix | string | null | no | CNAME prefix for the elasticbeanstalk.com endpoint. |
| vpc_id | string | — | yes | VPC the environment launches into. |
| instance_subnet_ids | list(string) | — | yes | Subnets for the EC2 instances / ASG. |
| elb_subnet_ids | list(string) | [] | no | Subnets for the load balancer; falls back to instance subnets. |
| associate_public_ip_address | bool | false | no | Whether instances receive public IPs. |
| instance_type | string | “t3.small” | no | EC2 instance type. |
| instance_profile_name | string | — | yes | IAM instance profile attached to instances. |
| service_role_arn | string | — | yes | Beanstalk service role ARN (enhanced health, managed updates). |
| ec2_key_name | string | “” | no | EC2 key pair for SSH; empty disables. |
| min_instances | number | 2 | no | ASG minimum size. |
| max_instances | number | 4 | no | ASG maximum size. |
| scale_up_cpu_threshold | number | 70 | no | Average CPU percent to scale out. |
| scale_down_cpu_threshold | number | 30 | no | Average CPU percent to scale in. |
| load_balancer_type | string | “application” | no | “application”, “network”, or “classic”. |
| elb_security_group_ids | list(string) | [] | no | Security groups on the load balancer. |
| https_certificate_arn | string | null | no | ACM cert ARN; enables HTTPS listener on 443 when set. |
| ssl_policy | string | “ELBSecurityPolicy-TLS13-1-2-2021-06” | no | TLS policy for the HTTPS listener. |
| deployment_policy | string | “Rolling” | no | AllAtOnce, Rolling, RollingWithAdditionalBatch, or Immutable. |
| deployment_batch_size_percentage | number | 50 | no | Percent of instances updated per batch. |
| enhanced_health | bool | true | no | Enable enhanced health reporting. |
| managed_updates_enabled | bool | true | no | Enable managed platform updates. |
| managed_updates_start_time | string | “Sun:03:00” | no | Maintenance window start (Day:HH:MM UTC). |
| managed_updates_level | string | “minor” | no | “minor” or “patch” auto-update level. |
| application_version_label | string | “initial” | no | Version label appended to the application version name. |
| source_bundle_bucket | string | null | no | S3 bucket holding the source bundle. |
| source_bundle_key | string | null | no | S3 key of the source bundle. |
| application_version_retention | number | 10 | no | Max application versions retained; null disables lifecycle. |
| app_environment_variables | map(string) | {} | no | Environment variables injected into the app. |
| tags | map(string) | {} | no | Tags applied to all created resources. |
Outputs
| Name | Description |
|---|---|
| application_name | Name of the Elastic Beanstalk application. |
| environment_id | ID of the environment. |
| environment_name | Name of the environment. |
| cname | Fully qualified CNAME hostname of the environment. |
| endpoint_url | Load balancer / instance endpoint URL. |
| autoscaling_groups | Auto Scaling group names backing the environment. |
| load_balancers | Load balancer names (empty for SingleInstance). |
| instances | EC2 instance IDs currently in the environment. |
| application_version_label | Deployed application version label, if a bundle was provided. |
Enterprise scenario
A payments platform runs a fleet of Spring Boot microservices and standardizes every one on this module so each service gets an identical orders-api-prod style environment: private-subnet instances behind an internet-facing ALB, an ACM-backed HTTPS listener, enhanced health wired to the service role, and managed platform updates pinned to a Sunday 03:00 UTC window. CI builds a JAR, uploads it to a versioned S3 artifact bucket, and passes the git SHA as application_version_label and source_bundle_key, so terraform apply promotes an immutable application version with a full audit trail and instant rollback to the prior label. Setting deployment_policy = "Immutable" means every release spins up a fresh batch of instances and only swaps traffic once they pass health checks, giving the payments team zero-downtime deploys without maintaining their own ECS pipeline.
Best practices
- Always run enhanced health with a real service role. Basic health only checks ELB status; enhanced health surfaces per-instance HTTP 5xx rates, latency percentiles, and degraded causes — essential for catching a bad deploy before it spreads. Keep
enhanced_health = trueand supply a least-privilegeservice_role_arn. - Use Immutable or RollingWithAdditionalBatch in production.
AllAtOncecauses an outage on every deploy andRollingreduces capacity mid-deploy; immutable deployments build a parallel batch and roll back cleanly if health checks fail, which is worth the extra instance-minutes for revenue-facing services. - Treat application versions as immutable, S3-backed artifacts. Pass a unique
application_version_label(a git SHA) per release and setapplication_version_retentionso old versions are pruned from S3 — this keeps rollbacks trivial and stops unbounded artifact storage cost. - Place instances in private subnets and keep the LB separate. Set
associate_public_ip_address = false, put instances ininstance_subnet_ids(private) and the ALB inelb_subnet_ids(public), and lock the instance security group to only accept traffic fromelb_security_group_ids. - Right-size the ASG and scale on CPU. Defaults of 2–4
t3.smallinstances suit a light service; for steady production load raisemin_instancesfor availability across AZs and tunescale_up_cpu_threshold/scale_down_cpu_thresholdso you are not paying for idle capacity or thrashing the group. - Pin and patch the platform deliberately. Set
solution_stack_nameto an explicit version for reproducibility, but leavemanaged_updates_enabled = truewithmanaged_updates_level = "minor"so Beanstalk applies security patches in your maintenance window instead of you rebuilding AMIs by hand.