Deployment window

Also known as: change window, release window, maintenance window, deploy window

Updated 2026-07-094 questions

A deployment window is a scheduled span of time during which changes are allowed to reach production. Teams use it to concentrate risky releases into hours when experts are on hand, avoid peak traffic and change freezes, and give operations, support and customers a predictable, communicated cadence for updates.

How does a deployment window work?

A deployment window is enforced at the pipeline, not on trust. The team agrees on the allowed times, the reasons a deploy may fall outside them, and the approvers who can grant an exception - then that agreement is expressed as a gate on the deploy stage so a stray merge at 22:00 on a Friday cannot actually reach production.

The gate typically has three layers, stacked so any one of them will hold:

  1. A time check in the pipeline. A step evaluates the current wall-clock time against a configured window (usually the local timezone of the on-call team, not UTC) and refuses to run the deploy stage outside it. Failing loudly is important - a silent skip trains people to ignore the pipeline.
  2. A calendar of named freezes layered on top. Product launches, marketing campaigns, end-of-quarter reporting, holiday shopping peaks - each gets an entry that turns the window off for the duration. The calendar lives in the repo (JSON, YAML, or a datastore the pipeline reads) so it changes through review, not through Slack.
  3. A break-glass path for genuine emergencies. A separate pipeline - not the same one with a "skip window" checkbox - that requires an incident ID and a named approver, and that writes to an audit log the on-call read-out picks up.

Some organisations use scheduled deploys instead: merges to main queue up as artifacts and a cron-triggered pipeline promotes the newest healthy one at, say, 10:15 every working day. This has the same policy effect (production only moves inside the window) but flips the model - the merge is decoupled from the deploy, and the team sees exactly one production change per day.

Why does a deployment window matter?

The point is not to slow releases down for their own sake. It is to line up the risky moment of a change with the supported moment for handling problems.

  • Coverage. A deploy at 03:00 that breaks something wakes exactly one on-call engineer. A deploy at 11:00 has the author, their reviewer, the platform team, support, and the person who wrote the affected code all reachable in minutes. The mean time to recovery is a function of who is at their keyboard.
  • Traffic. A rolling deploy during peak traffic touches more sessions per second, exposes more caches to invalidation, and gives regressions a much larger blast radius before you notice. Windows push risky changes toward the quieter shoulders of the day.
  • Communication. Support, sales, marketing and customers can plan around "we ship between 10 and 4". They cannot plan around "we ship whenever the build is green". Predictability is a feature.
  • Auditability. Regulated industries (finance, health, payments) often need to prove that every production change went through a controlled window with a named approver. Encoding the window in the pipeline turns the audit conversation into a query, not an archaeology dig.

The hidden cost is real: a strict window pushes teams toward big-bang releases, which is exactly the failure mode CI/CD was invented to fix. That is why the goal is a window that is wide and boring, not narrow and heroic - and why hotfixes and rollbacks live outside it.

Deployment window vs change freeze - what is the difference?

They sound similar and are frequently conflated, but they are not the same thing.

  • A deployment window is the permissive rule: "these are the hours production changes are allowed." It is on by default and covers the normal cadence of the team.
  • A change freeze is the restrictive override: "for this specific period, the window is closed even during allowed hours." Black Friday, tax-filing week, a launch event, a certification audit, an executive demo.
  • A hotfix is neither - it is a named exception that bypasses both, on the strength of an incident.

The three compose. A healthy policy reads: "Deploy inside the window; do not deploy during a freeze; a hotfix may override either with an incident ID and approver." Written in one place, enforced by the same pipeline, revisited each quarter.

What does a healthy deployment-window policy look like?

