Amazon EKS — GitHub Actions for EKS

2 minute read

GitHub Actions provides CI/CD workflows that build container images and deploy to EKS. Using OIDC federation eliminates long-lived AWS credentials — GitHub exchanges a short-lived token for an IAM role.


OIDC Authentication (No Long-Lived Credentials)

Step 1 — Create OIDC Identity Provider in AWS

# GitHub's OIDC provider
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

Step 2 — Create IAM Role

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

Attach policies for ECR push and EKS access:

aws iam create-role \
  --role-name github-actions-eks-deploy \
  --assume-role-policy-document file://trust-policy.json

aws iam attach-role-policy \
  --role-name github-actions-eks-deploy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser

# Add EKS access entry for this role
aws eks create-access-entry \
  --cluster-name my-cluster \
  --principal-arn arn:aws:iam::123456789012:role/github-actions-eks-deploy \
  --type STANDARD

aws eks associate-access-policy \
  --cluster-name my-cluster \
  --principal-arn arn:aws:iam::123456789012:role/github-actions-eks-deploy \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
  --access-scope type=cluster

Build, Push to ECR, Deploy to EKS

name: Deploy to EKS

on:
  push:
    branches: [main]

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

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: my-app
  EKS_CLUSTER_NAME: my-cluster
  DEPLOYMENT_NAME: my-app

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-eks-deploy
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to ECR
        id: ecr-login
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push image
        id: build
        env:
          ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

      - name: Update kubeconfig
        run: |
          aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --region $AWS_REGION

      - name: Deploy to EKS
        run: |
          kubectl set image deployment/$DEPLOYMENT_NAME \
            $DEPLOYMENT_NAME=${{ steps.build.outputs.image }} \
            -n production
          kubectl rollout status deployment/$DEPLOYMENT_NAME -n production --timeout=300s

Reusable Workflow Pattern

# .github/workflows/deploy-reusable.yml
name: Reusable EKS Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      cluster-name:
        required: true
        type: string
      namespace:
        required: true
        type: string
    secrets:
      AWS_ROLE_ARN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1
      - run: |
          aws eks update-kubeconfig --name ${{ inputs.cluster-name }}
          kubectl apply -k overlays/${{ inputs.environment }}/ -n ${{ inputs.namespace }}

Call from another workflow:

# .github/workflows/main.yml
jobs:
  deploy-staging:
    uses: ./.github/workflows/deploy-reusable.yml
    with:
      environment: staging
      cluster-name: staging-cluster
      namespace: staging
    secrets:
      AWS_ROLE_ARN: ${{ secrets.STAGING_AWS_ROLE_ARN }}

  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/deploy-reusable.yml
    with:
      environment: production
      cluster-name: prod-cluster
      namespace: production
    secrets:
      AWS_ROLE_ARN: ${{ secrets.PROD_AWS_ROLE_ARN }}

Helm Deploy with GitHub Actions

- name: Deploy with Helm
  run: |
    helm upgrade --install my-app ./charts/my-app \
      --namespace production \
      --set image.repository=$ECR_REGISTRY/$ECR_REPOSITORY \
      --set image.tag=${{ github.sha }} \
      --wait \
      --timeout 5m

Environment-Based Deployments

Configure GitHub Environments for manual approvals:

  1. Go to Settings → Environments
  2. Create production environment
  3. Add Required reviewers
  4. Add Deployment branches rule (only main)
jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      # ... deploy steps (requires reviewer approval)

Best Practices

  1. Always use OIDC — never store AWS credentials as GitHub secrets
  2. Lock to specific branch in IAM trust policy — prevent deploys from feature branches
  3. Use GitHub Environments for production approval gates
  4. Tag images with Git SHA — direct traceability from deployment to commit
  5. Use reusable workflows — DRY across environments
  6. Add kubectl rollout status — fail the workflow if deployment doesn’t stabilize

Updated: