Guides

Gate a deployment pipeline.

Last updated

What you are building

By the end of this guide a deploy job stops and waits. It asks AccessFlow for permission to release, a human approves or rejects it, and only then does the job run helm upgrade — or whatever your deploy step actually is. Every release leaves a request, a named approver and an audit trail behind it.

The eight steps below are the order an operator actually performs them in, and several of them are not guessable from the reference page. Budget about twenty minutes. You need an admin account on a running AccessFlow instance, and a CI pipeline you can edit.

How do I gate a deployment behind approval?

Create a service account for the CI job, give it an API key and a can-trigger grant on a deployment pipeline, attach a review plan that says who may approve, then add the two AccessFlow steps to your workflow around your existing deploy step. The first step blocks until someone approves; the second reports how the deploy went.

The gate is fail-closed. A deployment is releasable only when its status is APPROVED, no freeze window is active, and any scheduled time has passed. Anything else — an error, an unreachable instance, a request the key cannot see — answers not releasable, and the CI wrappers fail the job. That is the point, but it has a consequence worth accepting before you start: if AccessFlow is down, your deployments stop.

1. Create the CI service account

The API key's owning user is the submitter of every deployment it opens, and nobody can approve their own deployment. So the CI identity must be an account that never approves — not your admin account.

  1. Open the users page. Sidebar → Security & AccessUsers.
  2. Use the dropdown arrow, not the button. The control at the top right is a split button. Its main half is Invite via email; the path you want, Create with password, is behind the small arrow beside it.
  3. Fill the form. Email, Initial password, Display name. Leave Role on Analyst — the right to trigger deployments comes from the per-pipeline grant in step 5, not from the role.
  4. Submit. The button reads Send invite even on this path. No mail is sent.
Invite via email fails without SMTP. The email path checks that system SMTP is configured before it does anything else, and returns 422 SYSTEM_SMTP_NOT_CONFIGURED_FOR_INVITE when it is not. If you have not set up notifications yet, the password path is the only one that works.
The admin users page with the create-user form open, showing the email, initial password, display name and role fields.
Sidebar → Security & AccessUsers. Create with password lives behind the split button's arrow.
Prefer not to hand a service account a password? Steps 1 and 2 can be declared instead, with bootstrap.serviceAccounts[] — it creates the user with password login disabled and imports a key you generated yourself. See Automate with Terraform, or service-account API keys for the full property list. On a pipeline you intend to keep, this is the better path.

2. Mint its API key

API keys are self-service. There is no admin screen that mints a key on someone else's behalf, so you have to sign in as the service account once.

  1. Sign in as the service account — a private browser window keeps your admin session intact.
  2. Open its keys. User menu → Profile settingsAPI keysCreate API key.
  3. Name it and copy the value. The raw af_… key is shown once and never again. Paste it straight into your CI secret store.
Keys minted in the browser never expire. The form takes only a name. The REST API does accept an expiry, so a key you create through POST /api/v1/me/api-keys or through bootstrap can be given one.

3. Create the pipeline

Back on your admin account: sidebar → Data sourcesDeployment PipelinesAdd pipeline. Give it a name and pick the provider that matches your CI system — GitHub Actions, GitLab CI, Azure Pipelines, Jenkins, CircleCI, Bitbucket Pipelines, or generic.

Copy the pipeline id out of the address bar now. Creating a pipeline lands you on /admin/deployment-pipelines/<uuid>, and that UUID is the pipeline id your CI job needs. There is no id column on the pipeline list and no copy-id button; the only other place it appears is interpolated inside the CI snippet in step 7, whose copy control copies the whole snippet rather than the bare id. A copyable id is tracked in issue 771.

The pipeline is identified by that UUID rather than by name because a trigger-only API key is not allowed to list pipelines, so it has nothing to resolve a name against.

The admin deployment pipelines page listing configured pipelines with their CI provider, repository, whether AI analysis is enabled, and whether they are active.
/admin/deployment-pipelines — one row per pipeline. Open one to reach its six settings tabs.

4. Add environments

An environment is what a deploy targets, and it is where you decide whether a release needs a human at all. On the pipeline's Environments tab, choose Add environment. Name it exactly as your CI job will name it — staging, production — turn Requires review on, and set Required approvals.

Two rules decide how many approvals a release actually needs, and which people count:

  • An environment's Required approvals replaces the review plan's minimum when it is set. It does not add to it.
  • An environment's Review plan override beats the one attached to the pipeline. Leave it on Plan default to inherit.

Environments are also where break-glass is switched on. An emergency deploy needs both a break-glass grant on the pipeline and this opt-in on the target environment; there is no admin bypass for either half. Leave it off unless you have somebody who genuinely needs it.

The environments tab of a deployment pipeline, listing ordered environments with their required approval count, review requirement, review plan override and break-glass opt-in.
Pipeline settings → Environments — promotion order, required approvals, review-plan override and break-glass, per environment.

5. Grant the service account permission to trigger

On the Permissions tab, choose the service account in the User box, leave Can trigger on, and click Grant. Without this the CI job cannot open a deployment request at all.

Grants can go to groups as well as to individuals. Where both apply, the effective grant is the most permissive combination of a user's own grant and every group grant that has not expired.