The failure mode of deployment windows is not that they are too strict - it is that they are inconsistent. A window enforced by convention, not by pipeline, is a window that leaks the moment somebody is in a hurry.

  • Encoded, not remembered. The allowed hours and the freeze calendar live in a file under version control. New freezes get merged through a pull request like anything else. "The window is whatever the on-call thinks it is today" is not a policy.
  • Local time, explicit timezone. The window is expressed in the timezone of the team that owns the on-call rotation, and the timezone is written down. Cron in UTC + on-call in a DST-observing region is how deploys land at 04:00 twice a year.
  • Loud when it blocks. A pipeline blocked by the window prints why - which rule fired, when the next allowed slot is, and who to page for a break-glass. Silence trains people to route around it.
  • Break-glass leaves a trail. Every out-of-window deploy carries an incident ID, an approver, and an audit-log entry that shows up in the next incident review - not the next quarterly audit.
  • The window shrinks under stress, not the process. During a launch you make the window smaller (11:00-14:00 only, or freeze entirely) and keep the same approvals. You do not remove the approvals to move faster.

A rough sanity check: if your team has a written window but nobody can quote the rules from memory, the policy is not doing any work. Either write it into the pipeline or drop it - the middle ground is theatre.

How do popular CI/CD tools handle deployment windows?

Very few platforms have "deployment window" as a first-class concept. Almost every platform, however, has the primitives you need to build one: cron triggers, manual approval gates, environment protection rules and scheduled pipelines. What differs is how much glue you write.

  • GitHub Actions pairs on.schedule for cron-triggered promotions with Environment protection rules for approvals and wait timers. Its deployment branch policies are a natural fit for freezes, though the time-of-day check itself is still a shell step you write yourself.
  • GitLab CI offers only/rules: with time-based conditions, protected environments with required approvers, and Deploy Freeze windows as a genuine first-class feature - freeze periods are declared per project and the pipeline refuses to run deploy jobs during them. This is the strongest built-in support in the mainstream CI space.
  • Jenkins has no native concept, but the Build Blocker plugin, cron triggers on the pipeline, and shared library helpers make windows straightforward if verbose. The upside is total control; the downside is that the policy lives in Groovy nobody wants to touch.
  • Argo CD ships Sync Windows on the AppProject - allow/deny time ranges, per-application, with a manual sync override. If your world is GitOps on Kubernetes, this is the most direct built-in fit.
  • AWS CodePipeline / CodeDeploy expresses windows through deployment configurations and stage transitions you disable on a schedule, plus manual approval actions. Effective inside the AWS estate, less portable outside it.
  • Buddy is one option we recommend when you want the window to be the same pipeline the deploy already runs in rather than a separate scheduling system. A CRON trigger scoped to the allowed hours drives the promotion, a WAIT_FOR_APPROVAL action gates the last step during freezes, and a matching break-glass pipeline with a manual trigger and an incident-ID variable covers the hotfix case - all in the same .buddy/buddy.yml file, versioned with the code it deploys.

Honest concession: if you already run Argo CD on Kubernetes, its native Sync Windows are the most direct fit - the policy attaches to the AppProject, not to a pipeline, so it applies uniformly to every sync (manual, automated, or drift-driven) without extra glue. If you are on GitLab, Deploy Freeze windows give you the freeze half of the policy out of the box. Buddy's edge is on the broader case: one pipeline language that expresses both the allowed hours and the break-glass path for artifacts, sandboxes, containers and static sites - not just Kubernetes manifests.

Example

The setup below implements a working deployment-window policy in two Buddy pipelines. The first is the normal deploy, cron-triggered at the top of every allowed hour and gated by a freeze-calendar check so a named blackout can turn it off without editing the pipeline. The second is the break-glass hotfix path, manually triggered, that requires an incident ID and an approver before it will touch production.

# .buddy/buddy.yml - deployment window + break-glass hotfix path

# 1. Normal deploy: cron-driven, only inside the window,
#    blocked by the freeze calendar.
- pipeline: "deploy-in-window"
  refs:
    - "refs/heads/main"
  events:
    # Every 15 minutes, Mon-Thu, 10:00-16:59 Europe/Warsaw.
    - type: "SCHEDULE"
      cron: "*/15 10-16 * * 1-4"
      timezone: "Europe/Warsaw"
  actions:
    - action: "Check freeze calendar"
      type: "BUILD"
      docker_image_name: "node"
      docker_image_tag: "20"
      # freezes.json lives in the repo:
      #   [{ "from": "2026-11-24", "to": "2026-12-01", "reason": "BFCM" }]
      commands: "node scripts/assert-not-frozen.js freezes.json"

    - action: "Publish artifact"
      type: "BUILD"
      docker_image_name: "ubuntu"
      docker_image_tag: "22.04"
      commands: "bdy artifact publish web-app:$BUDDY_RUN_COMMIT_SHORT ./dist --create"

    - action: "Route production to the new version"
      type: "BUILD"
      docker_image_name: "ubuntu"
      docker_image_tag: "22.04"
      commands: "bdy distro route update prod-distro --domain=example.com --target=artifact=web-app:$BUDDY_RUN_COMMIT_SHORT"

