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.
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.
- Open the users page. Sidebar → Security & Access → Users.
- 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.
- 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.
- Submit. The button reads Send invite even on this path. No mail is sent.
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.
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.
- Sign in as the service account — a private browser window keeps your admin session intact.
- Open its keys. User menu → Profile settings → API keys → Create API key.
- Name it and copy the value. The raw
af_…key is shown once and never again. Paste it straight into your CI secret store.
POST /api/v1/me/api-keys or through bootstrap can be given one.
3. Create the pipeline
Back on your admin account: sidebar → Data sources → Deployment Pipelines → Add 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.
/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.
/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.
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.
6. Restrict who may approve
- Create the plan. Sidebar → Security & Access → Review plans → Add review plan. Set Minimum approvals.
- Add one approver row. Set Role to
Adminand Stage to1. - 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
1are 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:
- Submit a deployment request. It is idempotent on the CI run id, so a retried job never opens a second review.
- Poll the gate until it answers releasable.
- Confirm execution, immediately before deploying.
- Report the outcome once the deploy finishes.
- 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.
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.
/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.
From here: deployment pipelines covers freeze windows, routing policies, break-glass and rollback reviews in full.