
When we measured a full ECS blue/green deployment in the first post of this series, one number stood out: 28 seconds. That’s how long the new revision sits on the test listener before production traffic shifts to it, and it’s the only period in which a smoke test could stop a bad release before users see it. No realistic test suite fits in that time. Pause hooks are the fix. Announced in May 2026, they make the deployment stop at a stage you choose and wait for you, turning those 28 seconds into a smoke test phase of whatever length you need. Third-party coverage of the feature is still close to zero, so we measured the whole workflow on our test stack: the continue path, the rollback path, an alarm firing mid-pause, and one case where the hook is accepted and then does nothing at all. Every number below comes from a measured run (July 2026, eu-west-1).
How a pause hook works
A pause hook is a
deployment lifecycle hook
with nothing behind it. Its sibling, the Lambda hook, invokes a function you wrote. The pause
hook just stops (similar to CloudFormation WaitCondition). When the deployment reaches a stage you named,
ECS generates a hookId, emits
an EventBridge event, and waits until you call continue-service-deployment with an answer:
CONTINUE or ROLLBACK. If you never answer, a timeout action fires instead.
In CloudFormation, it’s a small block inside the DeploymentConfiguration we set up in the
previous post:
1WebService:
2 Type: AWS::ECS::Service
3 Properties:
4 # service properties as before
5 DeploymentConfiguration:
6 Strategy: BLUE_GREEN
7 BakeTimeInMinutes: 10
8 LifecycleHooks:
9 # PAUSE hooks need no Lambda function and no role:
10 # ECS stops and waits for continue-service-deployment
11 - TargetType: PAUSE
12 LifecycleStages: [POST_TEST_TRAFFIC_SHIFT]
13 TimeoutConfiguration:
14 Action: ROLLBACK
15 TimeoutInMinutes: 15Pause hooks can attach to six of the eleven lifecycle stages from the previous post:
RECONCILE_SERVICE, PRE_SCALE_UP, POST_SCALE_UP, POST_TEST_TRAFFIC_SHIFT,
PRE_PRODUCTION_TRAFFIC_SHIFT and POST_PRODUCTION_TRAFFIC_SHIFT. The two actual
traffic-shift stages are barred (so you can only stop before or after traffic shifts, not while it happens), and the
documentation
gives a sensible reason: those stages also run during a rollback, and a pause during a rollback
would need yet another continue call to finish undoing things. For a pre-production smoke test,
the stage you want is POST_TEST_TRAFFIC_SHIFT: the new revision is serving the test listener,
production is still on the old one, and nothing shifts until you say so.
Three settings worth knowing before you copy that snippet:
- The timeout defaults to 24 hours and accepts anything from 1 minute to 14 days. We set 15 minutes: long enough for a smoke suite, short enough that an abandoned deployment cleans itself up within the working hour.
- The timeout action defaults to
ROLLBACK(you can setCONTINUEinstead, if you’re only testing your… tests). We never sat through a timeout expiry, but every probe of a paused deployment showed the live deadline and theROLLBACKaction armed, and the rollback it performs is the same one we trigger by hand below. - CloudFormation caps the whole thing at 36 hours. The CloudFormation reference documents a 36-hour handler timeout for blue/green deployments, so a 14-day pause is only meaningful for deployments driven directly through the API. Driven through a stack update, the deployment has 36 hours in total, pause included.
The continue path, measured
Here’s a clean deployment through the hook, driven by a stack update, with a 1-minute bake to keep the run short. Times are elapsed from the moment the update was issued:
| Elapsed | What happened |
|---|---|
| 00:00 | Stack update issued |
| 00:19 | ECS deployment starts |
| 01:51 | Test listener starts serving the green revision |
| 02:03 | Deployment pauses at POST_TEST_TRAFFIC_SHIFT (smoke tests happen here) |
| 03:06 | Production shifts to green, seconds after we answer CONTINUE |
| 04:20 | Blue tasks stopped - the 1-minute bake is over |
| 05:00 | ECS marks the deployment successful |
| 06:07 | Stack reaches UPDATE_COMPLETE |
The pause engaged twelve seconds after the test listener flipped to green. From that moment the
deployment is simply parked, and everything you need to act on it is readable from
describe-service-deployments (trimmed to the interesting fields):
1{
2 "status": "IN_PROGRESS",
3 "lifecycleStage": "POST_TEST_TRAFFIC_SHIFT",
4 "lifecycleHookDetails": [
5 {
6 "hookId": "ecs-pause-FpUiWKVIQVe8LX4Xl...",
7 "targetType": "PAUSE",
8 "status": "AWAITING_ACTION",
9 "expiresAt": "2026-07-24T08:49:24Z",
10 "timeoutAction": "ROLLBACK"
11 }
12 ]
13}Your pipeline polls for AWAITING_ACTION, runs its smoke suite against the test listener, and
answers:
aws ecs continue-service-deployment \
--service-deployment-arn arn:aws:ecs:eu-west-1:123456789012:service-deployment/ecs-deploy-lab/web/T-TBjYq00... \
--hook-id ecs-pause-FpUiWKVIQVe8LX4Xl... \
--action CONTINUEProduction shifted within seconds of the answer. The whole deployment took 6 minutes and 7 seconds including the minute we held the pause, against roughly five minutes for the same flip without a hook. In other words, the hook’s cost is exactly the pause you take.
The split state is stable, too. In another run we held the pause for 12 minutes and answered
from the console instead of the CLI: green served the test listener and blue served production
for the entire wait, 970 requests on port 80 and 964 on port 8080, every one answered with a
200. Both answer paths work, and CloudFormation is untroubled by either: the service resource
simply holds UPDATE_IN_PROGRESS until the deployment finishes.
One planning consequence from the previous post applies here with more force: a paused deployment holds the stack’s deployment lock, so a long pause also blocks the next stack update.
The rollback path: a failed smoke test
The other answer is the whole point of the feature. Same flip again, but this time we answered
the pause with ROLLBACK, simulating a smoke suite that found a problem:
| Elapsed | What happened |
|---|---|
| 00:00 | Stack update issued |
| 01:48 | Deployment pauses, green revision on the test listener |
| 02:11 | We answer ROLLBACK (23 seconds into the pause) |
| 02:20 | ECS declares the deployment failed, 9 seconds after the answer |
| 02:58 | Test listener rule back on blue |
| 03:19 | Green tasks stopped |
| 04:31 | Old deployment reports a steady state |
| 04:33 | CloudFormation fails the service resource, stack rollback starts |
| 05:16 | Stack reaches UPDATE_ROLLBACK_COMPLETE |
Production never left blue: all 340 requests on port 80 during the run returned 200 from the old revision. The green revision existed for 84 seconds on the test listener and nowhere else. Just over three minutes after the answer, the stack was back in a deployable state with its parameters restored. That’s the contract you want from a failed smoke test: users never saw the release, and the pipeline can try again minutes later.
Of all the ways an ECS deployment can fail, this one has the best diagnostics. The service event names both the stage and the hook:
Service deployment rolled back because POST_TEST_TRAFFIC_SHIFT lifecycle hook(s) failed.
Hook ecs-pause-pNtGfdCBSjGV... returned FAILED status.Two details in that message are worth noting. First, an operator answering ROLLBACK is
recorded as the hook “returning FAILED”, the same wording a script-driven rollback produces, so
the record can’t tell you whether a human or your smoke suite made the call (more on that
below).
Second, compare it with the other failure detectors we’ll cover later in this series: alarms
report “one or more active alarms” without naming the alarm, and the circuit breaker reports
“tasks failed to start” whether or not that’s what happened. The hook is the only one that
tells you where and what.
One flag deserves special care in your tooling. On continue-service-deployment, the
--action flag is optional, and
when omitted it defaults to CONTINUE.
A script that answers a hook without passing the flag ships the release. Note that this is the
opposite of the hook’s timeout default: an ignored hook rolls back, a lazily answered one
continues. Make ROLLBACK explicit, always.
Four things the documentation does not tell you
An alarm can end the pause for you
The pause window is not exempt from deployment alarm monitoring. We deployed a revision that
fails half of its real requests (health checks stay green), let it pause at the hook, and sent
traffic at the test listener, as a smoke suite would. The 5xx alarm attached to the service
fired about 3 minutes after the errors started. Six seconds later, ECS failed the paused
deployment (“deployment failed: alarm detected”) and rolled everything back without waiting for
anyone to answer the hook. The run took 8 minutes and 6 seconds from update to
UPDATE_ROLLBACK_COMPLETE, production served the old revision throughout, and the entire
incident lived on the test listener.
This is good news, with one operational consequence. Good, because real smoke traffic against
a bad release trips the alarm exactly where you want it: before production ever shifts. The
consequence: your smoke-test tooling must tolerate the hook vanishing mid-wait. A continue call sent after
the rollback has begun fails harmlessly (“no hook awaiting action”), and the cause lives in the deployment record’s
statusReason, which mentions only the alarm, not the hook.
Once answered, the hook vanishes from the record
The hook statuses in the API model include SUCCEEDED, FAILED and TIMED_OUT, which
suggests the deployment record keeps a final entry for each hook. In every run we observed
(July 2026), it does not: the moment a hook is answered or preempted, lifecycleHookDetails
comes back as an empty array. No terminal status, no record of who answered. If you need an
audit trail of hook decisions (and for production approval flows, you do), it lives only in
CloudTrail, as ContinueServiceDeployment events.
The console does not refresh the hook state
After a CLI continue call, the ECS console kept showing the deployment as paused until we reloaded the page (hitting the little “refresh” icon did nothing). Refresh the browser page before concluding anything about a paused deployment, and especially before answering a hook that a colleague or a pipeline may have answered already.
A too-old CLI fails silently
Our first attempt at this test ran with AWS CLI 2.34.38 (an April 2026 build, predating the
pause-hook API). The deployment sat visibly paused in the console, while
describe-service-deployments from that CLI returned no lifecycleHookDetails key at all, and
even the hook’s configuration echo came back stripped of its type and timeout. No error, no
warning: the CLI silently drops response fields its bundled API model doesn’t know about. A
pipeline built on that CLI would wait forever for a pause it can never see. Pin a minimum
CLI and SDK version wherever your deployment tooling runs, because on this workflow, too-old
clients don’t fail loudly. They fail invisibly.
Where the hooks do nothing: rolling deployments
The announcement
says: “You can configure pause hooks for rolling, blue/green, linear, and canary deployment
strategies”. The developer guide’s hook pages are titled and framed without reference to any
strategy, though every example in them says "strategy": "BLUE_GREEN", and no page about
rolling deployments mentions hooks at all. The announcement is the only place rolling is named.
So we tested it.
We attached a pause hook to the
worker service - rolling strategy, no load balancer - at POST_SCALE_UP, the natural stage for
a service that has no traffic to shift, and ran a normal image flip.
| Elapsed | What happened |
|---|---|
| 00:00 | Stack update issued (image tag flip on the worker service) |
| 00:22 | First new task started |
| 00:43 | Second new task started (old pair still running) |
| 01:23 | First old task stopped |
| 01:42 | Second old task stopped |
| 04:15 | Service reaches steady state, deployment record marked SUCCESSFUL |
| 04:36 | Stack reaches UPDATE_COMPLETE |
Nothing paused. There’s a window in that table (both new tasks up, old pair still running)
where a POST_SCALE_UP pause could plausibly hold, but we can’t tell you what the stage means
on a rolling service, because the hook never engaged. The deployment ran straight through in 4
minutes and 36 seconds, identical to the same flip without a hook: no pause, no hook events,
and lifecycleHookDetails empty on the record.
The configuration side, meanwhile, accepted everything. CloudFormation validated the template, the change itself was a quiet 47-second configuration update (no deployment, no task replaced, the same behaviour we saw in the previous post), and the hook sits stored in the service’s deployment configuration. The console makes the state easy to miss: if you try to modify the service, the form hides the hook fields while the rolling strategy is selected, but shows the stored hook if you switch the strategy selector to blue/green. Configured everywhere, visible if you know where to look, and silently inert.
Read the announcement’s sentence again: it says you can configure pause hooks for rolling,
and configuring is precisely what worked. What we could not observe is the hook doing anything.
To state the scope exactly: this is one run, at one stage (POST_SCALE_UP), on one service
configuration (rolling, no load balancer), in eu-west-1, in July 2026. Other stages or configurations may
behave differently, and the behaviour may well change, so if your pipeline depends on a hook
holding a rolling deployment, test your own combination of stage and service configuration before trusting it.
What we can say from our runs: nothing (not CloudFormation, not the API, not the console, not
the deployment record) warned us that the hook we configured would not run. If your pipeline
assumes a pause hook holds rolling deployments for a smoke test, it currently holds nothing.
Until that changes, a template review rule is the honest fix: pause hooks belong on blue/green
services only.
Notes for your pipeline
- You can subscribe instead of polling. Pause hooks emit EventBridge events with the
detail-type
ECS Hook State Change:HOOK_AWAITING_ACTIONwhen the pause engages, thenHOOK_SUCCEEDED,HOOK_FAILEDorHOOK_TIMED_OUT. A rule matchingHOOK_AWAITING_ACTIONcan trigger the smoke suite the moment the deployment parks. - A service can carry up to 10 pause hooks, and hooks at the same stage run in parallel:
the deployment stays paused until every one of them is continued, and any single
ROLLBACKanswer rolls the whole deployment back. - Budget the pause into your stack timings. The pause holds the CloudFormation deployment lock, and the 36-hour handler timeout is the hard ceiling.
What you get
The 28-second window from the first post was the gap in an otherwise convincing process: green tasks reachable, production untouched, and no time to check anything. A pause hook closes it properly. The deployment stops with the new revision on the test listener for as long as your smoke suite needs, a passing suite costs you only the time it takes to run, and a failing one rolls everything back in about three minutes with production never having served the release. The weak points are all operational: vanished hook records, a console that needs refreshing, old CLIs that go silently blind, and rolling deployments, where the whole feature is accepted and inert.
The hook is also only one of a few ways a deployment can be stopped. What happens when nobody is watching and the tasks themselves are failing is the job of the circuit breaker and deployment alarms, and they get their own posts: “ECS circuit breaker and CloudFormation: who owns the rollback” and “ECS deployment alarms: the fine print”.
Building deployment pipelines with checks like these is part of our DevOps and infrastructure as code work - if a bad release reaching production is your recurring fear, we can help.