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.

Environments OIDC Authentication Approval Gates
What is Continuous Deployment?

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.

Teams practicing Continuous Deployment can deploy dozens of times per day. This reduces risk because each deployment is small and easy to roll back if issues arise.
GitHub Environments: Separate Staging from Production

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..."
Environment protection rules ensure that deployments happen in a controlled, auditable way. Each deployment is logged with who deployed it and when.
OIDC: Secure Authentication Without Long-Lived Secrets

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/
OIDC is the most secure way to authenticate GitHub Actions to your cloud infrastructure. No secrets to rotate, no risk of leaked credentials.
Approval Gates: Manual Review Before Production

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
When a workflow reaches this job, it pauses. Reviewers get notified, and the deployment proceeds only after approval. All approvals are logged for audit purposes.
Deploying to AWS with GitHub Actions

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 "/*"
Deploying to Azure with GitHub Actions

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'
Deployment Strategies: Blue-Green, Canary, Rolling

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
Rollback: When Things Go Wrong

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 }}
CD Pipeline Best Practices

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.

The best CD pipelines combine automation with safety mechanisms: staging environments, approval gates, health checks, and easy rollbacks.
Frequently Asked Questions
What's the difference between Continuous Delivery and Continuous Deployment?
Continuous Delivery means every change is automatically tested and ready for release, but deployment requires a manual trigger. Continuous Deployment goes further—every change that passes tests is automatically deployed to production without human intervention. Both approaches use the same technical foundation; the difference is whether you add a manual approval step.
How do I set up OIDC with AWS?
First, create an OIDC provider in AWS IAM. Then create an IAM role that trusts the GitHub OIDC provider with conditions on repository, environment, or branch. The role defines what actions the workflow can perform. Finally, use the aws-actions/configure-aws-credentials action with the role ARN. No secrets needed!
Can I have multiple approval gates in one workflow?
Yes! Each environment can have its own required reviewers. You might have a staging environment with automatic approval, a pre-production environment that requires one reviewer, and a production environment that requires two senior reviewers. The workflow pauses at each environment until approvals are granted.
How do I deploy to multiple cloud providers?
You can have multiple jobs or steps that deploy to different providers. Each deployment can use its own authentication—OIDC for AWS, Azure Login for Azure, etc. You can also use environment variables to configure which provider to deploy to based on branch or input.
What happens if a deployment fails during a rolling update?
It depends on your strategy. With ECS rolling updates, AWS automatically stops the deployment if tasks fail health checks. With Kubernetes, you can configure rollout strategies. You can also add monitoring to your workflow that triggers a rollback if error rates spike after deployment.
How do I test my deployment workflow without affecting production?
Use separate environments! Create a "test" or "development" environment with the same configuration but different resources. Deploy to that environment first. You can also use workflow_dispatch with inputs to run deployment jobs manually on specific branches.
Can I deploy from branches other than main?
Yes. Use branch protection rules to restrict which branches can deploy to certain environments. For example, you might allow any branch to deploy to dev, only main to deploy to staging, and only release branches to deploy to production. This creates a clean promotion path.
How do I handle database migrations in CD?
Database migrations are the most critical part of CD. Best practice is to make migrations backward-compatible so you can deploy code before the migration. Run migrations as a separate step before the application deployment. Have a rollback plan for migrations if something goes wrong.
Previous: CI Pipeline Next: Environments & Secrets

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.