Skip to main content
Environments

Deployment environments

Environments are named deployment targets (e.g. staging, production, eu-west) with composable safety gates. A workflow job declaring environment: production is evaluated against ALL configured gates before fremforge provisions the runner VM — if any gate fails, the deploy is refused with a structured reason. Org admins triage via the admin UI; the workflow can be re-run once the underlying condition clears.

Quick start

  1. Go to fremforge admin → Environments (https://frem.sh/<org>/_admin/environments).

  2. Under New environment, enter a name and click Create.

  3. Open the new environment’s detail page, scroll to Deploy policies, configure the policies you want, click Save policies.

  4. Reference the environment in any workflow job:

    jobs:
      deploy:
        runs-on: fremforge
        environment: production
        steps:
          - run: ./deploy.sh

The next deploy that targets this environment will be evaluated against your gates.

The 9 gates

All gates are independent and composable. ALL configured gates must PASS for a deploy to proceed. A gate with no configuration is skipped (treated as pass).

A. Required approvers

Block dispatch until N humans click Approve on the pending deploy.

  • When to use: production deploys, regulated environments, anything where a second pair of eyes is the safety net.
  • Configure: Required reviewers on the env detail page. 0 = no approval; 1–6 = N approvers required.
  • Where to approve: fremforge admin → Deployments (/<org>/_admin/deployments), “Pending approvals” section.
  • Notes: the workflow’s triggering user (the person who pushed the commit / opened the PR) cannot approve their own deploy. The platform rejects self-approval at both the route layer and the evaluator.
  • Revoke: approvers can revoke their own approval before the deploy dispatches (e.g. you spotted a problem after clicking Approve). Once the deploy has been dispatched, the approval is immutable.

B. Allowed refs (branch / tag patterns)

Restrict which git refs can deploy to this environment.

  • When to use: “only main deploys to production”; “only release/* tags deploy to canary”.
  • Configure: Deployment branches and tags on the env detail page. GitHub-style globs: main, release/*, **/hotfix-*, refs/tags/v[0-9]+.*. Empty = any ref allowed.

C. Time window

Restrict deploys to specific business hours or block during freezes.

  • When to use: “deploys only Mon–Thu 09:00–17:00 CET”; “block all deploys 22 Dec – 2 Jan (holiday freeze)”.

  • Configure: Time-window JSON on the env detail page. Shape:

    {
      "tz": "Europe/Copenhagen",
      "allowed": [
        { "days": ["Mon", "Tue", "Wed", "Thu"], "from": "09:00", "to": "17:00" }
      ],
      "blackouts": [
        { "from": "2026-12-22T00:00Z", "to": "2027-01-02T00:00Z", "reason": "holiday freeze" }
      ]
    }
    • tz: IANA timezone. Defaults to UTC.
    • allowed: array of allowed windows. Days are 3-letter (Mon..Sun). Times are 24-hour HH:MM in tz.
    • blackouts: array of absolute UTC ranges where deploys are forbidden regardless of allowed.
  • Notes: blackouts take precedence over allowed. Empty = no time restriction.

D. SLO budget healthy

Refuse deploys while the SLO error-budget is being burned.

  • When to use: “don’t deploy while customer-facing latency or error rate is in red — fix the regression first.”
  • Configure: Block when these alarms are firing — comma-separated CES/LTS alarm names that your burn-rate watchdogs maintain. Example: my-api-latency-sev1, my-api-error-budget-sev2.
  • How it works: fremforge tracks the latest state of each listed alarm. When any listed alarm is in state firing, this gate refuses dispatch.
  • Fail-closed: if fremforge has no recent state for a listed alarm, the gate fails closed — investigation is preferred to silent bypass.

E. Required status checks

Require named Forgejo commit-status contexts to be success on the deploying commit.

  • When to use: “deploys must wait until dep-scan, SAST, and image-scan all pass on this commit.”
  • Configure: Required check contexts — one per line. Examples: dep-scan, sast, sha-pin-scan, image-scan-trivy.
  • How it works: fremforge reads GET /api/v1/repos/<o>/<r>/statuses/<sha> at preflight time. Each required context must report state success. Missing OR non-success states fail the gate.

F. Concurrency lock

Cap the number of in-flight deploys to this environment.

  • When to use: “only one prod deploy at a time” — prevents race conditions where two deploys clobber each other.
  • Configure: Max in-flight deploys — integer 0–100. 0 = no cap.
  • Counts: pending_gates, pending_approval, and dispatched states. A completed or failed attempt no longer counts.

G. Recent-rollback cooldown

After a rollback, block deploys for N minutes so the team can investigate.

  • When to use: “after a rollback, force a 60-min pause before re-deploying” — prevents accidentally re-triggering the same regression.
  • Configure: Cooldown after rollback (minutes) — integer 0–1440 (24h). 0 = no cooldown.

H. Linked-ticket required

Require the deploying PR’s body to reference an issue/incident ticket.

  • When to use: compliance / audit-trail requirement — every prod deploy must have a “why” attached.
  • Configure: check Require PR body to reference an issue/incident ticket. Optional Ticket pattern regex override.
  • Default pattern: (?i)(JIRA|FF|INC|FIX)-[0-9]+|fixes? #[0-9]+|closes? #[0-9]+|resolves? #[0-9]+ — matches JIRA-1234, FF-99, fixes #42, Closes #99, etc. The (?i) prefix makes it case-insensitive.
  • Per-tenant override: any valid JavaScript regex. (?i) prefix is honoured.

I. Pause flag

A per-environment switch to halt all deploys.

  • When to use: during an incident. Set a pause; investigate; clear.
  • Configure: Per-environment (tenant admin): <org>/_admin/deployments → “Pause flags” section. fremforge can also apply a platform-wide pause during an incident; any active pause that covers your environment blocks the deploy.
  • Resolution: the most-specific scope wins for the reason field, but ANY active scope blocks the deploy.

How an evaluation runs

When a workflow with environment: <name> is picked up by the platform:

  1. Preflight — fremforge looks up the environment for the tenant. No environment configured = no gates apply (lazy onboarding; fresh customers’ workflows aren’t blocked before they configure envs).
  2. Create-or-find a deploy attempt keyed on (env, ref, commit_sha). Re-dispatches of the same workflow against the same commit share the same attempt — so an approval flow lasts across retries.
  3. Resolve plumbing — actor email and PR body are read from Forgejo. Best-effort; fallback to synthetic if Forgejo is degraded.
  4. Evaluate all 9 gates in parallel — each gate is independent and reads its own signal.
  5. Compose — if ALL configured gates pass, status flips to dispatched and the runner VM provisions. If ONLY Gate A (approvers) is failing, status flips to pending_approval — admins can click Approve, the next retry re-evaluates. If any other gate fails, status flips to gates_failed.
  6. Audit — the full gate-result breakdown is recorded and an audit event fires.

What a refused deploy looks like

The customer sees: workflow run cancelled, with a commit-status check showing the refusal reason (e.g. deploy_gate_C_time_window — outside allowed windows).

The org admin sees: a row in Pending deployments with the full evaluation breakdown — which gates passed, which failed, with the specific reason string per gate.

Combining gates

The ladder is intentionally composable — most customers configure 2–3 gates per env, not all 9. Suggested starting points:

PatternGates configuredRationale
Solo dev, single envnoneNo gates needed at solo-team scale; fremforge defaults to “all branches allowed”.
Small team, prod envB (refs=main) + I (pause-flag available)Prevents accidental feature-branch deploys to prod; oncall has the kill-switch.
Mid-size team, regulatedA (2 reviewers) + B (refs=main, release/*) + H (linked-ticket) + IApproval + audit-trail + cross-team kill-switch.
Enterprise, multi-stageA (3 reviewers) + B + C (business hours) + D (SLO healthy) + E (dep-scan + sast) + F (concurrency=1) + G (60-min cooldown) + H + IFull ladder; every gate makes sense at this scale.

Start small — add gates as you grow.

Per-environment secrets and variables

Independently of the gates, environments carry their own secrets and variables that are only injected into jobs declaring environment: <name>. See Secrets for the secret-handling pattern.

OIDC environment claim

Workflows that declare environment: <name> get an additional OIDC claim — sub becomes repo:<owner>/<repo>:environment:<name>. This is what cloud trust policies match on for short-lived federation (AWS STS AssumeRoleWithWebIdentity, Azure WIF, GCP Workload Identity Federation). See OIDC federation for the trust-policy shapes.

Troubleshooting

The workflow is stuck on “Review pending” and no notification went out. → Check the env’s Required reviewers count. Approvals happen on the Pending deployments admin page; fremforge doesn’t email reviewers (yet). Add the page to your team’s morning-standup loop.

A deploy was refused with deploy_gate_D_slo_burn but I don’t have Gate D configured. → Gate D fails closed when fremforge has no recent state for a listed alarm. If you’ve LISTED an alarm name that has never reported state, the gate auto-refuses. Remove the alarm name from the gate config, or make sure the alarm is wired so its state is reported.

The PR body matches a ticket pattern but Gate H still fails. → The deploy attempt was created BEFORE the PR was opened. fremforge captures the PR body at first-evaluation time and doesn’t re-fetch. Push an empty commit to trigger a fresh evaluation.

I want to bypass the gates temporarily. → There is no per-tenant bypass UI yet. Contact support@frem.sh to bypass a gate temporarily. The pause-flag scopes are clear/active toggles only.

Audit events

Every gate decision emits structured audit events:

EventWhen
environment.gates.updateTenant admin changes gate config
tenant.deploy.approval_grantedApprover clicks Approve
tenant.deploy.approval_rejectedApprover clicks Reject (with reason)
tenant.deploy.approval_revokedApprover revokes their own approval
tenant.deploy.pause_set / _clearedPer-env pause flag toggled (tenant admin)
platform.deploy.pause_set / _clearedPlatform-wide pause flag toggled