Skip to content

CircleCI Integration

Rack Gateway provides native CircleCI integration for automated deploy approvals. When configured, the gateway automatically approves CircleCI workflow jobs after an admin approves the deployment request.

The integration streamlines the deployment workflow:

Admins only approve once in the gateway. CircleCI jobs proceed automatically.

Terminal window
# CircleCI personal API token of a user who can approve jobs in the project
CIRCLECI_TOKEN=your-circleci-api-token

Getting a CircleCI Token:

  1. Sign in to CircleCI as the user the approvals should come from (ideally a dedicated machine user with write access to the project)
  2. Visit https://app.circleci.com/settings/user/tokens
  3. Click “Create New Token” and name it “Rack Gateway Deploy Approvals”
  4. Copy the token immediately

Approving a job needs a personal API token: CircleCI project tokens can’t call the v2 approval endpoint. Personal tokens have no scopes; they act as that user.

Each app using CircleCI needs these settings:

SettingDefaultDescriptionExample
vcs_providergithubVersion control provider (for pipeline links)github
vcs_repononeRepository (org/repo)MyOrg/myapp
ci_providercircleciCI systemcircleci
circleci_approval_job_namenoneApproval job nameapprove_deploy_prod
circleci_auto_approve_on_approvalfalseEnable auto-approvaltrue
Terminal window
# Per-app settings via environment
RGW_APP_MYAPP_SETTING_VCS_PROVIDER=github
RGW_APP_MYAPP_SETTING_VCS_REPO=MyOrg/myapp
RGW_APP_MYAPP_SETTING_CI_PROVIDER=circleci
RGW_APP_MYAPP_SETTING_CIRCLECI_APPROVAL_JOB_NAME=approve_deploy_production
RGW_APP_MYAPP_SETTING_CIRCLECI_AUTO_APPROVE_ON_APPROVAL=true
version: 2.1
workflows:
deploy:
jobs:
- test
- build:
requires:
- test
# Request approval from Rack Gateway
- request_approval:
requires:
- build
# CircleCI approval job (auto-approved by gateway)
- approve_deploy_production:
type: approval
requires:
- request_approval
# Deploy after approval
- deploy:
requires:
- approve_deploy_production
jobs:
test:
docker:
- image: cimg/node:18.0
steps:
- checkout
- run: npm test
build:
docker:
- image: cimg/node:18.0
steps:
- checkout
- run: npm run build
request_approval:
docker:
- image: cimg/base:current
steps:
- install_rack_gateway
- run:
name: Request deploy approval
command: |
rack-gateway deploy-approval request \
--app myapp \
--git-commit "$CIRCLE_SHA1" \
--branch "$CIRCLE_BRANCH" \
--ci-metadata "{\"workflow_id\":\"$CIRCLE_WORKFLOW_ID\",\"pipeline_number\":\"<< pipeline.number >>\"}" \
--message "Deploy $CIRCLE_BRANCH@${CIRCLE_SHA1:0:7} to production"
deploy:
docker:
- image: cimg/base:current
steps:
- checkout
- install_rack_gateway
- run:
name: Deploy via Rack Gateway
command: rack-gateway deploy --app myapp --wait
commands:
install_rack_gateway:
steps:
- run:
name: Install the rack-gateway CLI
command: |
VERSION=v0.1.10
BASE=https://github.com/docspring/rack-gateway/releases/download/$VERSION
curl -fsSLO $BASE/rack-gateway-linux-amd64.tar.gz
curl -fsSLO $BASE/rack-gateway-linux-amd64.tar.gz.sha256
sha256sum --check rack-gateway-linux-amd64.tar.gz.sha256
tar xzf rack-gateway-linux-amd64.tar.gz
sudo install -m 0755 rack-gateway-linux-amd64 /usr/local/bin/rack-gateway

Set RACK_GATEWAY_API_TOKEN (a token with the cicd role) and RACK_GATEWAY_URL in a CircleCI context or the project’s environment variables; CircleCI exports them to every job. Use one context per rack, each with its own token.

  1. request_approval job: Uses CLI to create approval request with CI metadata
  2. approve_deploy_production job: CircleCI approval job (type: approval) that blocks workflow
  3. Job name must match: Approval job name must match circleci_approval_job_name setting
  4. CI metadata: Include workflow_id and pipeline_number
  5. Deploy job: The deploy runs under the approval, so its manifest must reference pre-built images tagged with the commit (see Git Commit Verification)

The gateway requires specific metadata for auto-approval:

{
"workflow_id": "$CIRCLE_WORKFLOW_ID",
"pipeline_number": "<< pipeline.number >>"
}
FieldSourcePurpose
workflow_id$CIRCLE_WORKFLOW_IDIdentify workflow for job approval (must be a UUID)
pipeline_number<< pipeline.number >>Build pipeline URL for display (required for auto-approval)

A gateway that’s only reachable on a private network (Tailscale, a VPN, a VPC) needs the CI job to join that network before it calls the gateway. With Tailscale, run each job as an ephemeral node with a tag for CI, and let your Tailscale ACLs allow that tag to reach only the gateway. Store the Tailscale auth key or OAuth client secret in the same CircleCI context as the gateway token.

When admin approves in gateway:

