The Cycle
Cutoffs & Why
Staging Freeze — 5 PM UTC
Merges tomain after 5 PM UTC are not deployed to staging. The code lands in main but staging stays on whatever was last deployed before the cutoff.
Why: The content team relies on staging daily. If we kept deploying throughout the night, they’d wake up to an environment that changed under them — possibly broken. Freezing at 5 PM gives them a stable version to work with for at least 16 hours.
Promotion — 9 AM UTC
At 9 AM UTC every day, a scheduled workflow compares staging to production. If they differ, the exact image running on staging is deployed to production. Why: This is the core of the soak process. By the time promotion runs, the staging version has been live for at least 16 hours. The content team has been actively using it, which serves as real-world validation. If something is broken, we catch it before it reaches customers. After promotion, a catch-up deploy runs to bring staging back to the latestmain, starting the cycle again.
Weekly Release — Sundays
On Sunday’s 9 AM promotion, the workflow also publishes the official self-hosted release: git tag, GitHub release notes, and multi-platform Docker images on Docker Hub. Why: Self-hosted users need stable, predictable releases. A weekly cadence gives us enough time to accumulate meaningful changes while keeping the release overhead low. Sunday is chosen so the release has had the full week of cloud production validation behind it.Putting It Together
| Time | What Happens |
|---|---|
| Any time (9 AM–5 PM UTC) | Merge to main → auto-deployed to staging |
| Any time (5 PM–9 AM UTC) | Merge to main → code in main only, staging frozen |
| 9 AM UTC daily | Staging promoted to production, then staging catches up to latest main |
| 9 AM UTC Sunday | Same as above + official self-hosted release published |
Overrides
Sometimes the cycle needs to be bypassed:- Hotfix: Merge to
main, then manually dispatchcontinuous-delivery.ymlwithpromote-to-productionto skip the wait. - Emergency: Use
emergency-cloud-deploy.ymlto deploy directly to production, bypassing staging entirely. Use sparingly.
FAQ
Q: I merged at 6 PM UTC. When does my code reach production? Your code is inmain but staging is frozen. At 9 AM the next day, the current staging (which doesn’t include your change) promotes to production. Then staging catches up and deploys your change. It will promote to production at 9 AM the day after that — roughly 39 hours total.
Q: I merged at 10 AM UTC. When does my code reach production?
It deploys to staging immediately. At 9 AM the next day (roughly 23 hours later), it promotes to production.
Q: Can I deploy to staging during the freeze window?
Yes, manually dispatch continuous-delivery.yml with deploy-staging. But consider that the content team may be relying on the frozen version.