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 upgradeis controlled by the cluster'saws-authConfigMap (or EKS access entries). Add the GitHub Actions IAM role with appropriate RBAC permissions — see Tips below.
Required Repo Secrets/Variables
| Name | Type | Value |
|---|---|---|
AWS_ROLE_TO_ASSUME | Secret | ARN of the IAM role |
ECR_REPOSITORY | Variable | ECR repo URI (e.g. 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-app) |
EKS_CLUSTER_NAME | Variable | EKS cluster name |
HELM_RELEASE_NAME | Variable | Helm release name (e.g. my-app) |
HELM_NAMESPACE | Variable | Kubernetes namespace (e.g. default) |
AWS_REGION | Variable | e.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
--waitmakes Helm block until the rollout is healthy (or timeout). Without it the job succeeds even if pods are crashlooping.--create-namespaceis safe to leave on — it's a no-op if the namespace already exists.- Tag images with both
github.sha(immutable, for tracing deploys) andlatest(convenience for local pulls). - Add the GitHub Actions IAM role to the EKS
aws-authConfigMap:- rolearn: arn:aws:iam::ACCOUNT_ID:role/github-actions-eksusername: github-actionsgroups:- 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 STANDARDaws 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