At a glance
ECS container deployment failed silently in production, taking down a critical microservice.
- Identified misconfigured task definition and missing env variables via CloudWatch logs
- Rolled back to the previous stable task revision to restore service immediately
- Fixed the ECS task definition, updated IAM permissions, and deployed with health check validation
Service restored in 35 minutes. Added deployment checklist and automated health checks to prevent recurrence.
The situation
A critical microservice ran on AWS ECS. A deployment went out during business hours and failed silently — the deployment completed, and the service did not come back. Silent is the word doing the work here: nothing in the pipeline objected, so the failure did not announce itself as a deployment failure. It announced itself as an outage. Every consumer of that microservice inherited the failure. [NEEDS INPUT: which downstream services and client-facing features depended on the failed microservice] Business hours means anyone hitting the failure was hitting it in real use, not overnight against a synthetic monitor.
How it surfaced
The deployment finished. The service did not come back, and nothing in the deployment path objected. That gap is the entire character of this incident — the system that should have flagged the failure said nothing, so the first credible signal came from the service being unavailable rather than from the deployment that caused it. [NEEDS INPUT: how the outage was first reported] I treated it as real the moment the symptom and the deploy timestamp lined up. That correlation is not proof, but it is where you start.
What I ruled out, and why
CloudWatch logs were the first place I went, because a container that dies at startup leaves its evidence there and nowhere else. The obvious first suspect in any post-deploy outage is the application code in the new build. I did not chase it, and the reason is in the failure mode: the container was not getting far enough into its own lifecycle to fail on application logic. What the logs showed was a misconfigured task definition and missing environment variables — a container that cannot resolve its configuration never reaches the code you would otherwise blame.
I also eliminated the wider possibilities quickly, which matters under time pressure because each one you carry forward doubles the surface you are searching. A cluster- or account-level AWS problem does not stop at a single service, and this one did. Restarting the task was the other tempting move, and I skipped it: a task launched from a broken revision reproduces the same failure, and I would have spent the minutes learning nothing.
The decision and what it cost
I rolled back to the last stable task revision before I fixed anything. That ordering is the decision, and it costs something specific: the rollback replaced the failing state with a working one, which is also the state I was trying to investigate, and whatever that deployment was meant to deliver went back on the shelf. I accepted losing the live failure environment and the release window because the logs I already had were enough to work from, and because every minute spent proving a fix in production is a minute the service is still down for real users.
What I did
Three moves, in order. CloudWatch logs identified the misconfigured task definition and the missing environment variables — diagnosis first, because rolling back without knowing what broke means redeploying into the same wall later. Then the rollback to the previous stable task revision, which restored service immediately and took the outage off the clock. With production stable, I fixed the task definition, updated the IAM permissions it depended on, and redeployed — this time with health check validation, so the new revision had to prove it was serving before it was allowed to count as deployed.
The outcome
Service restored in 35 minutes. Added deployment checklist and automated health checks to prevent recurrence. [NEEDS INPUT: whether the 35 minutes is measured from the failed deployment or from the first report] [NEEDS INPUT: how the 35 minutes split between diagnosis and remediation] The fix itself was a corrected task definition and a permissions change. What made the recovery fast was deciding early which explanations not to pursue, and being willing to give up the broken environment rather than study it while users waited.
What stayed changed
A deployment that completes without objection while the service is down is a monitoring failure before it is a configuration failure, so the fix had to sit in both places. The deployment checklist catches the configuration class — task definition and environment variables verified before the revision goes out. The automated health checks catch the silence: a revision now has to answer for itself, and a deploy that cannot prove the service is serving is not treated as a successful deploy. Both are permanent.
Related incidents
Production VPS Performance Tuning: From 4.2s to 0.6s Under 3x the Load
A production VPS started dropping requests under moderate traffic. Profiling showed the ceiling was configuration and unindexed queries rather than hardware, and tuning took average response time from 4.2s to 0.6s.
Read the full incidentA Production Outage During a Traffic Spike: 55 Minutes to Restore Service
A marketing campaign drove traffic the app could not absorb and it went partially down. I stood up an incident channel with named owners in ten minutes, protected the critical path by degrading everything else, and ran the client on a 30-minute update cadence — service was restored within 55 minutes.
Read the full incident