Deploy from AWS CodeBuild to ECS, EKS, and Lambda

Deploying from AWS CodeBuild means running deployment commands inside CodeBuild’s post_build phase. After a successful build, the same job that compiled and packaged your application can also push it to the target platform: updating an ECS service, running kubectl against an EKS cluster, or uploading a new package to Lambda.

This is not always the right approach. For simple rolling updates to a single environment it is fast to set up and easy to understand. For production deployments that need canary traffic shifting, automatic rollback on health check failure, or multi-stage environment promotion, a dedicated CodeDeploy stage in CodePipeline is more appropriate. This page covers both paths and the decision framework for choosing between them.

Simple explanation

Think of CodeBuild as a temporary computer that AWS spins up, runs your commands, and then shuts down. Normally those commands compile code or run tests. There is nothing stopping you from also saying “and then update the live application” at the end.

CodeBuild organizes commands into four phases: install, pre_build, build, and post_build. The first three handle setup and building. The post_build phase is where deployment steps go. After a successful build, post_build can run aws ecs update-service, kubectl set image, or aws lambda update-function-code, and the target platform picks up the change.

The mental model is: build artifact or image → success check → deployment command → wait for rollout → done. CodeBuild is just running AWS CLI commands with an IAM role that has permission to update the target service. If you can run those commands from your laptop with the right credentials, you can run them from CodeBuild.

🔨

Think of it like a contractor who builds a shelf and installs it in one visit. That works fine for a single shelf in your home. But if you are renovating an entire building with safety inspections and sign-offs required between each floor, you bring in a project manager to coordinate. CodeBuild is the contractor. CodePipeline with CodeDeploy is the project manager.

How deploying from CodeBuild works

CodeBuild executes phases in order. Each phase runs to completion before the next starts. If a phase fails, subsequent phases are skipped — except post_build, which always runs. This is why a deployment guard is required in every post_build that contains deployment steps.

PhaseWhat happensDeployment relevance
installRuntimes and tools installedInstall kubectl here for EKS deployments
pre_buildRegistry login, variable setupECR login, kubeconfig setup, commit hash derived
buildCode compiled, image built, tests runThis is where build success or failure is determined
post_buildDeployment commands runAlways runs. Must check CODEBUILD_BUILD_SUCCEEDING first

After all phases complete, CodeBuild marks the build as succeeded or failed based on exit codes. If the post_build deployment step exits non-zero — for example, because kubectl rollout status reports a failed rollout — the overall build is marked failed. This means the build result in CodeBuild accurately reflects whether the deployment succeeded or not.

When to use this

Direct deployment from CodeBuild is a good fit when:

  • You are doing a simple rolling update to a single ECS service with no traffic shifting or automatic rollback needed.
  • You are running a straightforward kubectl rollout on an EKS cluster where a direct image update is all that is needed.
  • You are updating a Lambda function where all-at-once deployment is acceptable and you are not using aliases for traffic splitting.
  • You are deploying to dev or staging where a full CodePipeline setup is not yet justified.
  • Your team is new to CI/CD and wants to keep the setup simple before adding CodePipeline and CodeDeploy complexity.
Not sure which compute platform to use?

Before building deployment steps, see Choosing Between Lambda, ECS, and EC2 to confirm which platform fits your workload.

When not to use this

Do not deploy directly from CodeBuild when:

  • You need canary or blue/green traffic shifting. CodeBuild cannot shift 10% of traffic to a new ECS task set, wait, and then shift the rest. That is what CodeDeploy is for. See Canary Deployments and Blue/Green Deployments for those patterns.
  • You need automatic rollback on a CloudWatch alarm. CodeBuild cannot watch for a spike in 5xx errors post-deployment and roll back automatically. CodeDeploy with a deployment group and alarm configuration can.
  • You need a manual approval gate before production. A human approval step before the production deployment requires CodePipeline. CodeBuild is a single job that runs start to finish without pausing for a reviewer.
  • You are promoting code through multiple environments. If deployment should move through dev, staging, and production with different approval rules per stage, use CodePipeline with environment stages.
Production deployments need more than a shell command

If your application serves real users, a deployment failure requiring manual intervention is a serious event. CodeDeploy gives you traffic shifting, alarm-triggered rollback, and deployment event history that a post_build shell script cannot match. For anything beyond a simple rolling update in production, invest in the extra setup.

