Deployment
Local dev that mirrors production. One Helm chart, every environment. ArgoCD GitOps with multi-env values overrides.
The problem
Local docker-compose drifts from prod. 'Works on my k8s' doesn't work.
Most teams use docker-compose locally and Kubernetes in prod. The two diverge immediately: health checks behave differently, secrets are loaded differently, ingress is a different layer entirely. The first time you ship a Kubernetes-specific behavior — service mesh policy, pod disruption budget, init container ordering — you find out locally never tested it.
Teams that do use Kubernetes locally usually use Docker Desktop's embedded cluster — which is slow, resource-heavy, and behaves differently from any managed cloud cluster.
How it works
k3d locally + Helm + ArgoCD GitOps everywhere else — one chart.
Sage uses k3d (k3s in Docker) for local development. k3s is the real Kubernetes distribution used by Rancher and Civo Cloud; it conforms to upstream k8s. Ingress, storage classes, RBAC, network policies — all behave the way they will in production.
The same Helm chart deploys locally and to GKE / EKS / AKS. ArgoCD applications are pre-wired with automated sync, prune, and self-heal. Multi-env values files handle environment-specific overrides. The Dark Factory migration-gated pre-upgrade job ships in the chart by default.
- k3d for local dev — real Kubernetes (k3s) in Docker; ingress + storage class identical to prod
- One Helm chart at infra/helm/<product>/ — values.yaml + values-staging.yaml + values-prod.yaml
- ArgoCD applications pre-wired for staging + prod with automated sync, prune, self-heal
- Multi-cloud ready — GKE, EKS, AKS, or any conformant Kubernetes (no cloud-specific resources)
- External Secrets Operator pre-configured for Doppler / Vault / AWS Secrets Manager
- Database migrations gated by Dark Factory — pre-upgrade Job in the Helm chart
# infra/helm/my-product/values.yaml — single chart, env-specific overrides
backend:
image:
repository: ghcr.io/me/my-product-backend
pullPolicy: IfNotPresent
replicas: 2
resources:
requests: { cpu: 250m, memory: 512Mi }
limits: { cpu: 1000m, memory: 1Gi }
web:
image:
repository: ghcr.io/me/my-product-web
replicas: 2
env:
CEREBE_API_URL: https://cerebe.momentiq.ai
OTEL_EXPORTER_OTLP_ENDPOINT: https://tempo.example.com:4317
# infra/argo/my-product.yaml — ArgoCD application
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
source:
helm:
valueFiles: [values.yaml, values-staging.yaml]
destination:
server: https://k8s.staging.example.com
namespace: my-product
syncPolicy:
automated: { prune: true, selfHeal: true }
# Local dev — same Helm chart, k3d cluster
$ make dev
→ k3d cluster created · ingress on :80/:443
→ helm install my-product ./infra/helm/my-product -f values.local.yaml
→ backend ready · web ready · ingress live at https://my-product.localhost Get Started
Local that mirrors prod. One chart everywhere.
k3d for development, the same Helm chart for GKE / EKS / AKS production. No docker-compose drift. No 'works on my k8s' surprises.