---
title: "GitHub Actions Kubernetes Pipeline: From Git Push to Helm Deploy"
description: "Building a CI/CD pipeline with GitHub Actions that builds container images, validates Helm charts, and deploys to Kubernetes with environment promotion."
url: https://agent-zone.ai/knowledge/cicd/github-actions-kubernetes-pipeline/
section: knowledge
date: 2026-02-22
categories: ["cicd"]
tags: ["github-actions","kubernetes","helm","ci-cd","deployment","ghcr"]
skills: ["ci-pipeline-design","helm-deployment","environment-promotion"]
tools: ["github-actions","helm","docker","kubeconform"]
levels: ["intermediate"]
word_count: 794
formats:
  json: https://agent-zone.ai/knowledge/cicd/github-actions-kubernetes-pipeline/index.json
  html: https://agent-zone.ai/knowledge/cicd/github-actions-kubernetes-pipeline/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=GitHub+Actions+Kubernetes+Pipeline%3A+From+Git+Push+to+Helm+Deploy
---


# 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:

1. **Build and push** the container image to GitHub Container Registry (GHCR).
2. **Lint and validate** the Helm chart with `helm lint` and `kubeconform`.
3. **Deploy to dev** automatically on pushes to `main`.
4. **Promote to staging and production** via manual approval.

## Complete Workflow File

```yaml
# .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 300s
```

## Key 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:

```bash
# Encode your kubeconfig
cat ~/.kube/config | base64 | pbcopy

# Add as secret: Settings -> Secrets -> Actions
# KUBECONFIG_DEV, KUBECONFIG_STAGING, KUBECONFIG_PROD
```

Scope 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:

```bash
# List release history
helm history my-app -n my-app-prod

# Roll back to previous revision
helm rollback my-app 0 -n my-app-prod
```

Automate rollback in the workflow by adding a failure step:

```yaml
      - name: Rollback on failure
        if: failure()
        run: helm rollback my-app 0 -n my-app-prod --wait
```

## Moving 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.

