Websites12 min read

CI/CD GitHub Actions + Hetzner + Kubernetes: 2026 SaaS pipeline

Mohamed Bah·Fondateur, Kolonell
May 20, 2026
Share:
CI/CD GitHub Actions + Hetzner + Kubernetes: 2026 SaaS pipeline

CI/CD GitHub Actions + Hetzner + Kubernetes: 2026 SaaS pipeline

Websites

GitHub Actions + Hetzner Cloud + Kubernetes = winning combo for cost-controlled B2B SaaS in 2026. AWS EKS costs $70/month minimum for nothing. Hetzner K8s: €8-30/month real cluster. Here's the complete architecture.

TL;DR

- GitHub Actions: CI tests + build + auto deploy.

- Hetzner K8s or Talos: managed or self-managed cluster.

- ArgoCD: GitOps for deploys.

- Total stack: €80-200/month for medium-scale SaaS.

Complete architecture

`

[GitHub Repo]

[GitHub Actions]

├── Test (Vitest, Playwright)

├── Lint (ESLint, Prettier)

├── Build (Next.js, Docker)

├── Push image (GitHub Container Registry)

└── Update GitOps repo

[ArgoCD watches GitOps repo]

[Hetzner K8s cluster]

├── Frontend Next.js

├── Backend API

├── Postgres (Neon or self-host)

└── Redis

`

Step 1 — CI pipeline

`yaml

name: CI

on:

pull_request:

push:

branches: [main]

jobs:

test:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • uses: pnpm/action-setup@v3
  • uses: actions/setup-node@v4

with:

node-version: '20'

cache: 'pnpm'

  • run: pnpm install --frozen-lockfile
  • name: Type-check

run: pnpm tsc --noEmit

  • name: Lint

run: pnpm lint

  • name: Unit tests

run: pnpm test

  • name: E2E tests

run: pnpm playwright test

  • name: Cross-tenant security tests

run: pnpm test:tenant-scoping

build-and-push:

if: github.ref == 'refs/heads/main'

needs: test

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • name: Login to GHCR

uses: docker/login-action@v3

with:

registry: ghcr.io

username: ${{ github.actor }}

password: ${{ secrets.GITHUB_TOKEN }}

  • name: Build and push Docker

uses: docker/build-push-action@v5

with:

push: true

tags: |

ghcr.io/kolonell/app:${{ github.sha }}

ghcr.io/kolonell/app:latest

  • name: Update GitOps manifest

run: |

git clone https://github.com/kolonell/k8s-manifests

cd k8s-manifests

sed -i "s|image: ghcr.io/kolonell/app:.*|image: ghcr.io/kolonell/app:${{ github.sha }}|" prod/deployment.yaml

git commit -am "deploy: ${{ github.sha }}"

git push

`

Step 2 — multi-stage Dockerfile

`dockerfile

FROM node:20-alpine AS builder

WORKDIR /app

COPY package.json pnpm-lock.yaml ./

RUN corepack enable && pnpm install --frozen-lockfile

COPY . .

RUN pnpm build

FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/.next/standalone ./

COPY --from=builder /app/.next/static ./.next/static

COPY --from=builder /app/public ./public

EXPOSE 3000

USER node

CMD ["node", "server.js"]

`

Final image: ~150 MB (vs 800 MB without multi-stage).

Step 3 — Hetzner K8s cluster

`bash

# Create via hetzner.com console (managed Kubernetes)

# Cost: €30/month for control plane + €8-15/node

`

Option B — Self-managed with k3s

More economical:

`bash

# On Hetzner CX21 master node (4 vCPU, 8 GB)

curl -sfL https://get.k3s.io | sh -

sudo cat /etc/rancher/k3s/k3s.yaml

curl -sfL https://get.k3s.io | K3S_URL=https://MASTER_IP:6443 K3S_TOKEN=TOKEN sh -

`

3-node cluster: €24-45/month total.

Step 4 — K8s manifests

`yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: app

namespace: prod

spec:

replicas: 3

selector:

matchLabels:

app: app

template:

metadata:

labels:

app: app

spec:

Need a professional website?

Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.

containers:

  • name: app

image: ghcr.io/kolonell/app:abc123

ports:

  • containerPort: 3000

env:

  • name: DATABASE_URL

valueFrom:

secretKeyRef:

name: app-secrets

key: database-url

resources:

requests:

memory: "256Mi"

cpu: "200m"

limits:

memory: "512Mi"

cpu: "500m"

livenessProbe:

httpGet:

path: /api/health

port: 3000

initialDelaySeconds: 30

periodSeconds: 30

readinessProbe:

httpGet:

path: /api/health

port: 3000

initialDelaySeconds: 5

periodSeconds: 5

---

apiVersion: v1

kind: Service

metadata:

name: app

namespace: prod

spec:

selector:

app: app

ports:

  • port: 80

targetPort: 3000

type: ClusterIP

---

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: app

namespace: prod

annotations:

cert-manager.io/cluster-issuer: letsencrypt-prod

spec:

ingressClassName: traefik

tls:

  • hosts: [app.kolonell.com]

secretName: app-tls

rules:

  • host: app.kolonell.com

http:

paths:

  • path: /

pathType: Prefix

backend:

service:

name: app

port: { number: 80 }

`

Step 5 — ArgoCD GitOps

`bash

kubectl create namespace argocd

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

`

`yaml

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

name: app-prod

spec:

project: default

source:

repoURL: https://github.com/kolonell/k8s-manifests

targetRevision: HEAD

path: prod

destination:

server: https://kubernetes.default.svc

namespace: prod

syncPolicy:

automated:

prune: true

selfHeal: true

`

ArgoCD watches K8s manifests repo + auto-syncs on every push.

Step 6 — secrets management (Sealed Secrets)

`bash

kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml

kubectl create secret generic app-secrets \

--from-literal=database-url='postgres://...' \

--dry-run=client -o yaml > secret.yaml

kubeseal -o yaml < secret.yaml > sealed-secret.yaml

# Commit sealed-secret.yaml to Git (encrypted)

`

Encrypted Git secrets, only decryptable by the cluster.

Step 7 — K8s observability

`bash

helm install prometheus prometheus-community/kube-prometheus-stack \

--namespace monitoring --create-namespace

`

Included dashboards: pods CPU/RAM, latency, error rate, request volume.

Typical monthly costs

ComponentSpecCost
Hetzner K8s control planeManaged€30/month
Worker nodes (3× CX21)4 vCPU 8 GB each€24/month
Postgres (Neon Pro)5 GB€19/month
Redis (Upstash)Pay-as-you-go€5-15/month
Cloudflare CDNFree + Pro€0-25/month
Sentry50K errors€26/month
GitHub Actions3K minutes€0 (free tier)
Monthly total~€110-160

Compare AWS EKS: ~$300-500/month for equivalent. 60-70% savings.

Real case — Dakar SaaS (350 tenants, 1.2M req/day)

MetricStack
ClusterHetzner K8s 5 nodes
Monthly infra cost€145
Deploy frequency12-18/day
Deploy duration4-7 min
Rollback time30 sec (ArgoCD)
Uptime99.96%

Common pitfalls

  • No readiness probe — pods receive traffic before being ready.
  • Missing resources requests/limits — random OOM kills.
  • Plaintext secrets in Git — catastrophic leak. Sealed Secrets mandatory.
  • No rolling update strategy — downtime on every deploy.
  • No K8s state backup — Velero or similar for DR.

FAQ

Q: K8s vs Vercel for Next.js?

A: Vercel = simple but expensive at scale + lock-in. K8s = complex but unlimited economic scaling + portable.

Q: k3s vs k0s vs RKE?

A: k3s = simplest + battle-tested. k0s = modern alternative. RKE = SUSE enterprise.

Q: Multi-region multi-cluster?

A: For 99% Africa B2B SaaS: single Frankfurt cluster enough. Multi-region useful past $100M ARR.

Conclusion

CI/CD GitHub Actions + Hetzner K8s + ArgoCD = pro 2026 DevOps stack for serious B2B SaaS at reasonable cost. 2-4 weeks initial setup. ROI: dev agility + independent scaling + zero cloud lock-in.

Tags:#CI/CD#GitHub Actions#Hetzner#Kubernetes#ArgoCD#DevOps
Share:

Mohamed Bah

Fondateur, Kolonell

Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.