CodeBuild vs CodeDeploy vs CodePipeline

These three tools serve different purposes and work best together. Beginners often confuse them because all three appear in deployment discussions.

ToolWhat it doesUse it when
CodeBuildRuns build commands in a managed container: compile, test, package, push imageBuilding artifacts, running tests, simple CLI-based deployments
CodeDeployManages the deployment rollout to EC2, ECS, or Lambda with traffic controlCanary and blue/green shifts, automatic rollback on alarm, lifecycle hooks
CodePipelineOrchestrates the full CI/CD workflow end to end across stagesMulti-stage pipelines with source, build, test, approval, and deploy stages

The most common production pattern is: CodeBuild builds the Docker image and produces an artifact file, CodePipeline passes that artifact through stages, and CodeDeploy manages the actual rollout with traffic shifting. This page covers the simpler path where CodeBuild handles the deployment itself, and when that is enough.

Before you start

Before adding deployment steps to your buildspec, make sure these are in place:

  • buildspec.yml knowledge. Understand the four phases and how environment variables work. See AWS CodeBuild Overview if this is new to you.
  • Target platform already exists. The ECS service, EKS cluster, or Lambda function must already be running. CodeBuild updates them; it does not create them.
  • IAM role permissions configured. The CodeBuild service role must have the correct permissions for the deployment target. See the IAM section below.
  • Image registry ready for ECS and EKS. Container deployments require an ECR repository and a working Docker build step. See Building Docker Images with CodeBuild.
  • Environment separation in place. Use separate CodeBuild projects or branch conditions for dev and production. A single project deploying everywhere on every push is a misconfiguration. See Dev vs Staging vs Production.
  • Secrets handled correctly. Deployment credentials must not appear in the buildspec file. Use SSM Parameter Store or Secrets Manager references. See Secrets in CI/CD Pipelines.

Guarding deployment on build success

post_build runs even after a failed build

Without a guard, a compilation error or failed test run will still trigger deployment. One bad push could push a broken artifact to your live service. Always check CODEBUILD_BUILD_SUCCEEDING at the top of every deployment step.

CODEBUILD_BUILD_SUCCEEDING is set to 1 when all previous phases succeeded, and 0 when any phase has failed. Add this check as the first command in post_build:

post_build:
  commands:
    - |
      if [ "$CODEBUILD_BUILD_SUCCEEDING" -ne 1 ]; then
        echo "Build failed. Skipping deployment."
        exit 0
      fi
    - echo "Build succeeded. Proceeding with deployment..."
    # deployment commands follow

This pattern appears at the start of every deployment example on this page. It is the single most important line in any deployment buildspec.

Deploying to Amazon ECS

An ECS deployment from CodeBuild updates a service to run a new Docker image. The process has three steps: register a new task definition revision pointing to the updated image, call update-service to apply it, and wait until the service stabilizes. For a full end-to-end pipeline with CodePipeline stages and the imagedefinitions.json artifact, see CI/CD Pipelines for Amazon ECS.

version: 0.2

env:
  variables:
    AWS_DEFAULT_REGION: "us-east-1"
    ECR_REPO_NAME: "my-app"
    ECS_CLUSTER_NAME: "my-cluster"
    ECS_SERVICE_NAME: "my-app-service"
    TASK_FAMILY: "my-app"

