CD: Deploy to Cloud with GitHub Actions
Continuous Deployment automates the process of delivering your code to production. Learn to deploy to AWS, Azure, or GCP using GitHub Actions with secure authentication, environment protection, and manual approval gates.
Continuous Deployment (CD) is the practice of automatically deploying every change that passes your automated tests to production—without manual intervention. While Continuous Integration ensures code is tested, Continuous Deployment ensures it reaches users as quickly as possible. This means that when a developer merges a pull request, the code is automatically built, tested, and deployed to production within minutes.
This approach offers significant advantages. Bugs are caught and fixed immediately rather than accumulating. Features reach users faster, enabling quicker feedback loops. The deployment process becomes routine and reliable, reducing the stress associated with big-bang releases. However, CD requires strong confidence in your test suite and a robust rollback strategy. For many teams, a hybrid approach with staged deployments (dev → staging → production) and manual approvals for production is a practical starting point.
GitHub Environments allow you to define different deployment targets with specific protection rules. You might have a development environment for automatic deployments, a staging environment for manual testing, and a production environment that requires approval. Each environment can have its own secrets, branch restrictions, and protection rules.
To create an environment, go to your repository's Settings → Environments → New environment. Name it "production" or "staging". You can then configure required reviewers—people who must approve before a deployment can proceed. You can also restrict which branches can deploy to each environment, ensuring only main or release branches can deploy to production.
# Workflow that uses environments
jobs:
deploy-staging:
environment:
name: staging
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
run: echo "Deploying to staging..."
deploy-production:
needs: deploy-staging
environment:
name: production
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: echo "Deploying to production..."
Traditionally, CI/CD systems use long-lived API keys or secrets to authenticate to cloud providers. These secrets need to be stored, rotated, and protected—a significant security challenge. GitHub Actions supports OpenID Connect (OIDC), which eliminates the need for long-lived secrets entirely. Instead, GitHub generates a short-lived, scoped token that your cloud provider trusts.
With OIDC, you configure your cloud provider (AWS, Azure, GCP, etc.) to trust GitHub as an identity provider. When your workflow runs, GitHub provides a JSON Web Token (JWT) that includes claims about the repository, environment, and branch. Your cloud provider validates this token and returns temporary credentials scoped to the permissions you define. No secrets are stored in GitHub, and the credentials expire after the job finishes.
# AWS OIDC example
jobs:
deploy:
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: us-east-1
- name: Deploy to AWS
run: aws s3 sync build/ s3://my-bucket/
Even with full automation, many teams want a human to approve production deployments. GitHub Environments support required reviewers—people or teams who must approve before a deployment job can run. When a workflow reaches a job with an environment that requires approval, it pauses and sends notifications to the designated reviewers.
To configure required reviewers, go to Settings → Environments → production → Required reviewers. Add the GitHub usernames or teams that must approve. When a deployment is requested, each reviewer receives an email and a GitHub notification. Any reviewer can approve or reject the deployment. Once approved, the workflow continues automatically.
Approval gates are perfect for production deployments where you want an extra layer of oversight. They're also useful for security-sensitive deployments where compliance requires human sign-off. You can combine approval gates with other protection rules like branch restrictions and status checks.
# Workflow with production approval
jobs:
build-and-test:
# ... tests and build ...
deploy-production:
needs: build-and-test
environment:
name: production
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ./deploy.sh
AWS offers several deployment options through GitHub Actions. For static websites, you can sync files to S3. For serverless applications, you can deploy to Lambda using the Serverless Framework. For containerized applications, you can push to ECR and update ECS services. The official AWS actions make all of this straightforward.
name: Deploy to AWS
on:
push:
branches: [ main ]
permissions:
id-token: write
contents: read
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions
aws-region: us-east-1
- name: Deploy to S3
run: aws s3 sync ./build s3://my-app-bucket --delete
- name: Invalidate CloudFront
run: aws cloudfront create-invalidation --distribution-id E123456 --paths "/*"
Azure has excellent GitHub Actions integration with Azure Login and deployment actions for Web Apps, Functions, and Kubernetes. You can use OIDC for authentication with Azure AD, eliminating the need for stored secrets. Once authenticated, deployment is just a few lines of YAML.
name: Deploy to Azure
on:
push:
branches: [ main ]
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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@v2
with:
app-name: 'my-app'
package: './build'
Modern deployment strategies reduce risk by controlling how traffic moves to new versions. Blue-green deployment maintains two identical environments: blue (current) and green (new). After deploying to green, you switch traffic when ready. If issues arise, switching back is instant. Canary deployment sends a small percentage of traffic to the new version, gradually increasing as confidence grows. Rolling deployment updates instances one at a time, maintaining availability throughout.
GitHub Actions can implement these strategies by orchestrating your cloud provider's APIs. For example, you might deploy to a new ECS task definition (green), wait for health checks, then update the load balancer to route traffic to the new version. If the health check fails, the workflow stops and alerts the team.
# Blue-Green deployment with ECS
- name: Deploy to ECS (green)
run: aws ecs update-service --cluster my-cluster --service my-service --task-definition green-task
- name: Wait for health checks
run: aws ecs wait services-stable --cluster my-cluster --services my-service
- name: Switch traffic to green
run: aws elbv2 modify-listener --listener-arn $LISTENER_ARN --default-actions Type=forward,TargetGroupArn=green-tg
- name: Clean up blue
if: success()
run: aws ecs update-service --cluster my-cluster --service my-service --task-definition blue-task --desired-count 0
No deployment is perfect. A robust CD pipeline includes an automatic or manual rollback strategy. In a blue-green deployment, rollback is as simple as switching traffic back to the blue environment. In a rolling deployment, you might redeploy the previous version.
GitHub Actions can automate rollbacks by detecting deployment failures through health checks, monitoring alarms, or error rate thresholds. When a failure is detected, the workflow can trigger a rollback automatically, minimizing downtime. For manual rollbacks, you can create a separate workflow that deploys a previous version from an artifact or Git tag.
# Manual rollback workflow
name: Rollback to previous version
on:
workflow_dispatch:
inputs:
version:
description: 'Version to rollback to'
required: true
jobs:
rollback:
environment: production
runs-on: ubuntu-latest
steps:
- name: Deploy previous version
run: ./rollback.sh ${{ github.event.inputs.version }}
Start with staging. Before automating production deployments, set up a staging environment that mirrors production. Deploy to staging automatically on every main branch push. This builds confidence in your automation before touching production.
Use OIDC over secrets. OIDC eliminates the security risk of storing long-lived credentials. It's more secure and requires less secret management overhead.
Implement health checks. After deployment, verify that your application is healthy before considering the deployment successful. This prevents deployments that break core functionality from being marked as successful.
Monitor deployment metrics. Track deployment frequency, lead time, change failure rate, and mean time to recovery. These metrics help you improve your deployment process over time.
Keep deployments small and frequent. Small deployments are easier to debug and roll back. Large, infrequent deployments are riskier and harder to test thoroughly.
Continuous Deployment turns your CI pipeline into a delivery machine. Deploy with confidence, roll back when needed, and ship value to users faster than ever.