GitHub Actions Kubernetes Pipeline#
This guide builds a complete pipeline: push code, build a container image, validate the Helm chart, and deploy to Kubernetes. Each stage gates the next, so broken images never reach your cluster.
Pipeline Overview#
The pipeline has four stages:
- Build and push the container image to GitHub Container Registry (GHCR).
- Lint and validate the Helm chart with
helm lintandkubeconform. - Deploy to dev automatically on pushes to
main. - Promote to staging and production via manual approval.
Complete Workflow File#
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options: [dev, staging, production]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
validate:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
- name: Helm lint
run: helm lint ./charts/my-app -f charts/my-app/values.yaml
- name: Install kubeconform
run: |
curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz \
| tar xz -C /usr/local/bin
- name: Validate rendered templates
run: |
helm template my-app ./charts/my-app \
--set image.tag=${{ needs.build.outputs.image-tag }} \
| kubeconform -strict -summary \
-kubernetes-version 1.29.0
deploy-dev:
runs-on: ubuntu-latest
needs: [build, validate]
if: github.ref == 'refs/heads/main'
environment: dev
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
- name: Set up kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_DEV }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./charts/my-app \
--namespace my-app-dev \
--create-namespace \
-f charts/my-app/values-dev.yaml \
--set image.tag=${{ needs.build.outputs.image-tag }} \
--wait --timeout 300s
- name: Verify deployment
run: kubectl rollout status deployment/my-app -n my-app-dev --timeout=120s
deploy-staging:
runs-on: ubuntu-latest
needs: [build, validate, deploy-dev]
environment: staging
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
- name: Set up kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_STAGING }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./charts/my-app \
--namespace my-app-staging \
--create-namespace \
-f charts/my-app/values-staging.yaml \
--set image.tag=${{ needs.build.outputs.image-tag }} \
--wait --timeout 300s
deploy-production:
runs-on: ubuntu-latest
needs: [build, validate, deploy-staging]
environment: production
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
- name: Set up kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_PROD }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./charts/my-app \
--namespace my-app-prod \
--create-namespace \
-f charts/my-app/values-production.yaml \
--set image.tag=${{ needs.build.outputs.image-tag }} \
--wait --timeout 300sKey Design Decisions#
Image Tagging with Git SHA#
The docker/metadata-action generates tags from the git SHA. This creates immutable, traceable image tags – you can always identify exactly which commit produced a given deployment.
Environment Promotion#
GitHub Environments (dev, staging, production) in Settings provide:
- Required reviewers: Gate production deploys behind manual approval.
- Wait timers: Force a delay between staging and production.
- Deployment branch rules: Restrict which branches can deploy to production.
The environment: key on each job links it to the GitHub Environment. The staging and production environments should have required reviewers configured.
Secrets Management#
Store each cluster’s kubeconfig as a base64-encoded GitHub Actions secret:
# Encode your kubeconfig
cat ~/.kube/config | base64 | pbcopy
# Add as secret: Settings -> Secrets -> Actions
# KUBECONFIG_DEV, KUBECONFIG_STAGING, KUBECONFIG_PRODScope secrets to environments so the dev kubeconfig cannot access production. For tighter security, use a service account token scoped to the deployment namespace rather than a full admin kubeconfig.
Helm Validation in CI#
helm lint catches template syntax errors. kubeconform validates the rendered output against the Kubernetes API schema, catching issues like invalid field names or wrong API versions before anything hits the cluster. Together, these prevent the majority of deployment failures.
Handling Rollbacks#
If a deployment fails, Helm automatically rolls back when --wait detects unhealthy pods. For manual rollbacks:
# List release history
helm history my-app -n my-app-prod
# Roll back to previous revision
helm rollback my-app 0 -n my-app-prodAutomate rollback in the workflow by adding a failure step:
- name: Rollback on failure
if: failure()
run: helm rollback my-app 0 -n my-app-prod --waitMoving Beyond This Pipeline#
Once this pipeline is stable, consider adding: integration tests against the dev deployment before promoting, Slack or Teams notifications on deploy success or failure, ArgoCD for GitOps-style deployments (the Helm chart lives in git, ArgoCD syncs it), and image scanning with Trivy before pushing to the registry.