Skip to main content

GitHub Actions CI/CD Setup Guide

This guide walks you through setting up automatic deployment to AWS ECS when code is pushed to the staging branch.

📋 Prerequisites

  • AWS Account with ECS, ECR, and IAM access
  • GitHub repository with staging branch
  • AWS resources already created (ECR repository, ECS cluster, ECS service)

🔐 Step 1: Create IAM User for GitHub Actions

Why: GitHub Actions needs AWS credentials to push images to ECR and update ECS services.

  1. AWS ConsoleIAMUsersCreate user

  2. User name: github-actions-deploy

    • Provide user access to the AWS Management Console - Unchecked (not needed)
    • Access key - Programmatic access - Checked (required)
  3. Set permissionsAttach policies directly

  4. Add these policies:

    • AmazonEC2ContainerRegistryFullAccess (for ECR push)
    • AmazonECS_FullAccess (for ECS deployment)
    • AmazonEC2ContainerServiceFullAccess (alternative, broader access)

    OR create custom policy (more secure - recommended):

    Create policyJSON → Paste:

    Option 1: Staging-Only Policy (if using separate IAM users per environment):

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "ecr:GetAuthorizationToken",
    "ecr:BatchCheckLayerAvailability",
    "ecr:GetDownloadUrlForLayer",
    "ecr:BatchGetImage",
    "ecr:PutImage",
    "ecr:InitiateLayerUpload",
    "ecr:UploadLayerPart",
    "ecr:CompleteLayerUpload"
    ],
    "Resource": "*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "ecs:DescribeServices",
    "ecs:DescribeTaskDefinition",
    "ecs:DescribeTasks",
    "ecs:ListTasks",
    "ecs:RegisterTaskDefinition",
    "ecs:UpdateService"
    ],
    "Resource": "*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "iam:PassRole"
    ],
    "Resource": [
    "arn:aws:iam::606532921651:role/reelog-ecs-task-execution-role",
    "arn:aws:iam::606532921651:role/reelog-ecs-task-role"
    ]
    }
    ]
    }

    Option 2: Environment-Agnostic Policy (if using same IAM user for staging + production):

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "ecr:GetAuthorizationToken",
    "ecr:BatchCheckLayerAvailability",
    "ecr:GetDownloadUrlForLayer",
    "ecr:BatchGetImage",
    "ecr:PutImage",
    "ecr:InitiateLayerUpload",
    "ecr:UploadLayerPart",
    "ecr:CompleteLayerUpload"
    ],
    "Resource": "*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "ecs:DescribeServices",
    "ecs:DescribeTaskDefinition",
    "ecs:DescribeTasks",
    "ecs:ListTasks",
    "ecs:RegisterTaskDefinition",
    "ecs:UpdateService"
    ],
    "Resource": "*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "iam:PassRole"
    ],
    "Resource": [
    "arn:aws:iam::606532921651:role/reelog-ecs-task-execution-role",
    "arn:aws:iam::606532921651:role/reelog-ecs-task-role",
    "arn:aws:iam::606532921651:role/reelog-ecs-task-execution-role-prod",
    "arn:aws:iam::606532921651:role/reelog-ecs-task-role-prod"
    ]
    }
    ]
    }

    Recommendation: Use Option 2 (Environment-Agnostic) if you plan to deploy to both staging and production with the same IAM user. This allows one policy to work for both environments.

    • Policy name: GitHubActionsDeployPolicy
    • Description: Allows GitHub Actions to build and push Docker images to ECR, and deploy updates to ECS services. Grants minimal permissions required for CI/CD pipeline.
    • Tags (optional but recommended):
      • Tag 1: Key=Environment, Value=Staging (or All if using Option 2)
      • Tag 2: Key=Project, Value=Reelog
      • Tag 3: Key=Purpose, Value=GitHubActionsDeployPolicy
      • Tag 4: Key=Name, Value=GitHubActionsDeployPolicy
    • Create policy
  5. Attach policy to user → NextCreate user

  6. Create Access Key (if not created during user creation):

    • IAM ConsoleUsersgithub-actions-deploySecurity credentials tab
    • Access keys section → Create access key
    • Use case: Select Third-party service
      • Best match - GitHub Actions is a third-party CI/CD service that manages AWS resources
      • Alternative: "Application running outside AWS" also works, but "Third-party service" is more specific
    • Description (optional): GitHub Actions CI/CD deployment
    • Set description tag (optional): Key=Purpose, Value=GitHubActions
    • Create access key
  7. IMPORTANT: Copy the Access Key ID and Secret Access Key

    • ⚠️ You can only see the secret key once!
    • ⚠️ Download the .csv file or copy both values immediately
    • Save these securely - you'll add them to GitHub Secrets
  8. Tags (optional but recommended):

    • Tag 1: Key=Environment, Value=Staging
    • Tag 2: Key=Project, Value=Reelog
    • Tag 3: Key=Purpose, Value=GitHubActions
    • Tag 4: Key=Name, Value=github-actions-deploy

🔑 Step 2: Add GitHub Secrets

