Understanding GitHub Actions Concurrency

When working with GitHub Actions, you may face situations where multiple workflow runs overlap, consume unnecessary resources, or even cause conflicts in deployment. This is where concurrency comes into play.
Concurrency in GitHub Actions allows you to control how workflow runs are handled when a new run is triggered before a previous one finishes. By defining concurrency rules, you can decide whether to cancel, queue, or allow multiple runs simultaneously.
Why Concurrency Matters
Imagine the following scenarios:
CI pipelines: A developer pushes multiple commits rapidly. Without concurrency control, each commit triggers a workflow, resulting in unnecessary duplicate builds.
Deployments: Two workflows triggered close together may attempt to deploy to the same environment at the same time, leading to inconsistent states.
Resource Management: Avoiding multiple parallel runs saves GitHub-hosted runner minutes and keeps pipelines efficient.
Basic Concurrency Syntax
You can define concurrency in your workflow YAML file using the concurrency key:
name: CI Pipeline
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
concurrency:
group: ci-build-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: echo "Running build for $GITHUB_REF"
Key Components
1. group
Defines the "bucket" that runs belong to.
Can be static (e.g.,
"deploy") or dynamic (e.g.,"deploy-${{ github.ref }}").Runs in the same group respect concurrency rules.
2. cancel-in-progress
If
true: cancels any currently running jobs in the same group before starting a new one.If
false(default): waits until the current run in the group finishes before starting the next one.
Examples
Example 1: Cancel Previous Runs
Useful for CI pipelines where only the latest commit matters:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
Example 2: Sequential Deployments
Ensures only one deployment per environment happens at a time:
concurrency:
group: deploy-production
cancel-in-progress: false
Example 3: Matrix Jobs with Concurrency
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [14, 16, 18]
concurrency:
group: test-${{ matrix.node }}
cancel-in-progress: true
Best Practices
Use meaningful group names
Use${{ github.ref }}or${{ github.workflow }}to scope groups appropriately.Cancel aggressively for CI
Saves time and runner minutes when frequent commits occur.Don’t cancel for deployments
Deployment pipelines should finish in order to maintain state consistency.Mix with environments
You can combine concurrency with environment protection rules for more reliable workflows.
Docker Example with Concurrency
Suppose you are building and pushing a Docker image on every push to main. Without concurrency, multiple builds may try to push at the same time.
name: Docker Build & Push
on:
push:
branches:
- main
jobs:
docker-build:
runs-on: ubuntu-latest
concurrency:
group: docker-main
cancel-in-progress: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Log in to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push
run: |
docker build -t myapp:latest .
docker push myapp:latest
Here, if multiple pushes happen rapidly, only the latest Docker build will continue.
Conclusion
Concurrency in GitHub Actions is a powerful feature to:
Avoid duplicate work,
Prevent deployment conflicts,
Save runner minutes.
By properly configuring group and cancel-in-progress, you can make your pipelines faster, cheaper, and more reliable.





