architecture
article··updated ·6 min read

Go/no-go on 6 June 1944: what a release decision is made of

#go-live #deployment #decision-making #release-management #observability

The Normandy landing was planned for 5 June 1944. On the morning of 4 June it was postponed by 24 hours because of a storm over the Channel. Early on 5 June Eisenhower confirmed the landing for the 6th, based on a forecast from his chief meteorologist, Group Captain James Stagg, who predicted a short break in the weather. More than 150,000 troops landed that day.

Constraints fixed the window

The date followed from two physical conditions. Airborne troops needed a moon close to full to see drop zones at night. The navy and engineers needed low tide around first light, so that the landing could start on the rising tide while the beach obstacles were still exposed. Both held on only a few days a month. In June 1944 that meant 5, 6 and 7 June. The next acceptable tide was around 18 to 20 June, without the moon.

Releases have the same kind of window, and it's usually set by things outside the team: a contractual date, a regulatory deadline, a peak-traffic period during which nobody deploys, the availability of the people who can roll back. So write the window down early, as a constraint, together with the next window after it. "If we miss Thursday, the next slot is after the freeze on the 25th" is something the decision-maker needs on the day itself.

The criteria existed before the forecast

The planners didn't ask on 4 June whether the weather felt good enough. Limits for wind, cloud base, visibility and sea state had been set in advance by the army, navy and air force, each for its own reasons. The meteorologists' job was to say whether the forecast fell inside those limits.

Criteria set on the morning of the release are easy to bend to the deadline. Define them when the change is planned and keep them next to the change. Something like this (an illustrative format, it doesn't belong to any particular tool):

release: payouts-2026-06
window:
  primary: 2026-06-18T07:00/10:00
  next: 2026-06-25T07:00/10:00
go_criteria:
  - ci_green_on_release_commit: true
  - migration_tested_on_prod_size_copy: true
  - migration_duration_max_minutes: 5
  - rollback_rehearsed: true
  - error_rate_baseline_known: true
abort_criteria:            # evaluated during the canary
  - error_rate_above_baseline_pct: 50
  - p95_latency_above_ms: 800
  - failed_payment_jobs: 1
owner: one named person

The abort_criteria block matters as much as go_criteria. The postponement on 4 June was go_criteria at work: conditions were outside the limits, so the answer was no.

When I put my own SaaS on production for the first time, I wrote the go conditions down before the first deploy: off-server backups with a restore I'd actually rehearsed (it took 2 seconds), a restart policy, log rotation, TLS with a monitor and a runbook for the keys. CI had a DEPLOY_ENABLED variable, so a merge built the image but didn't deploy it until every condition was ticked off. The swap itself meant about a second of downtime. I accepted that on purpose, because there was no SLA that would justify zero-downtime work.

Blacksod Point and the German forecast

German forecasters saw the same storm and concluded that the stormy weather would last for about two weeks, too rough for a landing. They had fewer observations from the Atlantic than the Allies. The Allies had reports from weather ships, aircraft and stations on the western edge of Europe, including the Blacksod Point lighthouse in Ireland, and those reports showed a gap between two fronts. On the German side, senior officers acted on their forecast: Rommel left for his home near Ulm for his wife's birthday on 6 June, and several commanders were traveling to a staff exercise in Rennes.

You can't judge a canary without a baseline, so know the normal error rate and latency of the affected endpoints before the deploy. Measure the risky parts directly, for example the migration time on a production-sized copy. A quick run on staging says little about production. Business signals belong on the same screen as technical ones. A payment flow can return 200 and still create no orders.

I got the ordering wrong once, moving n8n to a new server. The order was strict: first switch off the old instance (clear the active workflow versions and webhooks, stop it), only then activate the workflows on the new one. Up to that step a rollback cost nothing, because the old instance was still doing the work. That's exactly where I slipped. I stopped the old instance before the A record really pointed to the new server, and for a few minutes the service returned 404. Since then, before switching off the source, I check both origins with curl --resolve. That check bypasses DNS, so where the A record points has to be checked separately. After the switch I tested the whole chain: a chat with an AI answer in 6.4 s, 8 active workflows, 6 webhooks.

Conflicting forecasts need one owner

Stagg didn't get one forecast. Three teams (the Met Office, the Admiralty and a US Army Air Forces group) worked with different methods and disagreed during the days before the landing. His role was to reconcile them into a single statement for Eisenhower, and Eisenhower's role was to decide.

Signals before a release can disagree too. The process should say explicitly who collects and presents them, and who makes the call, by name. That name goes in the release note.

Waiting also has a cost

The alternative to 6 June was the next window, around 18 to 20 June. From 19 to 22 June a severe storm hit the Channel and wrecked the artificial Mulberry harbor off Omaha Beach. Had the landing been moved to that window, it would have run into the worst weather of the month.

That's hindsight. Nobody could forecast that storm on 5 June. Still, postponing carries its own risks, and they go on the list next to the risks of going. For software, delay usually means a larger batch of changes in the next release, longer-lived branches that drift from main, and a deadline that moves closer while the window narrows.

end of node