Setup Guide
This guide explains how a KubeOpera environment is put together and how to build one from bare infrastructure. If you just want KubeOpera running, start with the Installation tutorial — come back here when you want to understand or customize what it did.
KubeOpera is a GitOps system
A KubeOpera environment is a directory in Git. It declares everything the environment runs — cluster add-ons, databases, the message bus and every platform service — as Kubernetes manifests, and Flux continuously makes the cluster match it.
That has practical consequences:
- Every change is a commit. Upgrading a service, changing a setting or adding a replica is a pull request, reviewed and auditable.
- Drift is corrected automatically. A manual
kubectl editis reverted on Flux's next reconcile — change Git instead. - Environments are reproducible. Staging and production differ only where their directories say they do.
How an environment is structured
Each environment lives under fluxcd/clusters/<environment>/ in your GitOps repository. Flux reconciles the whole tree from one root Kustomization:
clusters/production/
├── flux-system/ Flux's own controllers
├── infrastructure/
│ ├── sources/ HelmRepository and GitRepository definitions
│ ├── networking/ cert-manager, ingress-nginx, external-dns
│ ├── storage/ StorageClasses
│ ├── monitoring/ kube-prometheus-stack, Grafana, Loki, Jaeger
│ ├── security/ Kyverno, Falco, Sealed Secrets
│ ├── messaging/ RabbitMQ
│ └── compute/ Karpenter, aws-node-termination-handler
├── cert-manager-config/ ClusterIssuers (applied after cert-manager's CRDs exist)
├── database/ PostgreSQL
└── apps/ one Kustomization per KubeOpera service, for example:
apps/auth-service.yaml:
path: ./fluxcd/apps/auth-service/overlays/production
sourceRef: {kind: GitRepository, name: flux-system}
Cluster add-ons are managed exactly like KubeOpera's own services — as HelmRelease and Kustomization objects in this tree, reconciled on the same interval with the same drift correction. Only the few components Flux itself needs in order to run (networking, cloud integration, storage — see Step 2) are installed before Flux.
Services: base and overlays
Each service's manifests are split into:
base/— what's identical in every environment;overlays/<environment>/— what legitimately differs: replicas, resources, hostnames and settings, applied as Kustomize patches.
Put environment differences in overlays, never in base/. See Advanced configuration.
Building an environment from scratch
If you already have a Kubernetes cluster, skip to Step 3. Otherwise, KubeOpera's Terraform and bootstrap scripts build one on AWS for you.
1. Provision infrastructure
Terraform in infrastructure/terraform/environments/<env>/ creates the network, a bastion host, control-plane and worker instances, and (optionally) a worker Auto Scaling Group, using the vpc, security and compute modules.
cd infrastructure/terraform/environments/<env>
./setup-terraform-backend.sh # creates the S3 state bucket (safe to re-run)
terraform init
terraform apply -var-file=terraform.tfvars
Prefer a managed control plane? Use the eks module instead of compute, or let KubeOpera provision EKS or GKE clusters for you from Cluster Management.
2. Bootstrap the nodes
Before Flux can run, nodes need a container runtime, pod networking, a cloud integration and storage. From the bastion, run:
./configure-nodes.sh # OS, containerd and kubelet
./install-kubernetes.sh # kubeadm, Calico, AWS Cloud Controller Manager, EBS CSI driver, Metrics Server
install-kubernetes.sh initializes the control plane with an explicit kubeadm configuration that sets cloud-provider: external, so nodes register correctly with the AWS Cloud Controller Manager.
Everything else — including ingress — is installed by Flux in the next steps.
3. Install Flux
Generate an environment directory (if you haven't already — see Installation), then bootstrap Flux against it:
flux bootstrap github \
--owner=<org> \
--repository=gitops-iac \
--path=fluxcd/clusters/<env> \
--token-auth
Prefer to install Flux with Helm and wire it up yourself? That works too:
helm repo add fluxcd-community https://fluxcd-community.github.io/helm-charts
helm install flux-system fluxcd-community/flux2 -n flux-system --create-namespace
flux create source git flux-system \
--url=https://github.com/<org>/gitops-iac --branch=main --namespace=flux-system
flux create kustomization flux-system \
--source=GitRepository/flux-system --path="./fluxcd/clusters/<env>" \
--prune=true --namespace=flux-system
4. Let Flux reconcile
Once the root Kustomization exists, Flux does the rest, in dependency order:
- cert-manager, ingress-nginx and the other add-ons;
cert-manager-configapplies theClusterIssuers;- PostgreSQL and RabbitMQ start;
- every service under
apps/deploys from itsoverlays/<env>/directory.
From then on, a committed change — a new image tag, a setting, a replica count — reaches the cluster on the next reconcile.
5. Create the first administrator
kubectl exec -n kubeopera-core deploy/authapi -- \
kubeopera-admin create-super-admin --email you@example.com --username admin
The command prints a generated password; you'll change it at first sign-in. Run it again at any time to reset a lost administrator password.
Environments created from Cluster Management with an administrator email do this for you automatically.
Secrets
Nothing sensitive is committed in plain text. Database passwords, signing keys and API keys are Sealed Secrets: encrypted for a specific cluster before they're committed, and decryptable only by that cluster's controller.
To add or change a secret:
kubectl create secret generic my-secret -n kubeopera-core \
--from-literal=API_KEY=… --dry-run=client -o yaml \
| kubeseal --format yaml > apps/my-service/overlays/<env>/my-secret.sealed.yaml
git add … && git commit -m "Rotate my-service API key" && git push
Credentials that users manage at runtime — AI provider keys, registry credentials, cloud credentials — are stored encrypted by auth-service instead. See Configuration: where secrets live.
Creating more environments
You don't need to hand-write environment directories. Cluster Management's Connect Existing and Create New flows generate a complete, cluster-specific environment for you, using the development environment as the reference:
- the infrastructure layer, with external-dns pointed at the new cluster's domain and region;
- a hostname for every service under the new domain, added as overlay patches;
- fresh PostgreSQL and RabbitMQ credentials and a registry pull secret, sealed before they touch Git.
The generator (gitops-scaffolder, driven by host-cluster-controller) runs in two passes: it first pushes the infrastructure layer and waits for Sealed Secrets to come up on the new cluster, then seals and pushes everything else. The result is committed to the platform's shared GitOps repository or to your own repository, as you choose when creating the cluster.
Verifying an environment
flux get kustomizations # has Flux applied the latest commit?
flux get sources git # can Flux reach the repository?
kubectl get pods -n kubeopera-core
kubectl logs deploy/<service> -n kubeopera-core
Check flux get kustomizations first when something looks wrong: it tells you whether Flux has even applied what you expect, which is the layer above "is the pod running?".
The Helm alternative
The KubeOpera Helm charts are generated from the same manifests Flux applies, and a CI check keeps them in sync:
kubeopera-crds— the platform's CRDs (HostCluster,Tenant,KubeOperaApp,AdvisorAgent);kubeopera— every platform service.
helm install kubeopera-crds kubeopera/kubeopera-crds
helm install kubeopera kubeopera/kubeopera -n kubeopera-core --create-namespace -f my-values.yaml
The charts install KubeOpera's services, not cluster add-ons, so the cluster needs:
- an ingress controller with an
IngressClassmatchingglobal.ingress.className; - cert-manager with a
ClusterIssuermatchingglobal.ingress.clusterIssuer; - a
StorageClassfor persistent volumes (global.storageClass, defaulting to the cluster's default class).
How the chart handles credentials:
- Each credential (
global.postgres,global.jwt,global.authMasterKey,global.rabbitmq, and each service'sexistingSecret) either references a Secret you provide or is generated once on first install and kept on upgrade. - If you set
global.adminBootstrap.emailand.username, a post-install hook creates the first administrator;helm installprints how to read the password. - Images are pulled from
global.imageRegistrywhen you set it, so you can mirror KubeOpera's images into your own registry.
Next steps
- Configuration — configure services for your environment.
- Cluster Management — manage more clusters from the dashboard.
- Security — authentication, authorization and hardening.