Skip to main content

The Problem

Deploying to Kubernetes without GitOps often means:
  • Imperative chaos: kubectl apply from laptops, scripts that “usually work,” and no clear record of what’s actually running.
  • Drift: Someone makes a quick fix directly in the cluster. Now the running state doesn’t match any source of truth.
  • No audit trail: “Who deployed that change?” requires digging through CI logs, Slack messages, and hoping someone remembers.
  • Environment inconsistency: Staging and production diverge over time because deployments are manual and error-prone.
  • Rollback friction: Something breaks in production. How do you get back to the previous state? Which commit was that?

How Kube Starter Kit Addresses This

I’ve implemented GitOps with ArgoCD, where Git is the single source of truth for your cluster state: Git as the source of truth: Everything running in the cluster is defined in Git. If it’s not in the repo, it shouldn’t be in the cluster. Continuous reconciliation: ArgoCD watches your Git repository and automatically syncs the cluster to match. Drift gets corrected automatically. App of Apps pattern: A hierarchical structure where one ArgoCD Application manages other Applications. Add a new service by adding a file; ArgoCD picks it up automatically. Environment separation: Each cluster (staging, production) has its own rendered manifests. Same source templates, different configurations. Audit everything: Every deployment is a Git commit. Who changed what, when, and why is all in the commit history.

What’s Included

ArgoCD Structure

kubernetes/
├── src/                          # Source templates
│   ├── argocd/
│   │   ├── argocd/              # ArgoCD itself (bootstraps everything)
│   │   ├── infrastructure/      # Infrastructure apps definition
│   │   └── services/            # Application services definition
│   ├── infrastructure/          # Infrastructure components
│   │   ├── cert-manager/
│   │   ├── external-dns/
│   │   ├── traefik/
│   │   └── ...
│   └── services/                # Your applications
│       └── go-backend/
└── rendered/                    # Environment-specific output
    ├── staging/
    └── production/

App of Apps Hierarchy

ArgoCD uses a three-tier Application structure:
argocd-app-of-apps (root)
├── argocd-app-of-apps (manages itself 🔄)
├── infrastructure-app-of-apps
│   ├── cert-manager
│   ├── external-dns
│   ├── traefik
│   ├── external-secrets
│   ├── karpenter
│   └── ...
└── services-app-of-apps
    ├── go-backend
    └── ...
The root Application manages itself and the infrastructure and services Applications, which in turn manage individual components. This creates a clean separation between platform infrastructure and application workloads, while ensuring the entire ArgoCD configuration is also GitOps-managed.

Project Organization

ArgoCD Projects provide logical separation:
ProjectPurpose
argocdArgoCD’s own configuration and app-of-apps
infrastructurePlatform components (cert-manager, ingress, etc.)
servicesApplication workloads

Deployment Flow

1

Make changes

Update Kubernetes manifests or Helm values in the src/ directory.
2

Render manifests locally

Run mise run render-cluster <cluster> from the application directory to generate environment-specific manifests to rendered/staging/ or rendered/production/.
3

Commit and push

CI validates that rendered manifests are up-to-date (no git diff after re-rendering).
4

ArgoCD detects change

ArgoCD notices the rendered manifests differ from cluster state.
5

Automatic sync

ArgoCD applies the changes to the cluster. With auto-sync enabled, this happens automatically.
6

Verification

ArgoCD reports sync status and health. Failed deployments are visible immediately.

Configuration Management

Applications are enabled/disabled via a simple values file:
# infrastructure/values.yaml
applications:
  cert-manager:
    enabled: true
  external-dns:
    enabled: true
  traefik:
    enabled: true
  karpenter:
    enabled: true
  envoy-gateway:
    enabled: false  # Not using this yet
To add a new infrastructure component, create its directory in src/infrastructure/ and add an entry to the values file. ArgoCD picks it up on the next sync.

Repository Access

ArgoCD authenticates to your Git repository using SSH deploy keys. During bootstrap, a mise task generates a key pair and creates a Kubernetes Secret with the private key. You add the public key to your GitHub repository as a deploy key. No credentials in Git. ArgoCD discovers the repository secret automatically via the argocd.argoproj.io/secret-type=repository label. See cluster bootstrap for setup instructions.

Key Design Decisions

DecisionRationale
ArgoCD over FluxBetter UI for visibility, more mature ecosystem, easier onboarding for teams new to GitOps.
App of Apps over ApplicationSetsMore explicit control over the hierarchy. ApplicationSets are powerful but can be harder to reason about.
Rendered manifests in GitWhat’s deployed is exactly what’s in Git. No runtime templating surprises. Easier to audit and diff.
Bootstrap-generated deploy keysDeploy keys are generated during bootstrap and stored as Kubernetes Secrets. Simple, no external dependencies.
Auto-sync enabledReduces manual toil. If you commit it, it deploys. Rollback is just reverting a commit.