Safe continuous deployment needs approvals, per-environment secrets and a guarantee two deploys never race.
An environment (Settings, Environments) represents a deployment target such as staging or production. A job opts in with environment: production, and the environment can enforce protection rules before the job starts:
- Required reviewers: the job waits until a listed person or team approves.
- Wait timer: delay the job by up to 30 days.
- Deployment branches and tags: only allow, say,
mainandv*tags to deploy.
Environments also hold their own secrets and variables, which are only exposed to jobs referencing that environment. Combined with branch rules, this means a workflow on a feature branch cannot read production credentials. Deployments appear on the repository page with links to environment.url.
Concurrency controls overlapping runs. All runs or jobs with the same concurrency.group string form a queue where at most one runs and at most one waits; a newer pending run replaces the older pending one. cancel-in-progress: true cancels the running one instead, ideal for PR builds where only the latest commit matters. For deployments use cancel-in-progress: false so a half-finished deploy is never killed.
Typical groups: ${{ github.workflow }}-${{ github.ref }} for CI, and a fixed deploy-production for deploys.
Example
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
deploy:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: https://shop.example.com
concurrency:
group: deploy-production
cancel-in-progress: false # never kill a running deploy
steps:
- run: ./deploy.sh
env:
TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }} # environment secretRemember
- environment: gates a job with reviewers, timers and branch rules
- Environment secrets are only visible to jobs in that environment
- concurrency groups serialize runs; cancel-in-progress kills the running one
Watch out for
- Using cancel-in-progress: true on deploy jobs, leaving systems half-deployed.