The approval runs as a background job a moment after the admin approves. It approves the hold job in the workflow that requested the approval while that job is on hold. If it isn’t, and the workflow was rerun, the gateway approves the hold job in the newest rerun: the newest workflow in that pipeline with the same name, created after the original. The gateway only looks at the pipeline’s workflow list to find a rerun, because CircleCI sometimes leaves workflows out of that list for a while.

The ci_metadata comes from the CI job that requested the approval, so the gateway doesn’t trust it on its own: the hold job is only approved when the workflow is in the pipeline the request named (the one linked on the request’s page, which the approver reviewed), and that pipeline is building the approved commit of the app’s repository on the app’s VCS host (vcs_provider). Without a vcs_repo nothing is approved automatically. The job is re-checked against the deploy approval on every attempt (retries can run later, after a rejection or expiry), and again just before the hold job is approved.

Each approval is in the audit log as deploy_approval_request.circleci_approve, naming the hold job, its workflow and its pipeline: an attempted entry written before the approval is sent (nothing is sent if it can’t be written), then its result.

SourceData
CI metadataworkflow_id (a CircleCI workflow UUID), pipeline_number (string)
App settingscircleci_approval_job_name, vcs_repo (owner/repo), vcs_provider (github or bitbucket)
Gateway configCIRCLECI_TOKEN

The gateway builds URLs for display in the web UI:

The request’s page in the web UI links the CircleCI pipeline:

https://app.circleci.com/pipelines/{vcs_short}/{vcs_repo}/{pipeline_number}

VCS Short Codes:

  • github → gh
  • bitbucket → bb

Example:

https://app.circleci.com/pipelines/gh/MyOrg/myapp/1234

For multiple deployment environments, use different approval job names:

Terminal window
RGW_APP_MYAPP_SETTING_CIRCLECI_APPROVAL_JOB_NAME=approve_deploy_staging
Terminal window
RGW_APP_MYAPP_SETTING_CIRCLECI_APPROVAL_JOB_NAME=approve_deploy_production
workflows:
deploy:
jobs:
- test
- build
# Staging
- request_approval_staging:
requires: [build]
- approve_deploy_staging:
type: approval
requires: [request_approval_staging]
- deploy_staging:
requires: [approve_deploy_staging]
# Production
- request_approval_production:
requires: [deploy_staging]
- approve_deploy_production:
type: approval
requires: [request_approval_production]
- deploy_production:
requires: [approve_deploy_production]

The Integrations page shows CircleCI as enabled only when the gateway has a CIRCLECI_TOKEN.

  1. Verify CIRCLECI_TOKEN is set on gateway
  2. Restart gateway after adding environment variable
  3. Check token hasn’t been revoked

Test token:

Terminal window
curl -H "Circle-Token: YOUR_TOKEN" https://circleci.com/api/v2/me

Checklist:

  • circleci_auto_approve_on_approval is true
  • circleci_approval_job_name matches exactly
  • ci_metadata includes workflow_id
  • CIRCLECI_TOKEN has correct permissions
  • App ci_provider is set to circleci
  • App vcs_repo is set to the repository CircleCI builds (owner/repo)
  • The workflow was building the approved commit (not a later re-run of another commit)

Check gateway logs:

Terminal window
convox logs --app rack-gateway | grep -i circleci

Settings problems are logged as warnings when the request is approved, for example CircleCI auto-approve enabled but no approval_job_name configured for app myapp or CircleCI auto-approve enabled but no CircleCIToken configured. A failing approval job is logged with its attempts and retried.

Common error messages:

ErrorCause
approval job "xxx" not found in workflowJob name mismatch
CircleCI API error (401) / (403) / (404)Token revoked, its user can’t approve in the project, or the workflow doesn’t exist
ci_metadata.pipeline_number is requiredThe request’s --ci-metadata has no pipeline_number
invalid CircleCI workflow_idworkflow_id is not a UUID (the job is cancelled)
CircleCI pipeline does not match the deploy approvalThe workflow is in another pipeline than pipeline_number, or the pipeline builds another commit or repository, is a fork, or its repository can’t be determined (the job is cancelled; approve the hold manually after checking)
deploy approval is no longer activeThe approval was rejected, expired or deployed before the job ran (the job is cancelled)

If you see CircleCI API error (403):

  1. The token’s user needs write access to the project (enough to approve a hold job in the CircleCI UI)
  2. Regenerate if necessary:

Verify settings:

  • vcs_provider is correct (github)
  • vcs_repo is in org/repo format
  • pipeline_number is in CI metadata
  • Store CIRCLECI_TOKEN as environment variable, never in git
  • Use Convox secrets or AWS Secrets Manager
  • Rotate tokens every 90 days

The CircleCI token can do anything its user can, in every project that user can reach:

  • Use a dedicated machine user with access to only the projects you deploy
  • Monitor approval audit logs

Before approving the hold job, the gateway checks:

  • The request is pending, and the approver is an admin who passes MFA
  • The app’s ci_provider is circleci, circleci_auto_approve_on_approval is on, and CIRCLECI_TOKEN is set
  • The CI metadata is valid, and the app has a circleci_approval_job_name and a vcs_repo
  • The deploy approval is still approved and unexpired when the job runs, and just before the hold job is approved
  • The workflow is in the pipeline numbered pipeline_number, which builds the approved commit of vcs_repo on the vcs_provider’s host, and isn’t from a fork