GitOps repo for bootstrapping Kubernetes clusters with ArgoCD using the App of Apps pattern.
Full documentation is available at the MkDocs site. To preview locally:
pip install mkdocs-material
mkdocs serveOnline documentation available at Cluster Boostrap Docs
Runtime:
kubectlconfigured with access to the target clusterhelm(for local template testing)sopsandage(for secrets encryption/decryption) orgit-crypt(alternative encryption backend)- SSH private key with read access to this repo
Development (optional):
go1.25+ (to build the CLI from source)task(Task runner)pre-commit(pre-commit hooks)
Replace the default user-cube/cluster-bootstrap with your organization and repository:
./cluster-bootstrap-cli/cluster-bootstrap-cli template customize --org mycompany --repo k8s-gitopsThis updates Git URLs, GitHub badges, Go module paths, and documentation throughout the codebase. See Template documentation for details.
Option A: Homebrew (recommended)
brew install user-cube/tap/cluster-bootstrap-cliOption B: Install globally with go install
# From local source
go install ./cluster-bootstrap-cli
# Or from GitHub
go install github.com/user-cube/cluster-bootstrap/cluster-bootstrap-cli@latest
# Verify installation
cluster-bootstrap-cli --helpOption C: Build locally
task build
# Binary will be at: cluster-bootstrap-cli/cluster-bootstrap-cli
./cluster-bootstrap-cli/cluster-bootstrap-cli --helpOption D: Use task helper
task install
# Builds and installs to $(go env GOPATH)/bin./cluster-bootstrap-cli/cluster-bootstrap-cli init./cluster-bootstrap-cli/cluster-bootstrap-cli bootstrap devTo bootstrap Cilium as the cluster CNI before ArgoCD, opt in explicitly:
./cluster-bootstrap-cli/cluster-bootstrap-cli bootstrap dev --enable-ciliumIf the ArgoCD repo-server uses helm-secrets to decrypt SOPS-encrypted values
from Git, create its sops-age-key Secret during bootstrap:
./cluster-bootstrap-cli/cluster-bootstrap-cli bootstrap prod \
--age-key-file ./age-key.txt \
--store-sops-age-keyThis will:
- Decrypt environment secrets (SOPS + age by default, or git-crypt)
- Create the
argocdnamespace and SSH credentials secret - Optionally store the SOPS age key for ArgoCD to decrypt SOPS values in-cluster
- Optionally install Cilium and wait for it to become healthy
- Install ArgoCD via Helm
- Deploy the root App of Apps Application, enabling its Cilium child Application when requested
Before touching the cluster, bootstrap announces the target Kubernetes context with a 10 second countdown (skip with --yes), and stops if the cluster already has an App of Apps.
π‘οΈ Safe by default: A cluster that already has an
app-of-appsApplication is reported and left untouched. Re-run with--forceto bootstrap it again β the operations themselves are idempotent, detecting existing resources and updating them instead of failing.
The bootstrap command generates comprehensive reports with timing metrics, resource operations, and health check results:
# Default: Human-readable summary report
./cluster-bootstrap-cli/cluster-bootstrap bootstrap dev
# JSON report for automation/metrics collection
./cluster-bootstrap-cli/cluster-bootstrap bootstrap dev --report-format json
# Save report to file
./cluster-bootstrap-cli/cluster-bootstrap bootstrap dev --report-output bootstrap-report.jsonSee Bootstrap Reports documentation for details.
./cluster-bootstrap-cli/cluster-bootstrap init --provider git-crypt
./cluster-bootstrap-cli/cluster-bootstrap bootstrap dev --encryption git-cryptIf your Kubernetes manifests live in a subdirectory (e.g. k8s/), you need to configure both the CLI and values file:
- Update
apps/values.yamlto set the base path:
repo:
basePath: "k8s" # Set to your subdirectory name- Run bootstrap using either method:
From repository root:
./k8s/cli/cluster-bootstrap --base-dir ./k8s bootstrap dev \
--app-path k8s/apps \
--wait-for-health -vOr from inside the subdirectory (both work):
cd k8s
# Relative path
./cluster-bootstrap-cli/cluster-bootstrap bootstrap dev --app-path apps --wait-for-health -v
# Or full path
./cluster-bootstrap-cli/cluster-bootstrap bootstrap dev --app-path k8s/apps --wait-for-health -vKey points:
- The CLI automatically detects if you're in a Git subdirectory
- Works with both relative (
apps) and full paths (k8s/apps) - Strips prefixes intelligently for local validation
repo.basePath: "k8s"in values.yaml ensures component paths include the subdirectory prefix- Choose whichever feels most natural to you!
kubectl port-forward svc/argocd-server -n argocd 8080:443Get the initial admin password:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -dCLI bootstrap β optional Cilium β ArgoCD + App of Apps
β β
βββ Cilium Application apps/ (root chart)
β
components/argocd/
components/xxx/
ArgoCD manages itself β changes pushed to this repo are automatically synced.
The apps/ chart uses a single dynamic template that iterates over a components map defined in apps/values.yaml. Adding a new component requires only a new entry in the values β no template files to create or copy.
| Component | Namespace | Sync Wave | Description |
|---|---|---|---|
| Cilium | kube-system |
Bootstrap | Optional CNI, enabled only with --enable-cilium |
| ArgoCD | argocd |
0 | Self-managed GitOps controller |
| Vault | vault |
1 | Secrets management |
| External Secrets | external-secrets |
1 | Syncs external secrets into Kubernetes |
| Prometheus Operator CRDs | monitoring |
2 | CRDs for the monitoring stack |
| ArgoCD Repo Secret | argocd |
2 | SSH credentials for repo access |
| Reloader | reloader |
2 | Restarts pods on ConfigMap/Secret changes |
| Kube Prometheus Stack | monitoring |
3 | Prometheus monitoring stack |
| Trivy Operator | trivy-system |
3 | Vulnerability scanning |
| Command | Description |
|---|---|
bootstrap <env> |
Full cluster bootstrap (decrypt secrets, install ArgoCD, deploy App of Apps). Generates comprehensive reports with timing metrics and resource operations. Stops on an already bootstrapped cluster unless --force is passed; idempotent when forced. |
template customize |
Customize the template with your organization and repository (replaces placeholders in configs, docs, and code) |
doctor |
Run prerequisite checks for tooling and cluster access |
status <env> |
Show cluster status and component information |
validate <env> |
Validate local config, secrets, and optional cluster access |
init |
Interactive setup for encryption config and secrets files |
vault-token |
Store Vault root token as Kubernetes secret |
gitcrypt-key |
Store git-crypt symmetric key as Kubernetes secret |
| Flag | Default | Description |
|---|---|---|
--base-dir |
. |
Base directory for repo content (local file resolution) |
-v, --verbose |
false |
Enable verbose output |
--verbose-with-templates |
false |
Write rendered Helm manifests to a file during bootstrap (implies verbose; output may contain sensitive values) |
pre-commit installRun task --list to see all available tasks. The most common ones:
task test # Run Go tests with coverage
task lint # Run golangci-lint
task helm-lint # Lint Helm charts with templates
task fmt # Format Go source files
task vet # Run Go vet
task docs-serve # Serve MkDocs documentation locallySOPS (default): secrets.example.enc.yaml contains the expected secrets structure. To create a new environment:
cp secrets.example.enc.yaml secrets.myenv.enc.yaml
sops --encrypt --in-place secrets.myenv.enc.yamlOr use the CLI interactively: ./cluster-bootstrap-cli/cluster-bootstrap init myenv
git-crypt: Secrets are stored as plaintext YAML (secrets.<env>.yaml) and encrypted transparently by git-crypt on commit:
git-crypt init
./cluster-bootstrap-cli/cluster-bootstrap init --provider git-crypt myenvTo use a custom .sops.yaml path, set SOPS_CONFIG in your .env:
SOPS_CONFIG=/path/to/custom/.sops.yaml| Environment | Values File | Description |
|---|---|---|
| dev | apps/values/dev.yaml |
Local/development clusters, minimal resources |
| staging | apps/values/staging.yaml |
Pre-production, moderate resources |
| prod | apps/values/prod.yaml |
Production, HA configuration |
Environment files only need to set the environment key. Component defaults (namespace, sync wave, syncOptions, etc.) are defined in apps/values.yaml. To disable a component per environment:
# apps/values/dev.yaml
environment: dev
components:
trivy-operator:
enabled: false