If the account is not in the list, note that this control loads a single page of a hundred active users and searches within it, rather than querying the server. On a large instance a freshly created service account may not be among them. Creating the account immediately before this step is the reliable way to have it present.

6. Restrict who may approve

Do not skip this step. The permission that lets somebody approve a deployment is held by both ADMIN and REVIEWER. On any instance where reviewers exist who should not be shipping releases, a review plan that scopes approval is the only thing standing between them and your production deploy. It is mandatory, not optional.
  1. Create the plan. Sidebar → Security & AccessReview plansAdd review plan. Set Minimum approvals.
  2. Add one approver row. Set Role to Admin and Stage to 1.
  3. Attach it. On the pipeline's General tab, pick it under Review plan.

Three things about approver rows regularly catch people out:

  • A plan with no approver rows restricts nothing. Attaching an empty plan looks like configuration but leaves the request open to everyone who holds the deployment-review permission.
  • Deployment review is single-stage. Only rows at stage 1 are consulted. A row at stage 2 or higher is ignored silently — no warning, no error.
  • Review override bypasses the approver list entirely. It is an admin-only permission and it is meant to work this way, which means the rule that ultimately protects your pipeline is simply: do not make someone an admin unless they should be allowed to ship.

To pin approval to one named person rather than to every admin, put their user id in the row's User box. It is a plain text field expecting a UUID, and user ids are not shown anywhere in the users screen — read one from GET /api/v1/admin/users.

Whatever you configure, one rule cannot be configured away: the submitter can never approve their own deployment, admins and override holders included. That is why step 1 insisted on a separate account.

7. Add the gate to your pipeline

The pipeline's CI setup tab carries ready-made snippets with the pipeline id already filled in. Copy from there rather than retyping the UUID. It offers four, chosen from a toggle that always opens on GitHub Actions — it does not preselect the provider you set in step 3: GitHub Actions, GitLab CI, Azure Pipelines and curl. Pipelines set to Jenkins, CircleCI, Bitbucket Pipelines or generic use the curl sequence.

Every provider follows the same four beats:

  1. Submit a deployment request. It is idempotent on the CI run id, so a retried job never opens a second review.
  2. Poll the gate until it answers releasable.
  3. Confirm execution, immediately before deploying.
  4. Report the outcome once the deploy finishes.
.github/workflows/deploy.yml
- id: gate
  uses: bablsoft/accessflow/.github/actions/deployment-gate@v1
  with:
    accessflow-url: https://accessflow.example.com
    api-key: ${{ secrets.ACCESSFLOW_API_KEY }}
    pipeline-id: 1f0c9d02-1c2a-4a19-9f0e-6b2f8f1a4c77
    environment: production
    version: ${{ github.ref_name }}
    commit-sha: ${{ github.sha }}
    wait-timeout: 30m

- run: ./deploy.sh

- uses: bablsoft/accessflow/.github/actions/deployment-outcome@v1
  if: always()
  with:
    accessflow-url: https://accessflow.example.com
    api-key: ${{ secrets.ACCESSFLOW_API_KEY }}
    request-id: ${{ steps.gate.outputs.request-id }}
    job-status: ${{ job.status }}

The outcome step is separate, and runs under if: always(), because a composite action has no post-run hook to report from. Pass an explicit outcome: ROLLED_BACK when you roll back — on an environment that requires review that opens a follow-up the submitter is not allowed to close.

GitLab CI, Azure Pipelines and the plain-curl sequence for anything else are documented alongside the full input reference in the deployment gate in CI.

The CI setup tab of a deployment pipeline rendering a copy-paste snippet for the pipeline's provider with the pipeline id filled in.
Pipeline settings → CI setup — snippets for GitHub Actions, GitLab CI, Azure Pipelines and curl, each carrying the pipeline id the job gates on.

8. Your first gated release

Run the workflow. The gate step opens a request and then waits, printing its poll attempts. In AccessFlow the request appears for reviewers at /deployment-reviews, carrying the version, the target environment, the commit and the AI release-risk verdict if you enabled analysis. It never appears there for the account that submitted it.

An approver opens it and approves. Within a poll interval the gate answers releasable, the CI job unblocks, your deploy step runs, and the outcome step records how it went. The submitter can watch the whole thing at /deployments.

The deployment review queue listing deployments awaiting a decision with pipeline, environment, version, submitter, risk and age, and approve and reject actions.
/deployment-reviews — where an approver picks the release up. A deployment never appears here for its own submitter.

If the job fails instead

  • The gate returns 404. The key cannot see that request — usually a wrong pipeline id, or a key belonging to an account with no can-trigger grant. AccessFlow answers 404 rather than 403 on purpose, so a key cannot probe for pipelines it has no business knowing about.
  • It waits and then times out. Nobody approved inside wait-timeout, or a freeze window is holding the release. Check the request at /deployments — a held release resumes on its own when the window closes, with no re-approval.
  • Approved, but still not releasing. Approval alone is not a green light. The job branches on releasable, which also requires no active freeze window and any scheduled time to have passed.
A real worked example. The AccessFlow public demo deploys itself through its own gate. The workflow, the exact one-time setup it needed and the recovery path when the instance is down are written up in docs/09-deployment.md → The public demo environment.

From here: deployment pipelines covers freeze windows, routing policies, break-glass and rollback reviews in full.