phases:
  pre_build:
    commands:
      - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
      - ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com"
      - IMAGE_URI="${ECR_REGISTRY}/${ECR_REPO_NAME}"
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-8)
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY

  build:
    commands:
      - docker build -t $IMAGE_URI:$COMMIT_HASH .
      - docker push $IMAGE_URI:$COMMIT_HASH

  post_build:
    commands:
      - |
        if [ "$CODEBUILD_BUILD_SUCCEEDING" -ne 1 ]; then
          echo "Build failed. Skipping deployment."
          exit 0
        fi

      # Register a new task definition revision with the updated image
      - TASK_DEF=$(aws ecs describe-task-definition --task-definition $TASK_FAMILY --query taskDefinition)
      - NEW_TASK_DEF=$(echo $TASK_DEF | python3 -c "
          import json, sys
          td = json.load(sys.stdin)
          td['containerDefinitions'][0]['image'] = '${IMAGE_URI}:${COMMIT_HASH}'
          for key in ['taskDefinitionArn','revision','status','requiresAttributes','compatibilities','registeredAt','registeredBy']:
              td.pop(key, None)
          print(json.dumps(td))
        ")
      - NEW_TASK_DEF_ARN=$(aws ecs register-task-definition --cli-input-json "$NEW_TASK_DEF" --query 'taskDefinition.taskDefinitionArn' --output text)

      # Update the service and wait for stability
      - aws ecs update-service --cluster $ECS_CLUSTER_NAME --service $ECS_SERVICE_NAME --task-definition $NEW_TASK_DEF_ARN --force-new-deployment
      - aws ecs wait services-stable --cluster $ECS_CLUSTER_NAME --services $ECS_SERVICE_NAME

      - echo "ECS deployment complete"

What aws ecs wait services-stable does: It polls the service until all running tasks use the new task definition and pass their health checks, or until a 10-minute timeout. Without this wait, CodeBuild exits immediately after calling update-service and reports success even if new tasks are crashing on startup.

Most common failure point: Missing iam:PassRole on the CodeBuild service role. Registering a new task definition requires the caller to pass the task execution role to ECS. Without this permission, register-task-definition fails with an access denied error that references the task role ARN rather than naming the missing action directly. See Fixing IAM AccessDenied Errors if you hit this.

Deploying to Amazon EKS

EKS deployments from CodeBuild use kubectl, which must be installed during the install phase. The CodeBuild service role also needs access to the cluster through IAM (to generate a kubeconfig) and through Kubernetes RBAC (to apply changes inside the cluster). For a comparison of ECS and EKS as deployment targets, see EKS vs ECS.

phases:
  install:
    commands:
      # Install kubectl
      - curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
      - chmod +x kubectl && mv kubectl /usr/local/bin/

  pre_build:
    commands:
      # Generate kubeconfig for the EKS cluster
      - aws eks update-kubeconfig --name my-eks-cluster --region us-east-1
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-8)

  build:
    commands:
      - docker build -t $IMAGE_URI:$COMMIT_HASH .
      - docker push $IMAGE_URI:$COMMIT_HASH

  post_build:
    commands:
      - |
        if [ "$CODEBUILD_BUILD_SUCCEEDING" -ne 1 ]; then exit 0; fi

      # Update the deployment image and wait for rollout
      - kubectl set image deployment/my-app my-app=$IMAGE_URI:$COMMIT_HASH -n production
      - kubectl rollout status deployment/my-app -n production --timeout=5m

Granting the CodeBuild role in-cluster access

Running aws eks update-kubeconfig generates a kubeconfig but does not grant Kubernetes RBAC permissions inside the cluster. The CodeBuild role must be authorized separately. How you do that depends on how your cluster was set up.

Access entries vs aws-auth: which should you use?

EKS access entries are the current recommended method for clusters on platform version 1.28 or newer. They manage Kubernetes authorization through the AWS API with no ConfigMap edits needed. The aws-auth ConfigMap approach is still supported for older clusters and migration scenarios, but it requires manual YAML edits and carries a risk of locking yourself out of the cluster if misconfigured.

Current recommendation: EKS access entries

Use aws eks create-access-entry to register the CodeBuild role, then attach a scoped access policy:

# Register the CodeBuild service role as a cluster principal
aws eks create-access-entry \
  --cluster-name my-eks-cluster \
  --principal-arn arn:aws:iam::123456789012:role/CodeBuildDeployRole

# Attach edit access scoped to the production namespace only
aws eks associate-access-policy \
  --cluster-name my-eks-cluster \
  --principal-arn arn:aws:iam::123456789012:role/CodeBuildDeployRole \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicy \
  --access-scope type=namespace,namespaces=production

AWS provides four managed access policies. AmazonEKSEditPolicy scoped to a specific namespace is appropriate for a deploy role. Avoid AmazonEKSClusterAdminPolicy on a build role unless the cluster is development-only.

Legacy approach: aws-auth ConfigMap

For clusters that have not migrated to access entries, add the CodeBuild role to the aws-auth ConfigMap in the kube-system namespace:

# Download the current ConfigMap
kubectl get configmap aws-auth -n kube-system -o yaml > aws-auth.yaml

# Add the CodeBuild role to mapRoles, then apply
# - rolearn: arn:aws:iam::123456789012:role/CodeBuildDeployRole
#   username: codebuild
#   groups:
#     - system:deployers  # a custom RBAC group, not system:masters