Why: Store AWS credentials securely in GitHub (encrypted, not visible in logs).

  1. GitHub RepositorySettingsSecrets and variablesActions

  2. New repository secret → Add each of these:

    Secret 1:

    • Name: AWS_ACCESS_KEY_ID
    • Value: Your IAM user Access Key ID (from Step 1.6)
    • Add secret

    Secret 2:

    • Name: AWS_SECRET_ACCESS_KEY
    • Value: Your IAM user Secret Access Key (from Step 1.6)
    • Add secret

📝 Step 3: Verify Workflow File

The workflow file is already created at:

.github/workflows/deploy-staging.yml

What it does:

  1. ✅ Triggers on push to staging branch
  2. ✅ Builds Docker image
  3. ✅ Pushes to ECR (reelog-api-staging)
  4. ✅ Updates ECS task definition with new image
  5. ✅ Deploys to ECS service (reelog-api-staging)
  6. ✅ Waits for service to stabilize

Configuration:

  • AWS Region: us-east-2
  • ECR Repository: reelog-api-staging
  • ECS Cluster: reelog-staging
  • ECS Service: reelog-api-staging
  • Task Definition: reelog-api-staging
  • Container Name: reelog-api

To customize: Edit .github/workflows/deploy-staging.yml and update the env section if needed.

🚀 Step 4: Test Deployment

  1. Make a change to your code (e.g., update a comment)

  2. Commit and push to staging branch:

    git checkout staging
    git add .
    git commit -m "Test: GitHub Actions deployment"
    git push origin staging
  3. Check GitHub Actions:

    • GitHub RepositoryActions tab
    • You should see "Deploy to Staging" workflow running
    • Click on it to see progress
  4. Monitor deployment:

    • Watch the workflow logs
    • Check AWS ECS Console → reelog-staging cluster → reelog-api-staging service
    • New tasks should be deploying
  5. Verify:

    curl https://api-staging.reelog.app/ping

🔍 Troubleshooting

Workflow fails at "Login to Amazon ECR"

  • Check: AWS credentials are correct in GitHub Secrets
  • Check: IAM user has ecr:GetAuthorizationToken permission

Workflow fails at "Build, tag, and push image"

  • Error: "tag invalid: The image tag 'latest' already exists and cannot be overwritten because the tag is immutable"
    • Cause: ECR repository has immutable tags enabled (security best practice)
    • Solution: The workflow now only pushes Git SHA tags (not latest). This is actually better practice - each deployment uses a specific, traceable image tag.
    • Note: If you need to change this, you can disable immutable tags in ECR settings, but it's not recommended for security reasons.
  • Error: Access denied or permission errors
    • Check: IAM user has ECR push permissions (ecr:PutImage, ecr:InitiateLayerUpload, etc.)
    • Check: ECR repository name matches (reelog-api-staging)
  • Error: Dockerfile not found
    • Check: Dockerfile exists in repository root

Workflow fails at "Deploy Amazon ECS task definition"

  • Error: "Unexpected key 'enableFaultInjection' found in params"
    • Cause: Task definition downloaded from ECS contains metadata fields that the GitHub Action doesn't support
    • Solution: The workflow now includes a step to clean unsupported fields automatically
    • If still failing: Check that jq is available (it's pre-installed on GitHub Actions runners)
  • Error: "AccessDenied" or permission errors
    • Check: IAM user has ECS update permissions
    • Check: IAM user has iam:PassRole permission for task execution role
  • Error: Service or container name mismatch
    • Check: ECS service name matches (reelog-api-staging)
    • Check: Container name in task definition matches (reelog-api)

Deployment succeeds but service doesn't update

  • Check: ECS service is using the correct task definition family
  • Check: Service auto-deployment is enabled
  • Check: New tasks are healthy (check CloudWatch logs)

📊 Workflow Features

Automatic Triggers

  • Push to staging branch - Automatic deployment
  • Manual trigger - Use "Run workflow" button in Actions tab

Image Tagging

  • Git SHA tag - 606532921651.dkr.ecr.us-east-2.amazonaws.com/reelog-api-staging:abc123...
  • Latest tag - 606532921651.dkr.ecr.us-east-2.amazonaws.com/reelog-api-staging:latest
  • Both tags are pushed for flexibility

Deployment Safety

  • Waits for service stability - Ensures new tasks are healthy before completing
  • Non-destructive - Updates service with zero-downtime deployment
  • Rollback capability - Can manually update service to previous task definition if needed

🔒 Security Best Practices

  1. Use custom IAM policy (not full access) - Limits permissions to only what's needed
  2. Rotate credentials regularly - Update GitHub Secrets every 90 days
  3. Monitor IAM user activity - Check CloudTrail logs for unusual activity
  4. Use separate IAM users - One for staging, one for production (when you set up prod)
  5. Never commit secrets - All credentials stored in GitHub Secrets only

📝 Next Steps

  • ✅ Staging deployment is now automated!
  • 🔄 Consider setting up production deployment workflow (similar, but for main branch)
  • 📊 Set up deployment notifications (Slack, email, etc.)
  • 🔍 Add automated tests before deployment
  • 📈 Monitor deployment metrics in CloudWatch

📚 Additional Resources