Skip to content
KubeAtlas
CI/CD GitHub Actions Kubernetes

Deploying to Kubernetes with GitHub Actions: CI/CD Pipeline Design

Onur Ömer Tunç 3 min read

GitHub Actions is a popular starting point for a Kubernetes deploy pipeline. But before moving beyond the basic examples, there are a few critical design decisions to make.

Push-based or GitOps?

The first question: does the pipeline deploy directly to the cluster (push-based), or does it commit to Git and let a tool like Argo CD pull changes into the cluster (pull-based)?

For small teams, push-based is a simpler starting point with less operational overhead. If you have multiple clusters or drift detection matters, move to GitOps. This post covers the push-based approach.

Eliminate the kubeconfig secret with OIDC

The most common mistake with Kubernetes authentication in GitHub Actions: storing a long-lived kubeconfig file as a GitHub secret. Rotating it is difficult, and the blast radius when it leaks is large.

The modern approach: cloud provider OIDC integration. For Azure AKS:

- uses: azure/login@v2
  with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- uses: azure/aks-set-context@v4
  with:
    resource-group: my-rg
    cluster-name: my-aks

With Workload Identity, there is no need for a kubeconfig secret at all. A token is generated automatically for each workflow run and is short-lived.

Pipeline structure

name: Deploy to Kubernetes

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write  # required for OIDC

    steps:
    - uses: actions/checkout@v4

    - name: Build & push Docker image
      uses: docker/build-push-action@v6
      with:
        push: true
        tags: |
          ghcr.io/${{ github.repository }}:${{ github.sha }}
        cache-from: type=gha
        cache-to: type=gha,mode=max

    - name: Set Kubernetes context
      uses: azure/aks-set-context@v4
      with:
        resource-group: ${{ vars.AKS_RG }}
        cluster-name: ${{ vars.AKS_CLUSTER }}

    - name: Deploy with Kustomize
      run: |
        cd k8s/overlays/production
        kustomize edit set image \
          app=ghcr.io/${{ github.repository }}:${{ github.sha }}
        kubectl apply -k .
        kubectl rollout status deployment/app \
          -n backend-prod --timeout=5m

The rollout status command is critical — the pipeline waits for a healthy rollout after deploying. If the timeout passes, the pipeline fails and a rollback hook can be triggered automatically.

Kustomize overlay structure

k8s/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    │   └── kustomization.yaml    # replicas: 1
    ├── uat/
    │   └── kustomization.yaml    # replicas: 2
    └── production/
        └── kustomization.yaml    # replicas: 5

Each overlay defines only what changes — it does not repeat the base. kustomize edit set image writes the image tag into the overlay file, making every deploy fully traceable in Git history.

Rollback strategy

Kubernetes built-in rollback:

# Roll back the last deployment
kubectl rollout undo deployment/app -n backend-prod

# Roll back to a specific revision
kubectl rollout history deployment/app -n backend-prod
kubectl rollout undo deployment/app --to-revision=3 -n backend-prod

On the GitHub Actions side, create a separate workflow_dispatch-triggered workflow for rollbacks. Always using the same deploy mechanism for rollbacks — rather than running ad-hoc kubectl commands — improves auditability and eliminates the “how was this rollback done?” question.

Build cache for faster pipelines

The cache-from: type=gha and cache-to: type=gha,mode=max lines use the GitHub Actions cache layer. Unchanged layers are not rebuilt. For a typical application image, this difference can cut build time from 8–10 minutes down to 2–3 minutes.

What was left out intentionally

Topics not covered here: GitOps with Argo CD, image vulnerability scanning with Trivy, SLSA provenance, Helm chart versioning. Each deserves its own post.

If you want to assess your current pipeline, we can schedule a free 30-minute technical review.

Tags CI/CD GitHub Actions Kubernetes