Skip to main content

Kubernetes Deploy — EKS + Helm

Single workflow: build Docker image → push to ECR → deploy via Helm to EKS. Triggered on push to main. Uses OIDC throughout — no long-lived AWS credentials.

Prerequisites

IAM Role Permissions

The GitHub Actions OIDC role (same setup as the Terraform workflow) needs these additional permissions:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["eks:DescribeCluster"],
"Resource": "arn:aws:eks:REGION:ACCOUNT_ID:cluster/CLUSTER_NAME"
}
]
}

EKS access for helm upgrade is controlled by the cluster's aws-auth ConfigMap (or EKS access entries). Add the GitHub Actions IAM role with appropriate RBAC permissions — see Tips below.

Required Repo Secrets/Variables

NameTypeValue
AWS_ROLE_TO_ASSUMESecretARN of the IAM role
ECR_REPOSITORYVariableECR repo URI (e.g. 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-app)
EKS_CLUSTER_NAMEVariableEKS cluster name
HELM_RELEASE_NAMEVariableHelm release name (e.g. my-app)
HELM_NAMESPACEVariableKubernetes namespace (e.g. default)
AWS_REGIONVariablee.g. eu-west-1

Full Workflow

name: Deploy to EKS

on:
push:
branches: [main]

permissions:
id-token: write
contents: read

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ vars.AWS_REGION }}

# Build and push to ECR
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2

- name: Build Docker image
run: |
docker build \
-t ${{ vars.ECR_REPOSITORY }}:${{ github.sha }} \
-t ${{ vars.ECR_REPOSITORY }}:latest \
.

- name: Push to ECR
run: |
docker push ${{ vars.ECR_REPOSITORY }}:${{ github.sha }}
docker push ${{ vars.ECR_REPOSITORY }}:latest

# Deploy via Helm
- name: Configure kubectl for EKS
run: |
aws eks update-kubeconfig \
--region ${{ vars.AWS_REGION }} \
--name ${{ vars.EKS_CLUSTER_NAME }}

- name: Helm upgrade
run: |
helm upgrade --install ${{ vars.HELM_RELEASE_NAME }} ./helm \
--namespace ${{ vars.HELM_NAMESPACE }} \
--create-namespace \
--set image.repository=${{ vars.ECR_REPOSITORY }} \
--set image.tag=${{ github.sha }} \
--wait \
--timeout 5m

Tips

  • --wait makes Helm block until the rollout is healthy (or timeout). Without it the job succeeds even if pods are crashlooping.
  • --create-namespace is safe to leave on — it's a no-op if the namespace already exists.
  • Tag images with both github.sha (immutable, for tracing deploys) and latest (convenience for local pulls).
  • Add the GitHub Actions IAM role to the EKS aws-auth ConfigMap:
    - rolearn: arn:aws:iam::ACCOUNT_ID:role/github-actions-eks
    username: github-actions
    groups:
    - system:masters # or a tighter RBAC role for production
  • For EKS access entries (newer approach, no ConfigMap editing):
    aws eks create-access-entry \
    --cluster-name MY_CLUSTER \
    --principal-arn arn:aws:iam::ACCOUNT_ID:role/github-actions-eks \
    --type STANDARD

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