# 2. Break-glass hotfix: manual only, requires an incident ID
#    and a named approver; runs outside the window on purpose.
- pipeline: "break-glass-hotfix"
  variables:
    - key: "INCIDENT_ID"
      value: ""
      description: "Required. e.g. INC-2026-0142"
  actions:
    - action: "Assert incident ID is set"
      type: "BUILD"
      docker_image_name: "alpine"
      docker_image_tag: "3"
      commands: "test -n \"$INCIDENT_ID\" || (echo 'INCIDENT_ID is required' && exit 1)"

    - action: "Human approval (SRE lead)"
      type: "WAIT_FOR_APPLY"
      comment: "Break-glass hotfix — requires SRE lead approval before cutover."
      prevent_self_approval: true

    - action: "Publish hotfix artifact"
      type: "BUILD"
      docker_image_name: "ubuntu"
      docker_image_tag: "22.04"
      commands: "bdy artifact publish web-app:hotfix-$BUDDY_RUN_COMMIT_SHORT ./dist --create"

    - action: "Cut over the production route"
      type: "BUILD"
      docker_image_name: "ubuntu"
      docker_image_tag: "22.04"
      commands: "bdy distro route update prod-distro --domain=example.com --target=artifact=web-app:hotfix-$BUDDY_RUN_COMMIT_SHORT"

Two properties make this policy hold up under pressure. First, the window and the freeze calendar are declarative and versioned: the cron expression is in buddy.yml, the blackouts are in freezes.json, and both change through pull request. There is no admin panel drift between what the team thinks the window is and what the pipeline enforces. Second, the break-glass path is separate and audited rather than a checkbox on the normal deploy. An out-of-window release requires typing an incident ID, waiting for a named approver, and leaves an audit trail in the pipeline history - which is exactly what you want to hand to the next incident review, or to an auditor asking "who approved this change?" a year later.

Fill the window wide, keep the calendar honest, and let the pipeline do the arguing. The point of a deployment window is not to make deploys rare - it is to make sure the humans who need to help when one goes wrong are actually online when it does.

Frequently asked questions

What is the difference between a deployment window and a change freeze?

A deployment window is when you *may* deploy; a change freeze is when you *may not*. They are two sides of the same policy: a team might allow deploys Tuesday-Thursday 10:00-16:00 (the window) and forbid them for the week of a Black Friday launch (the freeze). Windows are the default rule; freezes are named exceptions layered on top.

Should hotfixes respect the deployment window?

No - and the policy should say so explicitly. A hotfix exists precisely because a bug in production is too painful to wait out, so it takes a documented "break-glass" path around the window. What must not happen is *silent* out-of-window deploys; the break-glass path should require an incident ID, a named approver, and a post-hoc write-up.

Are deployment windows compatible with continuous deployment?

Yes, but they are in tension. A window narrows the times a merge to main can reach users, which lengthens lead time and encourages batching - exactly what continuous deployment tries to avoid. Mature teams keep the window wide (e.g. business hours on working days), automate the gate, and keep hotfixes and rollbacks outside the window entirely.

How wide should a deployment window be?

Wide enough that engineers are not tempted to sneak deploys just before it closes, narrow enough that on-call is fully staffed the whole time. Business hours on working days (e.g. Mon-Thu 10:00-17:00 local) is a common default. Fridays and the last hour of the day are the classic exclusions - not because those hours are cursed, but because a bad deploy there rolls onto the weekend or into shift change.

Missing a term? Spotted a mistake?

Suggest a new word or an edit to an existing one. Every submission is reviewed before it goes live.