GitHub Actions

GitHub Actions Core Concepts

Using Job concurrency

Controlling how jobs run in parallel is crucial for production workflows. GitHub Actions’ concurrency key lets you ensure that only one workflow or job in a specified group runs at a time—preventing resource contention, conflicting deployments, or race conditions.

1. Baseline Workflow: Parallel Jobs

Consider a simple workflow with two jobs:

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Docker Build
        run: echo docker build -t ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest
      - name: Docker Login
        run: echo docker login --username=${{ vars.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }}
      - name: Docker Publish
        run: echo docker push ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest

  deploy:
    needs: docker
    runs-on: ubuntu-latest
    steps:
      - name: Docker Run
        run: |
          echo docker run -d -p 8080:80 ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest
          sleep 600s

After you trigger this workflow (e.g., via workflow_dispatch), the docker job runs first. The deploy job then spins up the container and sleeps for 600 seconds:

The image shows a GitHub Actions workflow interface with a job sequence involving "docker" and "deploy" steps, currently queued.

Without any concurrency settings, multiple runs will queue up and execute deploy in parallel—often not what you want in production.

2. Introducing concurrency

Use the concurrency key to define a group name and control behavior with cancel-in-progress. Here’s how to cancel any in-progress deploy when a new run starts:

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Docker Build
        run: echo docker build -t ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest
      - name: Docker Login
        run: echo docker login --username=${{ vars.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }}
      - name: Docker Publish
        run: echo docker push ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest

  deploy:
    needs: docker
    concurrency:
      group: production-deployment
      cancel-in-progress: true
    runs-on: ubuntu-latest
    steps:
      - name: Docker Run
        run: |
          echo docker run -d -p 8080:80 ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest
          sleep 600s

Note

The group value can include {{ github.ref }}, {{ github.sha }}, or environment variables to make it unique per branch or environment. See workflow syntax: concurrency.

When a new run starts, any in-progress deploy in production-deployment is canceled:

Error: The operation was canceled.

The image shows a GitHub Actions workflow summary for "Exploring Variables and Secrets," which was manually triggered and then canceled. It includes details about the "docker" and "deploy" jobs, with annotations indicating errors related to deployment cancellation.

3. Comparing cancel-in-progress Options

SettingBehaviorWhen to Use
cancel-in-progress: trueCancels any in-progress runAlways pick the latest deployment; short-running tasks
cancel-in-progress: falseQueues new runs until current finishesEnsure every run completes; long migrations or audits

4. Queuing New Runs (No Cancellation)

If you’d rather wait for the current deploy to finish, set cancel-in-progress: false:

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Docker Build
        run: echo docker build -t ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest
      - name: Docker Login
        run: echo docker login --username=${{ vars.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }}
      - name: Docker Publish
        run: echo docker push ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest

  deploy:
    needs: docker
    concurrency:
      group: production-deployment
      cancel-in-progress: false
    runs-on: ubuntu-latest
    steps:
      - name: Docker Run
        run: |
          echo docker run -d -p 8080:80 ${{ env.CONTAINER_REGISTRY }}/${{ vars.DOCKER_USERNAME }}/${{ IMAGE_NAME }}:latest
          sleep 600s

The second workflow’s deploy job will wait in the Waiting state until the first completes:

The image shows a GitHub Actions interface with a workflow titled "Exploring Variables and Secrets" in progress, specifically highlighting a "Docker Run" step under the "deploy" job.


Watch Video

Watch video content

Previous
Triggering a workflow