Skip to main content
GitHub Actions advanced Lesson 5 of 5

GitHub Actions: Deployment Workflows & OIDC

Build production-ready deployment pipelines with environment gates, manual approvals, OIDC keyless auth to cloud providers, and rollback strategies.

A deployment workflow takes tested code all the way to production. This tutorial covers environment gates, OIDC authentication, rollback patterns, and release automation.

Learning outcomes

By the end you can:

  • create deployment workflows with environment gates
  • use OIDC to authenticate to AWS without static credentials
  • implement a rollback step on failure
  • trigger releases automatically from tags

1) Environments and manual approval gates

GitHub Environments let you add protection rules to jobs:

  • Required reviewers — one or more people must approve before the job runs
  • Wait timer — delay deployment for a configurable period
  • Branch restrictions — only allow deployments from specific branches

Setup

  1. Go to Settings → Environments → New environment
  2. Name it production
  3. Add required reviewers (e.g., your team leads)

Use in a workflow

name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    environment: staging     # deploys automatically
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh staging

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production  # pauses here for manual approval
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh production

2) OIDC — keyless authentication to AWS

Instead of storing AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY as GitHub Secrets (long-lived, rotatable), use OIDC to get short-lived tokens.

Configure AWS (one-time setup)

Create an IAM OIDC identity provider in AWS:

  • Provider URL: https://token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com

Create an IAM role with a trust policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
        },
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

Use OIDC in a workflow

name: Deploy to AWS

on:
  push:
    branches: [main]

permissions:
  id-token: write   # required for OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: us-east-1

      # Now all AWS CLI / SDK calls use the assumed role
      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster production \
            --service myapp \
            --force-new-deployment

No long-lived credentials stored anywhere—the token is valid for the duration of the job only.

3) Deployment with rollback on failure

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - name: Update Kubernetes deployment
        id: deploy
        run: |
          kubectl set image deployment/myapp \
            myapp=${{ env.IMAGE_TAG }} \
            --namespace production
          kubectl rollout status deployment/myapp \
            --namespace production \
            --timeout=300s

      - name: Rollback on failure
        if: failure() && steps.deploy.outcome == 'failure'
        run: |
          echo "Deployment failed—rolling back"
          kubectl rollout undo deployment/myapp --namespace production
          kubectl rollout status deployment/myapp --namespace production

      - name: Notify on success
        if: success()
        run: echo "::notice::Deployed ${{ env.IMAGE_TAG }} to production successfully"

4) Release-triggered deployment

Trigger production deploys from published GitHub Releases (tag-based):

name: Release Deploy

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.release.tag_name }}

      - name: Extract version
        id: version
        run: echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - name: Deploy release ${{ steps.version.outputs.tag }}
        run: |
          aws ecs update-service \
            --cluster production \
            --service myapp \
            --force-new-deployment \
            --task-definition myapp:${{ steps.version.outputs.tag }}

5) Deployment summary and notifications

Write a job summary visible in the GitHub Actions UI:

- name: Write deployment summary
  run: |
    cat >> $GITHUB_STEP_SUMMARY <<EOF
    ## Deployment Summary
    - **Environment**: production
    - **Image**: \`${{ env.IMAGE_TAG }}\`
    - **Deployed by**: @${{ github.actor }}
    - **Commit**: [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})
    EOF

Next steps

  • GitOps with ArgoCD: let Kubernetes pull config from Git instead of pushing
  • Terraform in GitHub Actions: plan on PR, apply on merge
  • Multi-region blue/green deployments

Frequently Asked Questions

What is OIDC and why should I use it instead of long-lived secrets?
OIDC (OpenID Connect) lets GitHub Actions request a short-lived token from your cloud provider (AWS, GCP, Azure) at runtime—no static credentials stored in GitHub Secrets. Each token expires after the job, so there are no long-lived secrets to rotate or leak.
What happens if a deployment fails mid-way?
A good deployment workflow detects failure (e.g., a failed kubectl rollout or Terraform apply) and either rolls back automatically (kubectl rollout undo) or notifies the team with context to manually recover.