CircleCI’s approval hold jobs are a powerful mechanism for gating deployments, ensuring human oversight at critical stages.
Let’s see it in action. Imagine a deployment pipeline for a web application. A typical workflow might look like this:
workflows:
version: 2
build_and_deploy:
jobs:
- build-app
- test-app:
requires:
- build-app
- deploy-staging:
type: approval # This is the hold job
requires:
- test-app
- deploy-production:
requires:
- deploy-staging
- build-app # Ensuring build is still fresh
filters:
branches:
only: main
When the test-app job completes successfully, the deploy-staging job is triggered. However, because its type is set to approval, it doesn’t execute any commands. Instead, it pauses the workflow and waits for explicit approval from a user within the CircleCI UI. Only after a designated approver clicks the "Approve and Continue" button does the workflow proceed to the deploy-production job. This allows teams to review test results, staging environments, or perform manual checks before pushing to production.
The core problem this solves is the risk of automated deployments introducing regressions or critical issues into live environments. By introducing a manual gate, you inject a human checkpoint. This isn’t just about preventing errors; it’s about enforcing policy and process. For instance, you might require a QA sign-off before any merge to main can be deployed, or a product manager’s approval for a feature release.
Internally, the approval job is a special kind of job that consumes no compute resources. It simply exists as a state in the workflow graph. When a workflow reaches an approval job, CircleCI marks that specific workflow run as "pending approval." The workflow graph execution halts at that point. Any subsequent jobs that require the approval job are also effectively on hold. When a user with appropriate permissions navigates to the workflow execution page in CircleCI and clicks the approval button, CircleCI updates the workflow state, allowing the subsequent jobs to be scheduled and executed.
The key levers you control are:
type: approval: This is the fundamental setting that transforms a regular job into a hold.requires: This defines which jobs must complete successfully before the approval job can be reached, and which jobs can only start after the approval job is passed. This is how you build your gating logic.- Permissions: In CircleCI, not everyone can approve. You configure user permissions at the project level, ensuring only authorized individuals can give the go-ahead.
Consider a scenario where you want to deploy to staging automatically but require approval before deploying to production. You’d have a deploy-staging job that runs automatically, followed by an approval job that requires deploy-staging, and then your deploy-production job that requires the approval job.
The workflow graph itself is the state machine. When an approval job is present, the graph simply pauses at that node. It’s not a complex background process; it’s a declarative instruction to the CircleCI orchestrator: "Wait here until a human says go."
You can have multiple approval jobs in a single workflow, creating sequential gates. For example, one approval for staging deployment, and a second approval after staging testing before production deployment.
One thing that trips people up is the requires dependency for jobs after the approval. If your deploy-production job requires your deploy-staging job directly, but also requires the approval job (which itself requires deploy-staging), the deploy-production job will only start after the approval job is completed. This is because the requires array specifies all prerequisites. If deploy-production requires A and B, it won’t run until both A and B are done. If B is an approval job that itself requires A, then deploy-production implicitly waits for A to finish, then for the approval, and then it can run.
The next logical step is to explore conditional approvals based on branch or tag.