A deployment pipeline that had worked for a year started failing every third or fourth release with HEALTH_CONSTRAINTS_INVALID, no code changes involved. The actual cause: someone had resized the Auto Scaling Group’s desired capacity down as a cost-saving measure, and CodeDeploy’s in-place deployment configuration required a minimum number of healthy hosts that the new, smaller fleet couldn’t satisfy during a rolling deployment. The deployment logs said “health constraints invalid” — which was completely accurate, and completely unhelpful without knowing to check the ASG capacity against the deployment configuration’s minimum healthy host percentage.
CodeDeploy and CodeBuild failures are usually specific and well-logged — the trick is finding the right log, not the top-level pipeline status.
The Problem
Deployments or builds fail, or worse, hang without clearly failing:
| Symptom | What It Means |
|---|---|
HEALTH_CONSTRAINTS_INVALID |
Not enough healthy instances to satisfy the deployment configuration’s minimum |
Deployment stuck at Install or ValidateService |
Lifecycle hook script failing silently, or the appspec.yml path is wrong |
AccessDenied mid-deployment |
CodeDeploy service role or instance profile missing a required permission |
CodeBuild fails with YAML_FILE_ERROR |
buildspec.yml syntax error, or wrong version at the top of the file |
| Build succeeds locally, fails in CodeBuild | Different runtime/environment version in the build image, or missing environment variables |
| Deployment appears successful but the app doesn’t reflect changes | AfterInstall/ApplicationStart hooks succeeded on stale code due to an S3 revision caching issue |
Both services fail at a specific, named step almost every time — the failure just needs to be read at the right level of detail.
Why Does This Happen?
- Minimum healthy host percentage incompatible with current fleet size: A deployment configuration requiring, say, 75% healthy hosts during a rolling deployment can’t be satisfied if the ASG only has 2 instances and one is being replaced — do the arithmetic on your actual current capacity, not the capacity from when the config was written.
- Lifecycle hook scripts failing without propagating an exit code: A script referenced in
appspec.ymlthat exits 0 despite an internal failure (or silently backgrounds a process and returns immediately) reports success to CodeDeploy even though the actual work failed. - IAM role gaps discovered only when a specific hook runs: The CodeDeploy service role and the EC2 instance profile need distinct permissions — S3 access to fetch the revision, and (for ASG deployments) permissions to interact with Auto Scaling and EC2. A gap often only manifests at the specific lifecycle step that needs the missing permission.
- buildspec.yml version mismatch or indentation errors: CodeBuild’s buildspec YAML is picky about the
versionfield and phase structure — a build tool upgrade that changes buildspec schema expectations, or a stray tab character, producesYAML_FILE_ERRORbefore any build commands even run. - Build environment image doesn’t match local dev environment: A
Dockerfileorbuildspec.ymlreferencing a runtime version (e.g.,node: 18) that differs from what a developer has locally produces “works on my machine” build failures that are actually just legitimate version drift. - S3 revision bucket versioning or eventual consistency: If the deployment revision in S3 was overwritten (not versioned) and something raced the upload, CodeDeploy can fetch a stale or partially-written revision — deployments “succeed” against the wrong artifact.
The Fix
Step 1: Get the Deployment’s Lifecycle Event Details
aws deploy get-deployment \
--deployment-id d-ABCDEF123 \
--query "deploymentInfo.{Status:status,ErrorInfo:errorInformation}"
aws deploy list-deployment-instances \
--deployment-id d-ABCDEF123 \
--query "instancesList"
For a specific failing instance, get the exact hook and its output:
aws deploy get-deployment-instance \
--deployment-id d-ABCDEF123 \
--instance-id i-0abc123def456 \
--query "instanceSummary.lifecycleEvents[*].{Event:lifecycleEventName,Status:status,Diagnostics:diagnostics}"
The diagnostics.logTail field usually contains the actual script output that failed — read this before anything else.
Step 2: Check Deployment Configuration Against Actual Fleet Size
aws deploy get-deployment-config \
--deployment-config-name CodeDeployDefault.OneAtATime \
--query "deploymentConfigInfo.minimumHealthyHosts"
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names orders-app-asg \
--query "AutoScalingGroups[0].{Desired:DesiredCapacity,InService:length(Instances[?LifecycleState=='InService'])}"
If the minimum healthy host requirement can’t mathematically be satisfied by the current fleet size, either scale up before deploying or switch to a percentage-based / HOST_COUNT-based config that fits the fleet.
Step 3: Validate the appspec.yml Against What’s Actually on the Instance
aws s3 cp s3://orders-app-deploy-bucket/revisions/app-latest.zip - | unzip -p - appspec.yml
Confirm the hook scripts referenced actually exist at the listed paths and are executable in the deployed artifact, not just in the source repo.
Step 4: Check the CodeDeploy Service Role and Instance Profile
aws iam list-attached-role-policies \
--role-name CodeDeployServiceRole \
--query "AttachedPolicies[*].PolicyName"
Should include AWSCodeDeployRole (or the ECS/Lambda equivalent). For the instance profile, confirm S3 read access to the revision bucket:
aws iam get-role-policy \
--role-name ec2-codedeploy-instance-role \
--policy-name s3-revision-access
Step 5: Validate buildspec.yml Syntax Before Pushing
python3 -c "import yaml; yaml.safe_load(open('buildspec.yml'))"
Check the CodeBuild project’s configured buildspec override doesn’t conflict with an in-repo buildspec.yml:
aws codebuild batch-get-projects \
--names orders-app-build \
--query "projects[0].source.buildspec"
Step 6: Get the Full CodeBuild Log for the Failing Phase
aws codebuild batch-get-builds \
--ids orders-app-build:abc123-def456 \
--query "builds[0].{Phase:currentPhase,Logs:logs}"
aws logs get-log-events \
--log-group-name /aws/codebuild/orders-app-build \
--log-stream-name abc123-def456 \
--limit 100
Step 7: Confirm the Build Environment Image Matches Expected Runtime Versions
aws codebuild batch-get-projects \
--names orders-app-build \
--query "projects[0].environment.{Image:image,Type:type}"
Pin explicit runtime versions in buildspec.yml’s runtime-versions block rather than relying on the image’s default, so a base image update doesn’t silently shift your build’s Node/Python/Java version:
phases:
install:
runtime-versions:
nodejs: 18
Step 8: Confirm S3 Versioning Is Enabled on the Revision Bucket
aws s3api get-bucket-versioning \
--bucket orders-app-deploy-bucket \
--query "Status"
If not Enabled, turn it on so CodeDeploy deployments can reference an exact revision version rather than “whatever is currently at that key”:
aws s3api put-bucket-versioning \
--bucket orders-app-deploy-bucket \
--versioning-configuration Status=Enabled
Is This Safe?
Yes. All the get, list, and describe calls are read-only, as is validating YAML locally. Enabling S3 bucket versioning is safe and additive — it doesn’t affect existing objects or access patterns, only adds version tracking going forward. Adjusting a deployment configuration or IAM policy takes effect on the next deployment attempt and doesn’t retroactively change an in-flight one; if a deployment is actively stuck, stop it explicitly (aws deploy stop-deployment) before starting a new one to avoid two deployments racing against the same fleet.
Key Takeaway
Both CodeDeploy and CodeBuild log their failures precisely — the fix is almost always in diagnostics.logTail for a lifecycle hook, or the specific CodeBuild phase log, not in the top-level pipeline status. The recurring theme across both is drift: fleet capacity that no longer matches the deployment config’s assumptions, a buildspec schema that no longer matches the build tool’s expectations, or a runtime version that quietly diverged between local and CI. Check what changed around the failure, not just what the error message says.
Have questions or ran into a different CodeDeploy or CodeBuild issue? Connect with me on LinkedIn or X.