kubectl apply -f aws-auth.yaml

Most common failure point: kubectl returns Unauthorized. The kubeconfig was generated successfully (IAM permission worked) but the CodeBuild role has not been granted in-cluster access yet. See Troubleshooting Authentication Errors for step-by-step diagnosis.

Deploying to AWS Lambda

Lambda deployments are the simplest of the three: package the function code, upload it to S3, and call aws lambda update-function-code. Lambda updates are atomic. The new code is staged and then activated in a single step with no rolling update or task replacement involved.

version: 0.2

env:
  variables:
    LAMBDA_FUNCTION_NAME: "my-function"
    AWS_DEFAULT_REGION: "us-east-1"
    ARTIFACT_BUCKET: "my-artifacts-bucket"

phases:
  install:
    runtime-versions:
      python: 3.12

  build:
    commands:
      - pip install -r requirements.txt -t build/
      - cp -r src/ build/
      - cd build && zip -r ../function.zip . && cd ..

  post_build:
    commands:
      - |
        if [ "$CODEBUILD_BUILD_SUCCEEDING" -ne 1 ]; then exit 0; fi

      # Upload to S3 first (recommended for packages over 10 MB)
      - aws s3 cp function.zip s3://${ARTIFACT_BUCKET}/lambda/${CODEBUILD_RESOLVED_SOURCE_VERSION}/function.zip

      # Update the function code from S3
      - aws lambda update-function-code \
          --function-name $LAMBDA_FUNCTION_NAME \
          --s3-bucket $ARTIFACT_BUCKET \
          --s3-key lambda/$CODEBUILD_RESOLVED_SOURCE_VERSION/function.zip

      # Publish a version (required if you use Lambda aliases for traffic splitting)
      - aws lambda publish-version --function-name $LAMBDA_FUNCTION_NAME

      - echo "Lambda deployment complete"

What publish-version does: It creates an immutable snapshot of the function code at a specific version number. If you use Lambda aliases (for example, a production alias that points to a specific version) you must publish a version first and then update the alias to point to it. If you are not using aliases, this step is optional.

Most common failure point: Missing lambda:UpdateFunctionCode on the CodeBuild service role, or the S3 artifact bucket being in a different region than the Lambda function. The update-function-code call will fail with an access denied error or a region mismatch.

Using CodeBuild with CodeDeploy

When a simple rolling update is not enough — you need canary traffic shifting or automatic rollback on a CloudWatch alarm — use CodeBuild for the build step and CodeDeploy for the rollout, connected through CodePipeline. CodeBuild’s job in this pattern is to produce the artifact file that CodeDeploy expects.

# CodeBuild post_build: produce artifacts for CodeDeploy
post_build:
  commands:
    - |
      if [ "$CODEBUILD_BUILD_SUCCEEDING" -ne 1 ]; then exit 0; fi

    # imageDetail.json is used by ECS blue/green via CodeDeploy
    - printf '{"ImageURI":"%s"}' $IMAGE_URI:$COMMIT_HASH > imageDetail.json

    # imagedefinitions.json is used by the ECS rolling update deploy action
    - printf '[{"name":"my-app","imageUri":"%s"}]' $IMAGE_URI:$COMMIT_HASH > imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json
    - imageDetail.json
    - appspec.yml
    - taskdef.json

CodeDeploy picks up those artifacts and manages the traffic shift. CodeBuild does no deployment work itself in this pattern. For how CodeDeploy handles rollback when something goes wrong, see Rollbacks in CodeDeploy.

IAM permissions per deployment target

Each deployment target requires different IAM permissions on the CodeBuild service role. Scope permissions to specific resource ARNs rather than * to follow least privilege. For broader guidance on hardening the CodeBuild role, see Secure CI/CD Pipelines.

TargetRequired IAM actionsNotes
ECSecs:DescribeTaskDefinition, ecs:RegisterTaskDefinition, ecs:UpdateService, ecs:DescribeServices, iam:PassRoleiam:PassRole must be scoped to the task execution role and task role ARNs
EKSeks:DescribeClusterIn-cluster RBAC access granted separately via access entries or aws-auth
Lambdalambda:UpdateFunctionCode, lambda:PublishVersion, lambda:UpdateAlias, s3:PutObjects3:PutObject scoped to the artifact bucket path
CodeDeploy (artifact producer)codedeploy:CreateDeployment, codedeploy:GetDeploymentConfig, codedeploy:RegisterApplicationRevisionOnly needed if CodeBuild triggers a CodeDeploy deployment directly
iam:PassRole for ECS in plain English

