Freezes are scattered
Dates live in Slack threads, a shared calendar and a spreadsheet owned by someone on PTO. Nobody is sure which one is current, and no tool can query any of them.
One source of truth for freeze windows that every pipeline, script and auditor can query. Hard-block or soft-warn, time-zone-aware recurrence, exception workflow with named approvers, and an audit trail you can hand to SOC 2 / SOX auditors.
$ curl -si -X POST https://changefreeze.io/v1/check \
-H "Authorization: Bearer cfk_r_8fK2…" \
-d '{"service":"checkout-api","environment":"prod",
"change_type":"code","actor":"ci@acme.io","source":"github-actions"}'
HTTP/2 423 Locked
{
"ok": false,
"check_id": "chk_01JBX4Q7N2",
"frozen": true,
"decision": "block",
"mode": "hard",
"active": [{ "name": "Black Friday / Cyber Monday", "kind": "hard",
"reason_category": "peak_season",
"ends_at": "2026-12-01T05:00:00.000Z" }],
"exception": null,
"message": "Changes are blocked by the \"Black Friday / Cyber Monday\" freeze for Acme…"
}
# .github/workflows/deploy.yml
- name: Change freeze gate
run: |
curl -sf -X POST https://changefreeze.io/v1/check \
-H "Authorization: Bearer ${{ secrets.CHANGEFREEZE_KEY }}" \
-d '{"service":"checkout-api","environment":"prod","change_type":"code",
"actor":"${{ github.actor }}","ref":"${{ github.sha }}","source":"github-actions"}'
Works with anything that can make an HTTP request
Every team has freeze windows. Almost nobody has a system that enforces them everywhere a change can originate.
Dates live in Slack threads, a shared calendar and a spreadsheet owned by someone on PTO. Nobody is sure which one is current, and no tool can query any of them.
GitLab deploy freeze, Harness, Octopus, Argo sync windows and Merge Freeze each guard their own door. Terraform applies, kubectl, feature flags and cron jobs walk straight through.
"Can we ship the hotfix?" gets a thumbs-up from somebody, somewhere. Six months later an auditor flags a production change with no documented approval and you rebuild the story from chat logs.
Create hard or soft freezes with a time zone, recurrence and scope: services, environments, change types, tags. Pick a template (BFCM, year-end, quarter close, no-deploy Fridays) or hit Freeze now during an incident.
{ "name": "Quarter-end close", "kind": "soft",
"timezone": "America/New_York",
"recurrence": { "freq": "monthly", "interval": 3 },
"scope": { "tags": ["finance"], "environments": ["prod"] } }
Add one HTTP call before anything that changes production. A hard freeze answers 423 Locked and the job stops; a soft freeze answers 200 with decision: "warn" and is logged.
curl -sf https://changefreeze.io/v1/check \
-H "Authorization: Bearer $CF_KEY" \
-d '{"service":"billing","environment":"prod"}' \
|| { echo "Change freeze active"; exit 1; }
Engineers request an exception with a justification and rollback plan; named approvers approve it; the token EXC-7K2M-9QXD unlocks exactly that scope until it expires. Every check, block, override and approval is kept, and exported on demand.
-d '{"service":"billing","environment":"prod",
"exception_token":"EXC-7K2M-9QXD"}'
HTTP/2 200 "decision": "override"
Hard freezes return 423 and stop the job. Soft freezes let it through with a warn decision that is logged and can alert the channel.
Freeze by service, environment, change type (code, infra, config, data, flag, hotfix) or tag. Freeze customer-facing prod without freezing docs.
Daily, weekly (by weekday), monthly or yearly windows anchored to a wall clock in the freeze's own time zone. DST shifts are handled, not hand-corrected.
Requests carry a justification, rollback plan, risk level and expiry. Named approvers decide; no self-approval by default. The token overrides only the scope it was granted for.
Every check, block, override, approval and freeze edit is recorded with actor, source, IP and timestamp. Business exports CSV/JSON mapped to SOC 2, SOX, PCI DSS and ISO 27001.
A free "Are we frozen?" page at /s/your-org, an SVG badge for READMEs, status.json for scripts and an ICS feed for calendars.
Slack, Microsoft Teams, Discord, signed webhooks and email for upcoming, started and ended freezes, exception requests and decisions, and blocked deploys.
One button (or one POST /v1/freezes with freeze_now: true) during an incident. Default four hours, every pipeline stops, every channel is told.
Auditors ask the same questions every year: when were changes restricted, who approved what went through anyway, and can you prove the control actually ran? ChangeFreeze answers all three from one export.
curl, it can honour a freeze.No agents, no plugins to keep updated. Your tooling pulls the decision; ChangeFreeze never needs access to your infrastructure.
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Change freeze gate
env:
CF_KEY: ${{ secrets.CHANGEFREEZE_KEY }}
EXCEPTION_TOKEN: ${{ inputs.exception_token }}
run: |
curl -sf -X POST https://changefreeze.io/v1/check \
-H "Authorization: Bearer $CF_KEY" -H "Content-Type: application/json" \
-d "{\"service\":\"checkout-api\",\"environment\":\"prod\",\"change_type\":\"code\",
\"actor\":\"${{ github.actor }}\",\"source\":\"github-actions\",
\"ref\":\"${{ github.repository }}@${{ github.sha }}\",
\"exception_token\":\"$EXCEPTION_TOKEN\"}" \
|| { echo "::error::Change freeze is active. Request an exception in ChangeFreeze."; exit 1; }
- run: ./deploy.sh
deploy_prod:
stage: deploy
environment: production
before_script:
- |
curl -sf -X POST https://changefreeze.io/v1/check \
-H "Authorization: Bearer $CHANGEFREEZE_KEY" -H "Content-Type: application/json" \
-d "{\"service\":\"$CI_PROJECT_NAME\",\"environment\":\"prod\",\"change_type\":\"code\",
\"actor\":\"$GITLAB_USER_EMAIL\",\"source\":\"gitlab-ci\",\"ref\":\"$CI_PIPELINE_URL\",
\"exception_token\":\"${EXCEPTION_TOKEN:-}\"}" \
|| { echo "Change freeze is active"; exit 1; }
script:
- ./deploy.sh
#!/usr/bin/env bash
set -euo pipefail
res=$(curl -s -o /tmp/cf.json -w '%{http_code}' -X POST https://changefreeze.io/v1/check \
-H "Authorization: Bearer $CF_KEY" -H "Content-Type: application/json" \
-d "{\"service\":\"$SERVICE\",\"environment\":\"$ENV\",\"change_type\":\"infra\",
\"actor\":\"$(whoami)@$(hostname)\",\"source\":\"deploy.sh\",\"exception_token\":\"${EXCEPTION_TOKEN:-}\"}")
case "$res" in
200) ;; # allow, warn or override
423) echo "BLOCKED: $(jq -r .message /tmp/cf.json)"; exit 2 ;;
*) echo "ChangeFreeze returned $res"; exit 3 ;; # fail closed on errors
esac
data "http" "freeze" {
url = "https://changefreeze.io/v1/status?service=payments-db&environment=prod&change_type=infra"
request_headers = { Authorization = "Bearer ${var.changefreeze_key}" }
}
locals { freeze = jsondecode(data.http.freeze.response_body) }
resource "aws_db_instance" "payments" {
# ...
lifecycle {
precondition {
condition = local.freeze.decision != "block"
error_message = "Change freeze active: ${local.freeze.message}"
}
}
}
apiVersion: batch/v1
kind: CronJob
metadata: { name: nightly-etl }
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
initContainers:
- name: change-freeze-gate
image: curlimages/curl:8.10.1
env:
- { name: CF_KEY, valueFrom: { secretKeyRef: { name: changefreeze, key: api-key } } }
command: ["sh", "-c"]
args:
- |
curl -sf -X POST https://changefreeze.io/v1/check \
-H "Authorization: Bearer $CF_KEY" -H "Content-Type: application/json" \
-d '{"service":"nightly-etl","environment":"prod","change_type":"data","source":"k8s-cronjob"}'
containers:
- { name: etl, image: ghcr.io/acme/etl:1.42 }
restartPolicy: Never
More in the docs: Jenkins, Azure DevOps gates, exit-code conventions and webhook signature verification.
Every org gets changefreeze.io/s/your-org, with status.json, an SVG badge and an ICS feed behind it. Share it with support, sales and customers.
Changes are blocked until Tue 1 Dec 2026, 00:00 (America/New_York).
For a single team that wants one source of truth.
forever
Recurring windows, override workflow and a year of history.
or $390/yr (2 months free)
Audit evidence, customer-driven freezes and unlimited seats.
or $1,990/yr (2 months free)
Unlimited everything, SSO and a signed agreement.
annual contract
Those features freeze the tool they live in. ChangeFreeze is the freeze calendar those tools (and Terraform, kubectl, feature-flag scripts, cron jobs and humans) consult. Keep using them; point them at one source of truth so a window defined once applies to every path to production.
No. It is pull-based: your pipeline calls POST /v1/check and acts on the decision. That means no agents, no credentials to your cloud, and you decide how strictly each job honours the answer.
You choose. curl -f with exit 1 fails closed; appending || true or passing soft_fail: true (which makes a block return 200) fails open. Many teams fail closed for production deploys and open for low-risk jobs. Enterprise plans include an SLA.
A hard freeze returns decision: "block" with HTTP 423 unless a valid exception token is supplied. A soft freeze returns decision: "warn" with HTTP 200, is recorded, and can notify a channel. If both apply, hard wins.
A member requests one with a title, justification, rollback plan, risk level, scope and validity window (default 4 hours, max 14 days). Approvers (owner, admin or approver roles by default) approve or deny; the requester cannot approve their own. Approved requests get a token like EXC-7K2M-9QXD that turns a block into an override for matching checks until it expires.
Every check (target, actor, source, ref, decision, matching freezes, exception, API key, IP), every freeze create/edit/cancel, every exception request and decision, and key and member changes. Business and Enterprise export a date range as CSV or JSON with a summary that cites SOC 2 CC8.1, SOX ITGC, PCI DSS 6.5 and ISO 27001 A.8.32.
Each freeze carries an IANA time zone. Recurring windows are anchored to the wall clock in that zone, so "Friday 15:00 to Monday 09:00 in Europe/London" stays at 15:00 across the DST change. All API timestamps are ISO 8601 UTC.
Yes: 3 services, 2 environments, 2 members, 2 scheduled windows, 5,000 checks a month at 60 requests per minute, one Slack channel, a public status page and 30 days of history. No card required.
Create an org, add a service, generate a read key and paste one step into your pipeline. The first freeze window takes about five minutes.