Provision a Standalone Cluster
Overview
The following walks through the steps required to provision a standalone cluster in AWS and use it to validate platform changes before raising a pull request. Here we use the Terraform code in the terraform/ directory to provision the infrastructure and bootstrap ArgoCD.
When to Use This Workflow
Use a cloud cluster to validate changes that cannot be fully tested with a local Kind cluster:
- IAM and pod identity integration
- Cloud-provider-specific add-ons (EBS CSI, load balancers, ACK controllers)
- Karpenter and cluster autoscaler
- Storage class and persistent volume provisioning
- Real DNS and certificate issuance
For changes that only affect YAML structure, ApplicationSet logic, or Kustomize overlays, the local development workflow is faster.
How Branch-Based Validation Works
The platform's Terraform module accepts a revision_overrides variable that overrides the platform_revision and tenant_revision values normally sourced from the cluster definition YAML. This means ArgoCD is pointed at your feature branch from the moment the cluster is bootstrapped.
┌──────────────────────────────────────────────────────────────────┐
│ terraform apply │
│ └─ revision_overrides.platform_revision = "feat/my-change" │
│ │
│ ArgoCD bootstrapped with bootstrap Application │
│ └─ Sources kustomize/overlays/standalone/ │
│ └─ Creates system-platform ApplicationSet │
│ └─ Reads cluster definition from tenant repo │
│ └─ Creates platform Application │
│ ├─ apps/registration/standalone (branch)│
│ ├─ apps/system/ (branch)│
│ └─ apps/tenant/ (branch)│
│ │
│ Every push to the branch → ArgoCD detects within ~3 minutes │
│ └─ Diffs are applied automatically │
└──────────────────────────────────────────────────────────────────┘
Because revision_overrides is set in the Terraform .tfvars file rather than the cluster definition, you do not need to commit a revision change to the cluster YAML — the override is injected at bootstrap time and flows through every ApplicationSet the platform creates.
Provision the Cluster
1. Create a feature branch
git checkout -b feat/my-change
2. Set the revision override
Open terraform/variables/dev.tfvars (or copy it to a new file for an isolated environment) and set revision_overrides to your branch name:
## Path to the cluster definition
cluster_path = "../release/standalone-aws/clusters/dev.yaml"
## Override revision or branch for the platform and tenant repositories
revision_overrides = {
platform_revision = "feat/my-change"
tenant_revision = "feat/my-change"
}
If your change only affects the platform repository leave tenant_revision pointing at main (or the current stable branch). Set both when your change spans both repositories.
3. Provision the cluster
# Using the default dev.tfvars
make standalone-aws
# Or, using a custom variables file
terraform -chdir=terraform apply -var-file=variables/my-feature.tfvars
This runs terraform init, selects (or creates) the dev workspace, and provisions an EKS cluster with ArgoCD bootstrapped to your branch.
4. Authenticate to the cluster
# Using the Makefile helper
CLUSTER_NAME=dev make eks-login
# Or directly via the AWS CLI
aws eks update-kubeconfig --name dev --region eu-west-2
Understanding the Bootstrap Flow
Once the cluster is provisioned, ArgoCD drives everything. The bootstrap sequence is:
Step 1 — Bootstrap Application
Terraform creates a single ArgoCD Application named bootstrap in the argocd namespace. It sources kustomize/overlays/standalone/ from the platform repository at your branch revision. This overlay renders the top-level system-platform ApplicationSet.
Step 2 — system-platform ApplicationSet
system-platform reads cluster definitions from the tenant repository (filtered by cluster_path). For each cluster definition it creates a platform Application. The revision_overrides injected by Terraform are threaded through here so that all downstream sources target your branch.
Step 3 — platform Application (three sources)
The platform Application has three sources, all targeting your branch:
| Source path | Purpose |
|---|---|
apps/registration/standalone | Registers the cluster and maintains the ArgoCD cluster secret |
apps/system/ | Deploys platform add-ons (from addons/) |
apps/tenant/ | Deploys tenant workloads |
Step 4 — system-registration
apps/registration/standalone/ creates the system-registration Application, which runs the charts/cluster-registration Helm chart. This chart reads the cluster definition YAML and writes (or updates) an ArgoCD cluster Secret in the argocd namespace. That secret carries all the metadata.labels from the cluster definition, including every enable_* feature flag.
Step 5 — system-helm and system-kustomize ApplicationSets
apps/system/system-helm.yaml and apps/system/system-kustomize.yaml use a matrix generator that:
- Discovers add-on definition files from
addons/helm/**oraddons/kustomize/**. - Cross-references each add-on's
featurefield against the cluster secret labels. - Creates an Argo CD
Applicationonly when the matchingenable_<feature>: "true"label is present on the cluster secret.
cluster definition YAML
└─ metadata.labels.enable_kyverno: "true"
└─ cluster secret (maintained by system-registration)
└─ system-kustomize ApplicationSet filter
└─ Application: system-kust-kyverno-dev ✓ created
Add-on configuration is layered: platform defaults from config/<feature>/all.yaml are merged with cloud-specific overrides and then tenant-specific overrides.
Iterating on Changes
Once the cluster is running, your iteration loop is:
- Make a change locally.
- Commit and push to your branch.
- Wait for ArgoCD to poll the branch (approximately every 3 minutes).
- ArgoCD detects the diff and applies it automatically.
You can also force an immediate sync from the CLI:
# Force refresh (re-fetch from Git without waiting for the poll interval)
kubectl -n argocd get applications
argocd app refresh platform --hard
# Or using kubectl
kubectl -n argocd patch application platform \
--type merge -p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}'
Push your commits before starting Terraform provisioning — the cluster bootstraps against the branch HEAD, so any commits already there are applied on first sync.
Monitoring the Cluster
Check all applications
kubectl -n argocd get applications
A healthy standalone cluster looks like:
NAME SYNC STATUS HEALTH STATUS
bootstrap Synced Healthy
platform Synced Healthy
system-registration-dev Synced Healthy
system-cert-manager-dev Synced Healthy
system-kust-cert-manager-dev Synced Healthy
system-kyverno-dev Synced Healthy
system-kust-kyverno-dev Synced Healthy
...
Inspect a specific application
# Overview
kubectl -n argocd get application system-kust-kyverno-dev -o yaml
# Sync and health conditions
kubectl -n argocd describe application system-kust-kyverno-dev
Verify the cluster secret labels
The cluster secret is the source of truth for which features are enabled:
kubectl -n argocd get secret cluster-dev -o jsonpath='{.metadata.labels}' | jq .
If a label is missing or wrong, the add-on selector won't match and the Application will not be created.
Watch ArgoCD logs
kubectl -n argocd logs deployment/argocd-application-controller -f
kubectl -n argocd logs deployment/argocd-repo-server -f
Enabling or Disabling Features
Feature flags live in the cluster definition YAML. For the standalone development environment this is release/standalone-aws/clusters/dev.yaml.
metadata:
labels:
enable_cert_manager: "true"
enable_kyverno: "true"
enable_external_secrets: "true"
To toggle a feature:
- Edit the cluster definition YAML.
- Commit and push to your branch.
- ArgoCD runs
system-registration, which updates the cluster secret. system-kustomize/system-helmreconcile and create or delete the Application.
Removing a label causes the ApplicationSet to delete the corresponding Application, which triggers Argo CD to prune all resources the Application owned.
Pre-PR Checklist (Cloud Validation)
Before raising a pull request after cloud validation:
- All ArgoCD Applications show
SyncedandHealthy - The specific feature you changed behaves correctly on the cluster
- Unrelated Applications were not affected (check for unexpected
OutOfSync) -
make testpasses locally (schema and lint checks) - The
revision_overridesin your.tfvarsfile are not committed — they are a local testing aid - Cluster definition changes (if any) reflect the intended production values, not the branch name
Cleanup
# Using the Makefile
make destroy-standalone-aws
# Or using Terraform directly
terraform -chdir=terraform destroy -var-file=variables/dev.tfvars
Always run cleanup when finished to avoid ongoing infrastructure costs.
Related Documentation
- Local Development — Kind-based development for fast iteration
- Hub & Spoke — Cloud validation for multi-cluster hub/spoke topology
- Validation — Full validation checklist and test suite