GitHub Actions Cheatsheet

Deployment

Use this GitHub Actions reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Deployment Environments

Environments add protection rules, required reviewers, and scoped secrets to deployment jobs.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com   # shown in the Actions UI and PR deployments
    steps:
      - run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}   # from 'production' environment

Configure environments at: Repo → Settings → Environments.

Environment Protection Rules

RuleWhat it does
Required reviewersJob pauses and notifies reviewers; must be approved to proceed
Wait timerDelays job start by N minutes (1–43,200)
Deployment branchesRestrict which branches/tags can deploy
Deployment policiesCustom rules (Enterprise)

Branch Protection via Environments

# Only allow main and release/* to deploy to production
# Set in: Settings → Environments → production → Deployment branches
# Pattern: main, release/**

In the workflow, this blocks the job from running on other branches — no code change needed.

Concurrency — Prevent Duplicate Deployments

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false    # queue, don't cancel (safe for deploys)

Or cancel for preview/staging:

concurrency:
  group: deploy-preview-${{ github.event.pull_request.number }}
  cancel-in-progress: true

Deploy on Push to Main

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: ./scripts/deploy.sh
        env:
          API_KEY: ${{ secrets.API_KEY }}

Deploy on Release Publish

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.release.tag_name }}
      - run: echo "Deploying ${{ github.event.release.tag_name }}"
      - run: ./deploy.sh

Deploy to AWS (OIDC — no static keys)

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
          aws-region: us-east-1

      - uses: aws-actions/amazon-ecr-login@v2

      - run: |
          docker build -t $ECR_URI:${{ github.sha }} .
          docker push $ECR_URI:${{ github.sha }}
        env:
          ECR_URI: 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp

      - run: |
          aws ecs update-service \
            --cluster my-cluster \
            --service my-service \
            --force-new-deployment

Deploy to Google Cloud (OIDC)

permissions:
  id-token: write
  contents: read

steps:
  - uses: google-github-actions/auth@v2
    with:
      workload_identity_provider: projects/123/locations/global/workloadIdentityPools/github/providers/github
      service_account: deployer@my-project.iam.gserviceaccount.com

  - uses: google-github-actions/deploy-cloudrun@v2
    with:
      service: my-service
      image: gcr.io/my-project/app:${{ github.sha }}
      region: us-central1

Deploy to Azure (OIDC)

permissions:
  id-token: write
  contents: read

steps:
  - uses: azure/login@v2
    with:
      client-id: ${{ secrets.AZURE_CLIENT_ID }}
      tenant-id: ${{ secrets.AZURE_TENANT_ID }}
      subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

  - uses: azure/webapps-deploy@v3
    with:
      app-name: my-app
      package: dist/

GitHub Pages Deployment

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deploy.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

      - uses: actions/upload-pages-artifact@v3
        with:
          path: out/

      - id: deploy
        uses: actions/deploy-pages@v4

Preview / PR Environments

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]

jobs:
  deploy-preview:
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    environment:
      name: pr-${{ github.event.number }}
      url: https://pr-${{ github.event.number }}.preview.example.com
    steps:
      - run: ./deploy-preview.sh ${{ github.event.number }}

  teardown:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:
      - run: ./teardown-preview.sh ${{ github.event.number }}

Post Deployment Status to PR

- uses: actions/github-script@v7
  with:
    script: |
      github.rest.repos.createDeploymentStatus({
        owner: context.repo.owner,
        repo: context.repo.repo,
        deployment_id: '${{ steps.deploy.outputs.deployment-id }}',
        state: 'success',
        environment_url: 'https://example.com',
        description: 'Deploy complete',
      });

Or use the simpler PR comment approach:

- uses: actions/github-script@v7
  with:
    script: |
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: '**Deployed to production:** https://example.com',
      });

Rollback Pattern

jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      previous-sha: ${{ steps.get-prev.outputs.sha }}
    steps:
      - id: get-prev
        run: echo "sha=$(git rev-parse HEAD~1)" >> $GITHUB_OUTPUT
      - run: ./deploy.sh ${{ github.sha }}

  rollback:
    needs: deploy
    if: failure()
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh ${{ needs.deploy.outputs.previous-sha }}
        env:
          ROLLBACK: 'true'

Deploy Workflow Checklist

  • Use environment protection rules for production (required reviewers + branch restriction).
  • Use OIDC over static credentials whenever the cloud provider supports it.
  • Set concurrency.cancel-in-progress: false to queue, not clobber, concurrent deploys.
  • Upload build artifacts before deploy; download in deploy job — don't rebuild.
  • Always record the deployed SHA/version in a step output or annotation.
  • Post deployment URLs to the PR for preview environments.
  • Implement a rollback job triggered on failure().