Secure-by-default AWS CodeDeploy application + deployment group(s) + custom deployment config(s) β Server (EC2/On-Premises), Lambda, and ECS compute platforms, with alarm-triggered and failure-triggered auto-rollback ON by default. Built for the AWS provider v6.x.
- π Provisions the
aws_codedeploy_appkeystone plus any number of deployment groups and custom deployment configs from a single composite call. - π₯οΈ Supports all three CodeDeploy compute platforms β
Server(EC2/On-Premises),Lambda, andECSβ set once on the application and gating which deployment-group / deployment-config arguments are valid. - π Auto-rollback ON by default β every deployment group rolls back automatically on
DEPLOYMENT_FAILURE; supplying CloudWatch alarm names auto-enables alarm monitoring and appendsDEPLOYMENT_STOP_ON_ALARM. - π Fail-closed alarm polling β
ignore_poll_alarm_failuredefaults tofalse: a deployment stops (does not proceed blind) if CloudWatch alarm state can't be retrieved. - π΅π’ First-class blue/green support β
deployment_style,blue_green_deployment_config, and ECS/ALBtarget_group_pair_infotraffic shifting. - π
iam:PassRolecalled out prominently β this module never creates the CodeDeploy service role; it only consumesservice_role_arnby reference and documents the caller-principal trust boundary required to pass it. - π·οΈ Universal tagging on the application and every deployment group;
aws_codedeploy_deployment_confighas no tags argument β deliberately excluded, and documented as an exception (the CodeDeploy analog of the ASG tag-block exception). - π§± Deployment groups and custom deployment configs are
map(object(...))keyed by their own name β cleanfor_each, nocount.
π‘ Why it matters: A regulated FI's release pipeline is only as safe as its rollback discipline. Bundling the application, deployment groups, and any custom rollout curve behind one secure-by-default module means every deployment target β EC2 fleet, Lambda alias, or ECS service β inherits the same fail-closed rollback posture without each team having to remember to wire it themselves.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
tf-mod-aws-codedeploy is a Phase 5 (Developer Tools) composite. It deliberately creates none of its own compute, networking, messaging, or IAM resources β every deployment target is wired in by reference from a sibling module, and the service role that CodeDeploy assumes is consumed, never created.
flowchart LR
iam["tf-mod-aws-iam-role"]
asg["tf-mod-aws-autoscaling-group"]
ecsc["tf-mod-aws-ecs-cluster"]
ecss["tf-mod-aws-ecs-service"]
lb["tf-mod-aws-lb"]
sns["tf-mod-aws-sns"]
cwa["tf-mod-aws-cloudwatch-alarm"]
cd["tf-mod-aws-codedeploy"]
cp["tf-mod-aws-codepipeline"]
iam -- "service_role_arn (iam:PassRole)" --> cd
asg -- "autoscaling_groups (names)" --> cd
ecsc -- "cluster_name" --> ecss
ecss -- "service_name" --> cd
lb -- "target_group / listener_arns" --> cd
sns -- "trigger_target_arn" --> cd
cwa -- "alarm name (NOT arn)" --> cd
cd -- "application_name / deployment_group_name" --> cp
style cd fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
βΉοΈ
tf-mod-aws-iam-role,tf-mod-aws-autoscaling-group,tf-mod-aws-ecs-cluster,tf-mod-aws-ecs-service,tf-mod-aws-lb, andtf-mod-aws-snsare Phase 1 / Phase 2 modules;tf-mod-aws-cloudwatch-alarmis Phase 4;tf-mod-aws-codepipelineis a Phase 5 sibling that consumes this module'snameanddeployment_group_namesoutputs for its deploy action.
flowchart TB
subgraph CDMOD["tf-mod-aws-codedeploy"]
app["aws_codedeploy_app.this<br/>(keystone)<br/>name, compute_platform, tags"]
subgraph DG["aws_codedeploy_deployment_group.this<br/>(for_each: map keyed by deployment_group_name)"]
dg1["service_role_arn (var, shared)<br/>ec2_tag_filter / ec2_tag_set / autoscaling_groups<br/>ecs_service / load_balancer_info<br/>deployment_style / blue_green_deployment_config<br/>alarm_configuration / auto_rollback_configuration<br/>trigger_configuration / tags"]
end
subgraph DC["aws_codedeploy_deployment_config.this<br/>(for_each: map keyed by deployment_config_name)"]
dc1["compute_platform<br/>minimum_healthy_hosts (Server)<br/>traffic_routing_config (ECS/Lambda)<br/>zonal_config (Server)<br/>NO tags argument"]
end
end
app -- "app_name" --> DG
DG -. "deployment_config_name (by name, optional)".-> DC
style app fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
| Resource | Role | Cardinality |
|---|---|---|
aws_codedeploy_app.this |
Keystone application; owns compute_platform |
1 |
aws_codedeploy_deployment_group.this |
Deployment target(s) β in-place or blue/green | per deployment_groups entry |
aws_codedeploy_deployment_config.this |
Custom rollout curve(s) beyond the AWS-managed presets | per deployment_configs entry (0-N) |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
No provider {} block is declared inside the module β the caller's configured provider (region, credentials, default_tags) is inherited. No region variable β CodeDeploy is a standard regional service.
β οΈ iam:PassRoleis mandatory and must be scoped tightly. Everyaws_codedeploy_deployment_groupthis module creates requires the Terraform identity running this module to passvar.service_role_arnto thecodedeploy.amazonaws.comservice principal. Grantiam:PassRoleconditioned oniam:PassedToService = codedeploy.amazonaws.comand scoped to the exact role ARN(s) this module is expected to pass β never a wildcardResource: "*"grant. Without this permission,aws_codedeploy_deployment_groupcreation fails with an access-denied /InvalidRoleExceptionerror even though the role itself exists and is correctly configured.
Least-privilege actions the Terraform identity needs:
| Action | Required for | Notes |
|---|---|---|
codedeploy:CreateApplication, codedeploy:DeleteApplication, codedeploy:GetApplication, codedeploy:UpdateApplication |
Application lifecycle | β |
codedeploy:CreateDeploymentGroup, codedeploy:DeleteDeploymentGroup, codedeploy:GetDeploymentGroup, codedeploy:UpdateDeploymentGroup |
Deployment group lifecycle | β |
codedeploy:CreateDeploymentConfig, codedeploy:DeleteDeploymentConfig, codedeploy:GetDeploymentConfig |
Custom deployment config lifecycle | Only when deployment_configs is non-empty; AWS-managed presets need no create permission |
codedeploy:ListApplications, codedeploy:ListDeploymentGroups, codedeploy:ListDeploymentConfigs, codedeploy:ListTagsForResource |
Drift detection / terraform plan reads |
β |
codedeploy:TagResource, codedeploy:UntagResource |
Tagging (application + deployment groups) | β |
iam:PassRole |
Passing service_role_arn to CodeDeploy |
MANDATORY β condition on iam:PassedToService = codedeploy.amazonaws.com; see callout above |
iam:GetRole |
Validating the supplied service_role_arn exists |
Read-only |
The CodeDeploy service role itself (created by tf-mod-aws-iam-role, never by this module) additionally needs one AWS-managed policy, selected by compute_platform:
compute_platform |
AWS-managed policy | Grants |
|---|---|---|
Server |
AWSCodeDeployRole |
Read EC2/ASG tags; manage ASG lifecycle hooks/scaling; publish to SNS; read CloudWatch alarms; read/update Classic ELB / target groups. Launch-template ASGs additionally need ec2:RunInstances, ec2:CreateTags, and iam:PassRole for the instance profile. |
ECS (full access) |
AWSCodeDeployRoleForECS |
Manage ECS task sets; update ELB target groups/listeners/rules; invoke Lambda hooks; read S3 revision artifacts; read CloudWatch alarms; publish SNS |
ECS (limited) |
AWSCodeDeployRoleForECSLimited |
Manage ECS task sets; read CloudWatch alarms; publish SNS (no ELB/S3/Lambda-hook access) |
Lambda (full access) |
AWSCodeDeployRoleForLambda |
Read/update/invoke Lambda functions and aliases; read S3 revision artifacts; read CloudWatch alarms; publish SNS |
Lambda (limited) |
AWSCodeDeployRoleForLambdaLimited |
Read/update/invoke Lambda functions and aliases; read S3 revision artifacts; read CloudWatch alarms (no SNS publish) |
- No service-linked role. CodeDeploy relies entirely on the caller-supplied
service_role_arn(a standard IAM role, not a service-linked role) β the role and its trust policy (trustingcodedeploy.amazonaws.com) must exist before any deployment group is created. compute_platformis set once, at the application level, and drives which deployment-group and deployment-config arguments are valid β see the compatibility matrix below.- Region: No global-service constraint; CodeDeploy is regional and the module inherits the caller's provider region.
- Quotas (soft, raisable via Service Quotas): 100 applications per account/Region (default) Β· 100 deployment groups per application Β· 20 concurrent deployments per account/Region Β· 1,500 instances per deployment group (Server).
- Blue/green with
COPY_AUTO_SCALING_GROUP: CodeDeploy provisions a new, Terraform-unmanaged ASG (namedCodeDeploy_<deployment-group-name>_<deployment-id>) outside Terraform state. Prefergreen_fleet_provisioning_option.action = "DISCOVER_EXISTING"with separately Terraform-managed blue/green ASGs to avoid drift and orphaned infrastructure on destroy. - SNS trigger permission: if
trigger_configurationis set, the target SNS topic's resource policy (or the service role) must allowcodedeploy.amazonaws.com/ the service role tosns:Publishβ granted on the topic (tf-mod-aws-sns), not by this module.
| Deployment-group argument | Server |
ECS |
Lambda |
|---|---|---|---|
ec2_tag_filter / ec2_tag_set |
β | β | β |
on_premises_instance_tag_filter |
β | β | β |
autoscaling_groups |
β | β | β |
ecs_service |
β | β Required | β |
deployment_style.deployment_type = IN_PLACE |
β | β (BLUE_GREEN only) | N/A (block not used) |
deployment_style.deployment_type = BLUE_GREEN |
β (optional) | β Required | N/A |
load_balancer_info.elb_info (Classic ELB) |
β | β | β |
load_balancer_info.target_group_info |
β | β | β |
load_balancer_info.target_group_pair_info |
β | β Required for blue/green | β |
blue_green_deployment_config |
β (blue/green only) | β Required | β |
termination_hook_enabled |
β | β | β |
alarm_configuration |
β | β | β |
auto_rollback_configuration |
β | β | β |
trigger_configuration |
β | β | β |
| Deployment-config argument | Server |
ECS |
Lambda |
|---|---|---|---|
minimum_healthy_hosts |
β Required | β | β |
traffic_routing_config (TimeBasedCanary / TimeBasedLinear / AllAtOnce) |
β | β | β |
zonal_config |
β | β | β |
βΉοΈ This module's
variables.tfenforces the β/β Required cells above withvalidation {}blocks againstvar.compute_platform, so a mismatch fails atterraform planwith a named error instead of a server-side CodeDeploy API rejection.
tf-mod-aws-codedeploy/
βββ providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider block
βββ variables.tf # name, service_role_arn, compute_platform, deployment_groups, deployment_configs, tags
βββ main.tf # locals (secure-default computation) + the three resources
βββ outputs.tf # id + arn + name, deployment_group/config maps, tags_all
βββ README.md # this file
βββ SCOPE.md # in/out-of-scope, IAM, prerequisites, compatibility matrix, gotchas
module "deploy_role" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
name = "casey-codedeploy-service-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "codedeploy.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"]
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-billing-service"
service_role_arn = module.deploy_role.arn # iam:PassRole required on the caller β see above
deployment_groups = {
production = {
autoscaling_groups = ["casey-billing-prod-asg"] # from tf-mod-aws-autoscaling-group
}
}
tags = {
Environment = "prod"
App = "billing-service"
}
}
β οΈ Pin the source with?ref=v1.0.0, never a branch. The Terraform identity applying this module must be separately grantediam:PassRoleonmodule.deploy_role.arn, conditioned oniam:PassedToService = codedeploy.amazonaws.comβ this module cannot grant that permission to itself.
| Input | Type | Source module |
|---|---|---|
service_role_arn |
string (ARN) |
tf-mod-aws-iam-role β REQUIRED, see IAM section above |
deployment_groups[*].autoscaling_groups |
list(string) (ASG names) |
tf-mod-aws-autoscaling-group |
deployment_groups[*].ecs_service.cluster_name / .service_name |
string |
tf-mod-aws-ecs-cluster / tf-mod-aws-ecs-service |
deployment_groups[*].load_balancer_info names / listener ARNs |
string / list(string) |
tf-mod-aws-lb |
deployment_groups[*].trigger_configuration[*].trigger_target_arn |
string (ARN) |
tf-mod-aws-sns |
deployment_groups[*].alarm_configuration.alarms |
list(string) (alarm names, not ARNs) |
tf-mod-aws-cloudwatch-alarm (pass the alarm's name output) |
| Output | Description | Consumed by |
|---|---|---|
id |
AWS-assigned application id | Reference/lookup |
arn |
Application ARN β arn:aws:codedeploy:<region>:<account>:application:<name> |
tf-mod-aws-iam-policy (scoping policies), audit |
name |
Application name | tf-mod-aws-codepipeline deploy-action application_name; deployment group app_name |
deployment_group_ids / deployment_group_arns |
Maps keyed by deployment group name | tf-mod-aws-codepipeline deploy-action wiring; audit |
deployment_group_names |
Map of deployment group names (echoes the map key) | CLI/console cross-reference; CodePipeline deployment_group_name |
deployment_config_ids / deployment_config_arns |
Maps of any custom deployment configs created | deployment_groups[*].deployment_config_name wiring, including cross-module reuse |
tags_all |
All tags on the application, incl. provider default_tags |
Governance/audit |
1 Β· Minimal β Server-platform application, no deployment groups yet
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-minimal-app"
service_role_arn = module.deploy_role.arn
# compute_platform defaults to "Server"; deployment_groups defaults to {}
}2 Β· EC2/ASG deployment group β wiring tf-mod-aws-autoscaling-group + tf-mod-aws-iam-role
module "deploy_role" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
name = "casey-app-codedeploy-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow", Principal = { Service = "codedeploy.amazonaws.com" }, Action = "sts:AssumeRole"
}]
})
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"]
}
module "asg" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-autoscaling-group?ref=v1.0.0"
name = "casey-app-prod"
#... launch_template, vpc_zone_identifier, min/max/desired...
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app"
service_role_arn = module.deploy_role.arn
deployment_groups = {
production = {
autoscaling_groups = [module.asg.name]
}
}
}3 Β· Blue/green EC2 with a Classic ELB
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-legacy-app"
service_role_arn = module.deploy_role.arn
deployment_groups = {
production = {
deployment_style = {
deployment_type = "BLUE_GREEN"
deployment_option = "WITH_TRAFFIC_CONTROL"
}
load_balancer_info = {
elb_info = { name = "casey-legacy-classic-elb" } # from a Classic ELB, wired by name
}
blue_green_deployment_config = {
deployment_ready_option = {
action_on_timeout = "STOP_DEPLOYMENT"
wait_time_in_minutes = 60
}
green_fleet_provisioning_option = {
action = "DISCOVER_EXISTING" # never COPY_AUTO_SCALING_GROUP β see SCOPE.md gotchas
}
terminate_blue_instances_on_deployment_success = {
action = "KEEP_ALIVE"
}
}
}
}
}4 Β· ECS deployment group β wiring tf-mod-aws-ecs-cluster/tf-mod-aws-ecs-service + ALB target-group pair
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-orders-service"
compute_platform = "ECS"
service_role_arn = module.ecs_deploy_role.arn # attached to AWSCodeDeployRoleForECS
deployment_groups = {
production = {
ecs_service = {
cluster_name = module.ecs_cluster.name # tf-mod-aws-ecs-cluster
service_name = module.ecs_service.name # tf-mod-aws-ecs-service
}
deployment_style = {
deployment_type = "BLUE_GREEN"
deployment_option = "WITH_TRAFFIC_CONTROL"
}
load_balancer_info = {
target_group_pair_info = {
prod_traffic_route = { listener_arns = [module.alb.https_listener_arn] } # tf-mod-aws-lb
target_group = [
{ name = module.tg_blue.name },
{ name = module.tg_green.name },
]
}
}
blue_green_deployment_config = {
deployment_ready_option = { action_on_timeout = "CONTINUE_DEPLOYMENT" }
terminate_blue_instances_on_deployment_success = {
action = "TERMINATE"
termination_wait_time_in_minutes = 5
}
}
}
}
}5 Β· Lambda deployment group with a custom TimeBasedLinear deployment config
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-invoice-processor"
compute_platform = "Lambda"
service_role_arn = module.lambda_deploy_role.arn # attached to AWSCodeDeployRoleForLambda
deployment_configs = {
"casey-invoice-linear-10-5min" = {
compute_platform = "Lambda"
traffic_routing_config = {
type = "TimeBasedLinear"
time_based_linear = { interval = 5, percentage = 10 }
}
}
}
deployment_groups = {
production = {
deployment_config_name = "casey-invoice-linear-10-5min"
# deployment_style / load_balancer_info are NOT used for Lambda β traffic
# shifting comes entirely from the deployment config above.
}
}
}6 Β· Alarm-based rollback β wiring tf-mod-aws-cloudwatch-alarm
module "high_error_rate" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-cloudwatch-alarm?ref=v1.0.0"
name = "casey-app-5xx-high"
#... metric, threshold, comparison_operator...
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app"
service_role_arn = module.deploy_role.arn
deployment_groups = {
production = {
autoscaling_groups = [module.asg.name]
alarm_configuration = {
alarms = [module.high_error_rate.name] # NAME, not arn β see Architecture Notes
# ignore_poll_alarm_failure defaults to false (fail closed)
}
# DEPLOYMENT_STOP_ON_ALARM is appended to auto_rollback automatically
# because alarm_configuration.alarms is non-empty.
}
}
}7 Β· SNS trigger configuration β wiring tf-mod-aws-sns
module "deploy_notifications" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-sns?ref=v1.0.0"
name = "casey-app-deploy-events"
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app"
service_role_arn = module.deploy_role.arn
deployment_groups = {
production = {
autoscaling_groups = [module.asg.name]
trigger_configuration = [
{
trigger_name = "deploy-failures"
trigger_events = ["DeploymentFailure", "DeploymentRollback"]
trigger_target_arn = module.deploy_notifications.arn
}
]
}
}
}
# NOTE: the SNS topic's resource policy (or the service role) must separately
# allow codedeploy.amazonaws.com / the service role to sns:Publish β grant that
# on module.deploy_notifications, not here.8 Β· Custom Server deployment config with minimum_healthy_hosts and zonal_config
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app"
service_role_arn = module.deploy_role.arn
deployment_configs = {
"casey-zonal-90pct" = {
compute_platform = "Server"
minimum_healthy_hosts = { type = "FLEET_PERCENT", value = 90 } # REQUIRED for Server
zonal_config = {
monitor_duration_in_seconds = 300
first_zone_monitor_duration_in_seconds = 600
minimum_healthy_hosts_per_zone = { type = "FLEET_PERCENT", value = 50 }
}
}
}
deployment_groups = {
production = {
autoscaling_groups = [module.asg.name]
deployment_config_name = "casey-zonal-90pct"
}
}
}9 Β· Tags β merge with provider default_tags
# Caller's provider block owns default_tags; resource tags win on key conflict.
provider "aws" {
region = "us-east-1"
default_tags {
tags = { Owner = "platform", ManagedBy = "terraform" }
}
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app"
service_role_arn = module.deploy_role.arn
tags = {
Environment = "prod"
DataClass = "internal"
}
deployment_groups = {
production = { autoscaling_groups = [module.asg.name] }
}
}
# module.codedeploy.tags_all == { Owner, ManagedBy, Environment, DataClass }
# aws_codedeploy_deployment_config (if any) receives NO tags β no tags argument exists.10 Β· Secure-by-default opt-out β outdated_instances_strategy = IGNORE (documented exception)
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-legacy-batch"
service_role_arn = module.deploy_role.arn
deployment_groups = {
production = {
autoscaling_groups = [module.asg.name]
outdated_instances_strategy = "IGNORE" # opt-out β mid-deployment new instances
# keep the OLD revision instead of the
# current one. EXCEPTION: only for legacy
# batch fleets where mixed-revision
# instances are tolerated; document why in
# the calling root module.
# auto_rollback_enabled is intentionally left at its secure default (true) β
# this opt-out targets outdated-instance handling only, never rollback safety.
}
}
}11 Β· for_each multi-environment deployment-group pattern
locals {
envs = {
dev = { asg = module.asg_dev.name, min_healthy = 0 }
qa = { asg = module.asg_qa.name, min_healthy = 50 }
prod = { asg = module.asg_prod.name, min_healthy = 90 }
}
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app"
service_role_arn = module.deploy_role.arn
deployment_configs = {
for k, v in local.envs : "casey-app-${k}-minhealthy" => {
compute_platform = "Server"
minimum_healthy_hosts = { type = "FLEET_PERCENT", value = v.min_healthy }
}
}
deployment_groups = {
for k, v in local.envs : k => {
autoscaling_groups = [v.asg]
deployment_config_name = "casey-app-${k}-minhealthy"
}
}
}12 Β· Import block β adopting an existing application into Terraform state
import {
to = module.codedeploy.aws_codedeploy_app.this
id = "casey-app-existing"
}
import {
to = module.codedeploy.aws_codedeploy_deployment_group.this["production"]
id = "casey-app-existing:production" # composite id β app_name:deployment_group_name
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-app-existing"
service_role_arn = module.deploy_role.arn
deployment_groups = {
production = { autoscaling_groups = [module.asg.name] }
}
}13 Β· End-to-end composition β IAM role + ECS + ALB + SNS + CloudWatch alarm (finale)
module "ecs_deploy_role" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
name = "casey-orders-codedeploy-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow", Principal = { Service = "codedeploy.amazonaws.com" }, Action = "sts:AssumeRole"
}]
})
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSCodeDeployRoleForECS"]
}
module "ecs_cluster" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-ecs-cluster?ref=v1.0.0"
name = "casey-orders"
}
module "ecs_service" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-ecs-service?ref=v1.0.0"
name = "orders-api"
cluster_arn = module.ecs_cluster.arn
# deployment_controller.type = "CODE_DEPLOY" on the service, per AWS blue/green requirements
}
module "alb" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-lb?ref=v1.0.0"
name = "casey-orders"
vpc_id = module.vpc.vpc_id
subnet_ids = values(module.vpc.public_subnet_ids)
}
module "deploy_notifications" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-sns?ref=v1.0.0"
name = "casey-orders-deploy-events"
}
module "high_error_rate" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-cloudwatch-alarm?ref=v1.0.0"
name = "casey-orders-5xx-high"
}
module "codedeploy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-codedeploy?ref=v1.0.0"
name = "casey-orders-service"
compute_platform = "ECS"
service_role_arn = module.ecs_deploy_role.arn
deployment_groups = {
production = {
ecs_service = {
cluster_name = module.ecs_cluster.name
service_name = module.ecs_service.name
}
deployment_style = {
deployment_type = "BLUE_GREEN"
deployment_option = "WITH_TRAFFIC_CONTROL"
}
load_balancer_info = {
target_group_pair_info = {
prod_traffic_route = { listener_arns = [module.alb.https_listener_arn] }
target_group = [
{ name = module.tg_blue.name },
{ name = module.tg_green.name },
]
}
}
blue_green_deployment_config = {
terminate_blue_instances_on_deployment_success = {
action = "TERMINATE"
termination_wait_time_in_minutes = 5
}
}
trigger_configuration = [{
trigger_name = "deploy-failures"
trigger_events = ["DeploymentFailure", "DeploymentRollback"]
trigger_target_arn = module.deploy_notifications.arn
}]
alarm_configuration = {
alarms = [module.high_error_rate.name]
}
}
}
tags = { Environment = "prod", App = "orders-service" }
}- Core:
name(FORCE-NEW),service_role_arn(REQUIRED βiam:PassRoleon the caller),compute_platform(Server|Lambda|ECS, defaultServer) - Deployment groups:
deployment_groupsβmap(object({ deployment_config_name?, ec2_tag_filter?, ec2_tag_set?, on_premises_instance_tag_filter?, autoscaling_groups?, ecs_service?, load_balancer_info?, trigger_configuration?, alarm_configuration?, auto_rollback_enabled?, auto_rollback_events?, deployment_style?, blue_green_deployment_config?, outdated_instances_strategy?, termination_hook_enabled? })), default{} - Custom deployment configs:
deployment_configsβmap(object({ compute_platform?, minimum_healthy_hosts?, traffic_routing_config?, zonal_config? })), default{} - Universal:
tags(flows to the application and every deployment group; NOT to deployment configs)
- Primary:
id(AWS-assigned application id),arn(application ARN),name,application_id(alias ofid) - Application attributes:
compute_platform,linked_to_github,github_account_name - Deployment groups:
deployment_group_ids(compositeapp:groupstrings),deployment_group_arns,deployment_group_names,deployment_group_unique_ids(AWS-assigned unique id, distinct from the composite id) - Deployment configs:
deployment_config_ids,deployment_config_arns(empty maps whendeployment_configs = {}) - Tags:
tags_all(application only β deployment groups carry their own individually; deployment configs have none)
βΉοΈ No outputs are marked
sensitiveβ this module emits no secrets.
- ARN / ID formats:
- Application
arnβarn:aws:codedeploy:<region>:<account>:application:<name> - Application
idβ an AWS-assigned opaque id string β not the application name (use thenameoutput for that). - Deployment group
idβ the composite string<app_name>:<deployment_group_name>β not an opaque AWS-assigned id. This affectsterraform importsyntax (colon-separated, see Example 12) and any consumer expecting a UUID-style id; usedeployment_group_unique_idsfor the true AWS-assigned identifier. - Deployment group
arnβarn:aws:codedeploy:<region>:<account>:deploymentgroup:<application>/<deployment_group_name>(note the slash, not a second colon, before the group name). - Deployment config
arnβarn:aws:codedeploy:<region>:<account>:deploymentconfig:<deployment_config_name>;idequalsdeployment_config_nameitself. - Force-new / immutable fields:
nameis FORCE-NEW β renaming destroys and recreates the application and, because deployment-group ids embedapp_name, implicitly forces replacement of every attached deployment group.compute_platformis effectively force-new: the provider doesn't mark it explicitly, but changing it after deployment groups exist breaks their argument compatibility and requires destroy/recreate of the app and its children in practice. tagsβtags_allβdefault_tags:var.tagsflows to the application and every deployment group;tags_allon the application reflects the computed merge of resource tags over providerdefault_tags(resource tags win on key conflict).default_tagsremains the caller's provider-block concern.aws_codedeploy_deployment_confighas notags/tags_allat all β deployment configs are not a taggable CodeDeploy resource type;var.tagsis deliberately not wired toaws_codedeploy_deployment_config.this(seemain.tf).alarm_configuration.alarmsis a list of CloudWatch alarm NAMEs, not ARNs. Unlike most cross-resource wiring in this library, passing an ARN here is a silent no-op / invalid reference at the CodeDeploy API level, not a Terraform-time type error β passmodule.alarm.name, nevermodule.alarm.arn.- Eventual consistency on freshly created IAM roles. A
service_role_arncreated in the sameapply(viatf-mod-aws-iam-role) can causeaws_codedeploy_deployment_groupcreation to fail intermittently if CodeDeploy's assume-role check races IAM's eventual-consistency window. Add an explicitdepends_onin the calling root module frommodule.codedeployto the role/policy-attachment resources; this module cannot add that dependency internally since it does not create the role. - Destroy ordering. Deployment groups are removed before (or alongside, via Terraform's automatic dependency graph on
app_name) the parent application. An in-flight deployment blocks deletion of its deployment group until it reaches a terminal state (Succeeded,Failed, orStopped) β aterraform destroymid-deployment will hang or error. - Blue/green
COPY_AUTO_SCALING_GROUPcreates unmanaged infrastructure. See AWS Prerequisites above β preferDISCOVER_EXISTING. - No
us-east-1constraint. CodeDeploy is a standard regional service; no global-resource gotcha applies here.
Secure-by-default posture and the explicit opt-out for each:
| Hardened default | Behavior | Opt-out / control |
|---|---|---|
| Automatic rollback on deployment failure | auto_rollback_configuration { enabled = true, events = ["DEPLOYMENT_FAILURE",...] } on every deployment group |
deployment_groups[*].auto_rollback_enabled = false β requires a documented exception; regulated-FI deployments should not disable failure rollback |
| Alarm-triggered rollback | Enabled automatically (alarm_configuration.enabled = true, DEPLOYMENT_STOP_ON_ALARM appended to rollback events) whenever alarm_configuration.alarms is non-empty |
Omit / empty the alarms list to skip alarm monitoring entirely |
ignore_poll_alarm_failure |
false (fail closed β the deployment stops if CloudWatch alarm status can't be retrieved) |
alarm_configuration.ignore_poll_alarm_failure = true β NOT recommended; document the exception |
outdated_instances_strategy |
"UPDATE" (mid-deployment new EC2 instances receive the current revision, avoiding drift) |
deployment_groups[*].outdated_instances_strategy = "IGNORE" β see Example 10 |
deployment_option traffic control |
Defaults to "WITH_TRAFFIC_CONTROL" automatically whenever load_balancer_info is wired, so instances drain before receiving new code |
Only relevant when deployment_style is used; omitting load_balancer_info naturally yields "WITHOUT_TRAFFIC_CONTROL" |
IAM PassRole scope |
Documented requirement that the caller's principal scope iam:PassRole to the specific service_role_arn, never Resource: "*" |
Not a module opt-out β an IAM-policy authoring requirement on the caller (see Required IAM Permissions) |
Other principles: one application, N deployment groups, N custom deployment configs from a single module call; deployment groups and deployment configs are for_each over map(object(...)) keyed by their own name (no count); IAM role creation, ASG/ECS/LB/SNS/CloudWatch-alarm resources are deliberately out of scope β this module's blast radius is limited to the CodeDeploy application / deployment-group / deployment-config layer itself; AWS-managed deployment config presets are never created as resources, only referenced by name.
terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan # requires valid AWS credentials (profile / SSO / OIDC) + a region
terraform apply
terraform output
β οΈ plan/applyrequire valid AWS credentials and a configured region (provider block /AWS_PROFILE/ SSO / OIDC). Always pin the module source with?ref=v1.0.0, never a branch.
terraform init -backend=false && terraform validateβ schema + reference integrity.terraform fmt -checkβ formatting.terraform planagainst a sandbox account β confirm the compute-platform validation blocks catch a deliberately-wrong field (e.g. setecs_servicewithcompute_platform = "Server"and confirm plan-time failure, not an API error).- Confirm
deployment_group_arnsuses thedeploymentgroup:<app>/<group>ARN shape (slash, not colon) afterapply. - Verify rollback: trigger a deliberately failing deployment revision in a sandbox and confirm the deployment group rolls back automatically per
auto_rollback_configuration. - Destroy test in a throwaway account β confirm an in-flight deployment blocks deletion as documented, then retry after it reaches a terminal state.
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:codedeploy:us-east-1:123456789012:application:casey-app"
id = "d-ABCDEFGHI"
name = "casey-app"
compute_platform = "Server"
deployment_group_ids = { "production" = "casey-app:production" }
deployment_group_arns = { "production" = "arn:aws:codedeploy:us-east-1:123456789012:deploymentgroup:casey-app/production" }
deployment_group_names = { "production" = "production" }
deployment_config_ids = {}
deployment_config_arns = {}
tags_all = { "Environment" = "prod", "App" = "billing-service" }
AccessDeniedException/InvalidRoleExceptionon deployment group create: The Terraform identity is missingiam:PassRoleonservice_role_arn, or the conditioniam:PassedToService = codedeploy.amazonaws.comdoesn't match. Grant it scoped to the exact role ARN β neverResource: "*". See the Required IAM Permissions callout.- Tag drift / unexpected tags on the application: Caused by
default_tagsoverlap.tags_allmerges resource tags over providerdefault_tagswith resource tags winning β set the value explicitly invar.tagsif it differs from expectation. Rememberaws_codedeploy_deployment_confighas no tags at all, so it can never show tag drift. - Credential-chain failures (
NoCredentialProviders/ExpiredToken): No valid credentials resolved. SetAWS_PROFILE, refresh SSO, or confirm OIDC role assumption in CI β this module never takes credentials as variables. - Deployment group creation intermittently fails right after the IAM role is created in the same apply: IAM eventual consistency β CodeDeploy's assume-role check can race a freshly created role/policy attachment. Add
depends_onfrommodule.codedeployto the IAM role/policy-attachment resources in your root module, or re-apply. - Alarm-based rollback never triggers: Confirm you passed the alarm's name, not its arn, to
alarm_configuration.alarmsβ the CodeDeploy API silently treats an ARN as an unmatched name. Also confirmignore_poll_alarm_failureisfalse(the default) if you expect a stop-on-unknown-state behavior. - A blue/green deployment leaves behind an Auto Scaling group after destroy:
green_fleet_provisioning_option.action = "COPY_AUTO_SCALING_GROUP"was used β CodeDeploy creates that ASG outside Terraform state, soterraform destroynever sees it. Switch to"DISCOVER_EXISTING"with a separately Terraform-managed blue/green ASG pair, and manually clean up any already-createdCodeDeploy_*ASGs. DependencyViolation/ hang on destroy: An in-flight deployment is blocking deletion of its deployment group. Stop or wait out the deployment (Succeeded/Failed/Stopped) before retrying destroy.- Plan-time validation error naming a compute-platform mismatch (e.g. "ecs_service is only valid when compute_platform = ECS"): Working as intended β this module's
variables.tfenforces the compute_platform compatibility matrix at plan time rather than letting it surface as a CodeDeploy API error during apply. Fix the field/platform mismatch per the error message. terraform importfails on a deployment group with an "invalid id" error: The import id must be the colon-separated composite stringapp_name:deployment_group_name(see Example 12), not the AWS-assigneddeployment_group_id(usedeployment_group_unique_idsif you need that value for reference).
- Terraform Registry β
hashicorp/awsprovider:aws_codedeploy_app,aws_codedeploy_deployment_group,aws_codedeploy_deployment_config - AWS β AWS CodeDeploy User Guide: primary components, IAM service role setup, deployment configurations, blue/green deployments
- AWS β How AWS CodeDeploy works with IAM (resource ARN formats, managed policies)
- AWS β Integrating CodeDeploy with Amazon EC2 Auto Scaling (blue/green ASG provisioning behavior)
- β
tf-mod-aws-iam-role,tf-mod-aws-autoscaling-group,tf-mod-aws-ecs-cluster,tf-mod-aws-ecs-service,tf-mod-aws-lb,tf-mod-aws-sns,tf-mod-aws-cloudwatch-alarm,tf-mod-aws-codepipeline(sibling modules)
π§‘ "Infrastructure as Code should be standardized, consistent, and secure."