When you register a new ECS task definition, you are telling ECS “run containers with this IAM role.” AWS requires the caller to have explicit permission to hand that role to another service. Without iam:PassRole scoped to the task execution role and task role ARNs, register-task-definition fails with an access denied error. The error message references the task role ARN, which can make it confusing to diagnose if you are not expecting it.

Common beginner mistakes

  1. Deploying in post_build without checking build success. The post_build phase runs even when build fails. Without checking CODEBUILD_BUILD_SUCCEEDING, a failed test run will still push a broken artifact to your live service. Always add the guard as the first step.
  2. Not waiting for rollout completion. Calling aws ecs update-service or kubectl apply and immediately exiting means the build reports success while new tasks are still starting. A deployment where new tasks crash on startup goes completely undetected. Always follow with aws ecs wait services-stable or kubectl rollout status.
  3. Missing iam:PassRole for ECS task definitions. The error message references the task role ARN and does not name iam:PassRole directly, making it harder to diagnose. If ECS task definition registration is failing with an access denied error, this is the first thing to check.
  4. Mixing environments in one project. A single CodeBuild project that deploys to production on every push to any branch is a serious misconfiguration. Use separate projects or explicit branch conditions for dev and production. See Dev vs Staging vs Production for how to structure this.
  5. Weak secret handling. Database passwords and API keys must not appear in buildspec.yml or as plain-text environment variables in the CodeBuild project console. Use SSM Parameter Store or Secrets Manager references. A buildspec committed to a public repository is a leaked credential.
  6. Using CodeBuild direct deployment in production when you actually need rollback. A post_build deployment has no built-in rollback trigger. If new ECS tasks fail health checks after the wait timeout, you must manually roll back by deploying the previous task definition revision. If automatic rollback matters, use CodeDeploy.

Frequently asked questions

Can AWS CodeBuild deploy directly to ECS, EKS, and Lambda?

Yes. CodeBuild can run AWS CLI commands in its post_build phase to update an ECS service, apply Kubernetes manifests to an EKS cluster, or upload new code to a Lambda function. This works well for simple, single-environment deployments. For multi-stage rollouts, canary traffic shifting, or automatic rollback, a dedicated CodeDeploy stage in CodePipeline is the better approach.

Should I deploy from CodeBuild or use CodeDeploy instead?

Use CodeBuild deployment for simple cases: rolling updates to a single ECS service, kubectl apply workflows for EKS, or Lambda zip updates where traffic shifting is not needed. Use CodeDeploy when you need canary or blue/green traffic shifting, automatic rollback on a CloudWatch alarm, or per-deployment approval gates. The two are not mutually exclusive. CodeBuild builds the artifact and CodeDeploy manages the rollout.

Why can post_build still run after a failed build?

CodeBuild runs the post_build phase regardless of whether the build phase succeeded or failed. This is intentional because post_build is designed for cleanup, artifact uploads, and notifications that should always run. For deployments this behavior is dangerous: a failed build will still trigger deployment unless you explicitly check the CODEBUILD_BUILD_SUCCEEDING environment variable and exit early when it equals 0.

How does CodeBuild authenticate to an EKS cluster?

The CodeBuild service role needs access configured in two places. First, eks:DescribeCluster in IAM so it can generate a kubeconfig via aws eks update-kubeconfig. Second, the CodeBuild role must be granted Kubernetes RBAC access inside the cluster. On clusters running platform version 1.28 or newer, use EKS access entries via aws eks create-access-entry and aws eks associate-access-policy. For older clusters still using ConfigMap-based auth, add the CodeBuild role ARN to the aws-auth ConfigMap mapRoles section.

What IAM permissions does CodeBuild need to deploy?

Permissions vary by target. For ECS: ecs:DescribeTaskDefinition, ecs:RegisterTaskDefinition, ecs:UpdateService, ecs:DescribeServices, and iam:PassRole on the task execution role. For EKS: eks:DescribeCluster plus in-cluster RBAC access via access entries. For Lambda: lambda:UpdateFunctionCode, lambda:PublishVersion, and s3:PutObject for the artifact bucket. Scope all permissions to specific resource ARNs, not wildcards.

Last verified: 25 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.