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
- Go to Settings → Environments → New environment
- Name it
production - 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