Terraform can create CodeDeploy applications and deployment groups, but it cannot start a deployment. With ECS Blue/Green that means changing your task definition in Terraform leaves the live service untouched until somebody manually clicks Deploy. There is no aws_codedeploy_deployment resource and aws_ecs_service with the CODE_DEPLOY controller will not roll out new task revisions on its own.
This Lambda closes the loop:
- Terraform builds the new task definition / AppSpec.
aws_lambda_invocationcalls this Lambda with the AppSpec.- The Lambda creates a CodeDeploy deployment and waits until it Succeeds or Fails.
terraform applyblocks until the deployment finishes — success or non-zero exit.
flowchart LR
TF["terraform apply"] -- "aws_lambda_invocation" --> L["Lambda: codedeploy-trigger"]
L -- "CreateDeployment" --> CD["AWS CodeDeploy"]
CD -- "Blue/Green shift" --> ECS["ECS service"]
L -- "GetDeployment (poll)" --> CD
L -- "JSON result" --> TF
- Synchronous: blocks
terraform applyuntil the deployment reaches a terminal state. - Returns structured JSON (
deploymentId,status, timestamps) — readable fromaws_lambda_invocation.<name>.result. - Optional fire-and-forget mode (
wait: false). - Configurable poll interval (
POLL_INTERVAL_SECONDS). - Forwards every CodeDeploy
CreateDeploymentknob you commonly need: description, auto-rollback, ignore application stop failures. - Single static binary,
provided.al2023runtime, ~5 MB zipped, supportsarm64andx86_64.
The exact pattern this project is designed for:
locals {
appspec = {
version = "0.0"
Resources = [{
TargetService = {
Type = "AWS::ECS::Service"
Properties = {
TaskDefinition = module.ecs_service.task_definition_arn
LoadBalancerInfo = {
ContainerName = local.container_name
ContainerPort = local.container_port
}
PlatformVersion = "LATEST"
}
}
}]
}
}
resource "aws_lambda_invocation" "deploy" {
function_name = var.codedeploy_lambda_function_name
input = jsonencode({
applicationName = module.code_deploy.name
deploymentGroupName = module.code_deploy.name
revision = {
revisionType = "AppSpecContent"
appSpecContent = {
content = jsonencode(local.appspec)
sha256 = sha256(jsonencode(local.appspec))
}
}
})
triggers = {
task_definition_arn = module.ecs_service.task_definition_arn
}
depends_on = [
module.code_deploy,
module.ecs_service,
module.alb_ingress,
]
}
output "deployment_id" {
value = jsondecode(aws_lambda_invocation.deploy.result).deploymentId
}End-to-end runnable examples:
examples/terraform/— provision the Lambda function from a release artifact.examples/terraform-ecs-blue-green/— full ECS Blue/Green flow.
The handler accepts a JSON object whose keys match the AWS CodeDeploy CreateDeployment API in lower-camel-case.
| Field | Type | Required | Description |
|---|---|---|---|
applicationName |
string | yes | CodeDeploy application name. |
deploymentGroupName |
string | yes | CodeDeploy deployment group name. |
revision |
object | yes | A standard CodeDeploy RevisionLocation. For ECS Blue/Green use revisionType: "AppSpecContent". |
description |
string | no | Description attached to the deployment. |
ignoreApplicationStopFailures |
bool | no | Forwarded to CreateDeployment. |
autoRollbackConfiguration |
object | no | CodeDeploy AutoRollbackConfiguration. |
wait |
bool | no | Default true. Set to false to return immediately after CreateDeployment without polling. |
{
"deploymentId": "d-XXXXXXXXX",
"status": "Succeeded",
"createdAt": "2026-05-09T10:00:00Z",
"completedAt": "2026-05-09T10:05:30Z"
}A failed deployment returns the same shape plus a non-empty errorMessage field on the Lambda invocation, which causes aws_lambda_invocation to fail the Terraform run.
| Env var | Default | Description |
|---|---|---|
POLL_INTERVAL_SECONDS |
15 |
Interval between GetDeployment polls. Lower it for faster feedback on small deployments. |
The AWS region is taken from the standard Lambda environment — no extra variables required.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codedeploy:CreateDeployment",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/<codedeploy-service-role>"
}
]
}Plus the standard AWSLambdaBasicExecutionRole for CloudWatch Logs.
- AWS Lambda hard limit of 15 minutes. If your deployment can take longer (large fleets, slow Blue/Green traffic shifts), either tune CodeDeploy
wait_time_in_minutesand the deployment configuration to fit, or invoke the Lambda withwait: falseand poll deployment status from elsewhere. aws_lambda_invocationre-runs on every change to itstriggersmap. Keeptriggersprecise (e.g.task_definition_arn) to avoid spurious deployments.
Each tagged GitHub Release ships two ready-to-use Lambda zip artifacts:
lambda-codedeploy-trigger_<version>_linux_amd64.ziplambda-codedeploy-trigger_<version>_linux_arm64.zip
Pin a version in your Terraform config and download via archive_file, null_resource + curl, or by uploading to S3.
git clone https://github.com/dmitryint/aws-lambda-codedeploy-trigger.git
cd aws-lambda-codedeploy-trigger
make package # builds amd64 + arm64 zips into ./package
make test # runs unit tests with -raceIssues and pull requests are welcome. Please run make test and go vet ./... before opening a PR.
MIT © 2026 Dmitry K.