This guide explains how to bootstrap a complete GitOps environment using Cluster-Forge's five-step deployment model. The bootstrap process establishes ArgoCD, OpenBao, and Gitea as foundation components, then creates the cluster-forge Application which manages all remaining components via ArgoCD.
- Kubernetes cluster (1.33+ recommended, running and accessible via
kubectl) - Tools installed:
kubectlwith cluster-admin accesshelm(3.0+)openssl(for password generation)yq(v4+)
./scripts/bootstrap.sh <domain> [--cluster-size=small|medium|large]- domain (required): Cluster domain for all services (e.g.,
example.com,192.168.1.100.nip.io)
- --apps=APP1,APP2: Deploy only specified components (default: applies to cluster)
- Bootstrap apps:
namespaces,argocd,openbao,gitea,cluster-forge - Child apps: Any app from enabledApps list (see values_{size}.yaml for app names)
- Use with
--template-onlyto render instead of applying
- Bootstrap apps:
- --cluster-size
[small|medium|large]: Cluster size configuration (default:medium) - --template-only, -t: Output YAML manifests to stdout instead of applying to cluster
- --target-revision, -r: cluster-forge git revision for ArgoCD to sync from
- --skip-deps: Skip dependency checking (for advanced users)
- --airm-image-repository=REPO: Custom AIRM container image repository for air-gapped deployments
- --help, -h: Show usage information
# Basic usage with default medium cluster size
./scripts/bootstrap.sh 192.168.1.100.nip.io
# Large cluster
./scripts/bootstrap.sh example.com --cluster-size=large
# Deploy only specific components
./scripts/bootstrap.sh example.com --apps=openbao,openbao-init
# Render templates for debugging (doesn't apply)
./scripts/bootstrap.sh example.com --apps=gitea --template-only
# Deploy from specific git branch
./scripts/bootstrap.sh example.com --target-revision=feature-branch
./scripts/bootstrap.sh example.com --CLUSTER_SIZE=large
# Custom AIRM image repository
./scripts/bootstrap.sh example.com --airm-image-repository=ghcr.io/mycompany
# Air-gapped deployment with local registry
./scripts/bootstrap.sh 192.168.1.100.nip.io --cluster-size=small --airm-image-repository=harbor.internal.com/airmThe bootstrap script uses a five-step deployment model:
- The pre_cleanup function performs selective cleanup, only affects cf-gitea and cf-openbao namespaces
- Detects previous installations by checking for completed gitea-init-job
- Removes Gitea resources to enable fresh deployment
- Deletes OpenBao initialization jobs and temporary files
- Ensures clean state for new bootstrap
1. Configuration Preparation
- Validates required domain argument
- Validates cluster size (small, medium, or large)
- Merges base
values.yamlwith size-specific overridesvalues_<size>.yaml - Sets
global.domainandglobal.clusterSizein merged configuration
2. Namespace Creation Creates three namespaces for bootstrap components:
argocd- GitOps controllercf-gitea- Git repository servercf-openbao- Secrets management system
3. ArgoCD Bootstrap
- Extracts ArgoCD values from merged configuration
- Deploys ArgoCD using
helm templatewith server-side apply - Uses
--field-manager=argocd-controllerto match ArgoCD's self-management - Waits for all ArgoCD components to be ready:
- application-controller StatefulSet
- applicationset-controller Deployment
- redis Deployment
- repo-server Deployment
4. Gitea Bootstrap
- Generates random admin password using
openssl rand -hex 16 - Creates
initial-cf-valuesConfigMap with merged configuration - Creates
gitea-admin-credentialssecret - Extracts Gitea values from merged configuration
- Deploys Gitea using
helm template - Waits for Gitea deployment to be ready
- Runs initialization job (
gitea-init-job) which:- Creates admin API token
- Creates
cluster-orgorganization - Clones and pushes cluster-forge repository from initial-cf-values ConfigMap
- Creates cluster-values repository with configuration
5. ClusterForge Application Deployment
- Renders root helm chart with merged configuration
- Creates
cluster-forgeApplication resource in ArgoCD - ArgoCD syncs all remaining components in wave order:
- Wave -70: OpenBao (secrets management)
- Wave -60: OpenBao configuration
- Wave -50: OpenBao initialization job
- Wave -40: External Secrets, Cert-Manager
- Wave -30 to 0: All other applications
Key Improvement: OpenBao is now managed by ArgoCD rather than bootstrapped separately, simplifying the bootstrap process while maintaining proper dependency ordering through sync waves.
- When
externalValues.enabled: true, uses multi-source feature:- Source 1: cluster-forge repo (root/ helm chart)
- Source 2: cluster-values repo (custom values.yaml)
- ArgoCD manages the complete application lifecycle
- Proper dependency ordering ensures OpenBao is ready before applications that depend on secrets
7. Cleanup
- Removes temporary merged values files from /tmp/
ClusterForge uses a layered configuration approach with YAML merge semantics:
-
Base values (
root/values.yaml):- Contains all app definitions
- Defines default configuration for all apps
- Specifies
enabledAppslist (alpha-sorted) - Configured with:
clusterForge.repoUrl- Points to Gitea service URL (local mode) or GitHub (external mode)clusterForge.targetRevision- Version/branch to deployexternalValues.enabled: true- Enables dual-repository patternexternalValues.repoUrl- Points to cluster-values repo in Giteaglobal.domain- Set by bootstrap scriptglobal.clusterSize- Set by bootstrap script
-
Size-specific values (
root/values_<size>.yaml):- Override base values for specific cluster sizes
- Define resource limits and requests
- Single node (small and medium) RWO local-path storage
- Multinode (large) RWX storage
- Modify replica counts and HA settings
- Add size-specific enabled apps (e.g.,
kyverno-policies-storage-local-pathfor small/medium) - Available sizes:
small,medium,large - Uses DRY principle - only contains differences from base
-
External values (
cluster-values/values.yamlin Gitea):- Created during bootstrap in the
cluster-valuesrepository - Contains cluster-specific overrides
- Can be modified post-bootstrap for customizations
- Structure:
clusterForge: repoURL: http://gitea-http.cf-gitea.svc:3000/cluster-org/cluster-forge.git path: root targetRevision: main global: clusterSize: medium # Set by --cluster-size flag domain: example.com # Set by domain argument # AIRM Image Repository Configuration (optional, only when AIRM_IMAGE_REPOSITORY is set) airm-api: airm: backend: image: repository: ghcr.io/mycompany/airm-api frontend: image: repository: ghcr.io/mycompany/airm-ui airm-dispatcher: airm: dispatcher: image: repository: ghcr.io/mycompany/airm-dispatcher
- Created during bootstrap in the
When ArgoCD renders the cluster-forge application, values are merged in this order (later values override earlier):
- Base
values.yaml - Size-specific
values_<size>.yaml - External
cluster-values/values.yamlfrom Gitea
Each cluster size is optimized for different resource constraints:
- Small: Development/testing environments, minimal resources
- Medium (default): Production-ready, balanced configuration
- Large: High-availability, maximum performance
Size-specific configurations typically adjust:
- Component replica counts (ArgoCD, PostgreSQL, etc.)
- Resource limits and requests (CPU, memory)
- Storage sizes (PVC, retention periods)
- High-availability features (Redis HA, multiple replicas)
The bootstrap script creates the root cluster-forge Application in ArgoCD, which implements an app-of-apps pattern.
The cluster-forge Application is defined in root/templates/cluster-forge.yaml:
The root chart renders individual Application resources for each app listed in enabledApps using the template in root/templates/cluster-apps.yaml.
Each child application includes:
- Namespace: Target namespace for the application
- Path: Location of helm chart or manifests in
sources/<path> - Values: Configuration from
apps.<name>.valuesObjectorvaluesFile - Sync wave: Deployment order (lower numbers deploy first)
- Sync policy: Automated with prune and self-heal enabled
- Ignore differences: Optional resource-specific ignore rules
Example child application configuration in values:
apps:
argocd:
path: argocd/8.3.5
namespace: argocd
syncWave: -3
valuesObject:
# ArgoCD-specific values
helmParameters:
- name: global.domain
value: "argocd.{{ .Values.global.domain }}"-
ArgoCD:
# Initial admin password kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d # Access URL (replace with your domain) echo "https://argocd.${DOMAIN}"
-
Gitea:
# Admin username kubectl -n cf-gitea get secret gitea-admin-credentials -o jsonpath="{.data.username}" | base64 -d # Admin password kubectl -n cf-gitea get secret gitea-admin-credentials -o jsonpath="{.data.password}" | base64 -d # API token (created by init job) kubectl -n cf-gitea get secret gitea-admin-token -o jsonpath="{.data.token}" | base64 -d # Access URL (replace with your domain) echo "https://gitea.${DOMAIN}"
-
OpenBao:
# Root token kubectl -n cf-openbao get secret openbao-keys -o jsonpath='{.data.root_token}' | base64 -d # Unseal keys (stored in openbao-keys secret) kubectl -n cf-openbao get secret openbao-keys -o jsonpath='{.data.unseal_keys_b64}' | base64 -d
-
Keycloak (deployed by ArgoCD):
# Admin password kubectl -n keycloak get secret keycloak-credentials -o jsonpath="{.data.KEYCLOAK_INITIAL_ADMIN_PASSWORD}" | base64 -d # Dev user password kubectl -n keycloak get secret airm-devuser-credentials -o jsonpath="{.data.KEYCLOAK_INITIAL_DEVUSER_PASSWORD}" | base64 -d
If the Gitea initialization job fails during repository migration:
# Check job logs
kubectl logs -n cf-gitea job/gitea-init-job
# The job automatically retries migration up to 5 times
# If it continues failing, check Gitea pod logs
kubectl logs -n cf-gitea deploy/gitea -c giteaProduction mode only
If OpenBao initialization fails:
# Check init job logs
kubectl logs -n cf-openbao job/openbao-init-job
# Verify OpenBao is running
kubectl get pods -n cf-openbao
# Re-run bootstrap (pre-cleanup will handle the retry)
./bootstrap.sh your-domain.comIf applications aren't deploying:
# Check cluster-forge app status
kubectl get application cluster-forge -n argocd -o yaml
# Check individual app status
kubectl get applications -n argocd
# View app details in ArgoCD UI
# https://argocd.your-domain.comThe bootstrap script creates temporary merged values at /tmp/merged_values.yaml for debugging. You can inspect this file during bootstrap to see the final merged configuration.
After bootstrap completes in production mode, you can customize the cluster by modifying the cluster-values repository in Gitea:
- Access Gitea at
https://gitea.${DOMAIN} - Navigate to
cluster-org/cluster-valuesrepository - Edit
values.yamlto add/override configuration - Commit changes
- ArgoCD will automatically detect and apply changes
Example customizations in cluster-values/values.yaml:
# Override app-specific values
apps:
keycloak:
valuesObject:
replicas: 2
resources:
requests:
memory: "1Gi"
# Disable specific apps
enabledApps:
- argocd
- gitea
# ... list only apps you want enabled
# Add custom global values
global:
myCustomValue: "something"The --apps flag allows you to deploy only specific components instead of the full stack. This is useful for:
- Development workflows: Deploy only the components you're working on
- Troubleshooting: Deploy components individually to isolate issues
- Testing: Validate specific component configurations
- Incremental deployment: Add components to an existing cluster
These are the core infrastructure components deployed manually via helm template:
namespaces- Creates required namespaces (argocd, cf-gitea, cf-openbao)argocd- GitOps controller for managing all other componentsgitea- Self-hosted Git server for cluster-forge and cluster-values repositoriescluster-forge- ArgoCD parent application that manages all child apps
Any application listed in enabledApps from values.yaml can be deployed individually:
# Deploy only OpenBao components
./scripts/bootstrap.sh example.com --apps=openbao,openbao-init,openbao-config
# Deploy only monitoring stack
./scripts/bootstrap.sh example.com --apps=prometheus-crds,otel-lgtm-stack,opentelemetry-operator
# Deploy identity management
./scripts/bootstrap.sh example.com --apps=keycloak,cluster-auth,cluster-auth-configCombine with --template-only to render manifests without applying:
# Generate YAML for debugging
./scripts/bootstrap.sh example.com --apps=keycloak --template-only > keycloak-manifests.yaml
# View what would be deployed
./scripts/bootstrap.sh example.com --apps=openbao,openbao-init --template-only | kubectl diff -f -The bootstrap script automatically cleans up temporary files at the end:
/tmp/merged_values.yaml/tmp/argocd_values.yaml/tmp/argocd_size_values.yaml/tmp/openbao_values.yaml/tmp/openbao_size_values.yaml/tmp/gitea_values.yaml/tmp/gitea_size_values.yaml