Bootstrap Your Own Platform
Introduction
This guide provides a step-by-step process for bootstrapping your platform running on Kubernetes, including the necessary prerequisites, architecture setup, and deployment instructions. Try to follow the instructions first. If you have any questions or issues, please reach out directly via Teams. If you're interested in the setup details, explore the Wiki pages.
1. Getting Started
kubara can run on any Kubernetes cluster as long as the required surrounding capabilities exist, especially a secret backend for external-secrets and DNS handling for external-dns.
For the ready-made example flows, use the Infrastructure Presets section. Whether you're running on STACKIT Cloud, STACKIT Edge, T Cloud Public, or other cloud providers, we recommend you to use IaC (Infrastructure-as-Code) be it Terraform/Tofu, Pulumi or other solutions.
If you already have a Kubernetes cluster without DNS, secrets management, etc., simply disable those services in the config.yaml file, which will be generated in the next steps.
1.1 Environment Configuration
Refer to the Prerequisites guide and ensure all non-optional tasks in that guide are completed.
Don't forget to create a new Git repository - all following steps should be executed from within that newly created repository.
The easiest way is to run kubara inside the repository (but do not add the binary to Git).
1.2 Generate preparation files
-
Run this command to scaffold essential setup files:
kubara init --prepThis will generate:
- A
.gitignorefile to help prevent accidental commits of sensitive or unnecessary files - An
.envfile that serves as a template for your environment configuration. Fill all placeholders (<...>) before runningkubara init.
Working with coding agents
Run
kubara agentsto scaffold anAGENTS.mdinto your repository so tools like Claude Code or Codex get a compact, token-lean entry point instead of crawling the full docs site. It points atkubara --helpandkubara schemaas the source of truth and links the raw Markdown documentation pinned to your installed kubara version. Commit it so it travels with the repository, and re-runkubara agents --overwriteto refresh it after upgrading kubara. - A
-
Update the values inside
.envHandling .env Files
.envfiles contain sensitive credentials and must be treated as secrets. Never commit a plain.envfile directly into Git. If you really need it in the repository, make sure it is stored in encrypted form only. Always add.envto.gitignoreto avoid accidental commits. For team collaboration, proven approaches include encrypted.envfiles in the repository, centralized secret management, or helper tools likedotenv. Important: A plain.envfile in Git exposes all secrets and must be avoided. -
Check your values
Warning
Keep in mind that weak passwords such as
123456forARGOCD_WIZARD_ACCOUNT_PASSWORDare a bad idea, since your platform will be publicly available by default via your DNS zone.
1.3 Generate Base Configuration
Initialize your configuration:
kubara init
This command creates a config.yaml file based on the values from your .env.
If you make changes to .env later, you can re-run the command with --overwrite to update the configuration.
When using --overwrite, only values from .env are replaced.
Additional settings in your existing config.yaml are preserved and merged.
This currently applies only to the first cluster entry.
Info
If you plan to use Velero here, and have velero enabled, you need to also put in the s3Url inside the service definition file. More info about this here.
1.4 Validate your config.yaml against schema (optional, recommended)
Generate a JSON schema file:
kubara schema
By default this creates config.schema.json in your current directory.
You can set a custom output file:
kubara schema --output custom-config.schema.json
For editor integration (e.g. VS Code with YAML language server), reference the schema in your config.yaml:
# yaml-language-server: $schema=./config.schema.json
1.5 Update and Prepare Templates
Info
What is "type:" in config.yaml: hub is your hub cluster, spoke is your spoke cluster Hub and Spoke Cluster
Tip
Not using STACKIT Edge? Just remove the load balancer IPs from your config.yaml.
Example:
clusters:
- name: project-name-from-env-file
stage: project-stage-something-like-dev
type: <hub or spoke>
dnsName: <cp.demo-42.stackit.run>
ssoOrg: <oidc-org>
ssoTeam: <org-team>
terraform:
provider: stackit # currently supported: stackit, t-cloud-public
projectId: <project-id-or-tenant-name>
kubernetesType: <ske, edge or cce>
kubernetesVersion: 1.34
dns:
name: <dns-name>
email: <email>
...
services:
...
metallb:
status: enabled
config:
loadBalancerAddressPool:
- 0.0.0.0
publicLoadBalancerIPs: 0.0.0.0
...
terraform.projectId is provider-specific. For t-cloud-public, use the T Cloud Public tenant/project name that the Terraform provider expects as tenant_name, not a UUID.
ingressClassName defaults to traefik. Set it explicitly when using a different ingress controller.
Each service also accepts an optional ingress.annotations map under services.<service>.ingress.annotations that is merged with kubara's defaults, allowing you to add controller-specific annotations without overwriting the full set.
User-provided annotations are merged on top using mergeOverwrite: equal keys are overwritten, while kubara default keys that are not present in the override remain.
kubara generates resources in two stages:
- Terraform modules and overlays to provision infrastructure and the Kubernetes cluster
- Helm templates to deploy Argo CD and platform services
If you are not using Terraform, you can skip directly to Step 3.
2. Infrastructure Provisioning
kubara is mainly focused on the platform layer on top of Kubernetes. For some environments we provide ready-made infrastructure presets and generated Terraform examples.
Visit the Infrastructure Presets section for:
- STACKIT SKE
- STACKIT Edge Cloud
- T Cloud Public
- More provider support is in the works
If you already have an existing Kubernetes cluster and a secret manager supported by external-secrets, continue with the next section.
3. Helm
This step extends the service catalog:
- Generates an umbrella Helm chart in
managed-service-catalog/ - Creates cluster-specific overlays in
customer-service-catalog/
kubara generate --helm
The generated values.yaml files are pre-filled from your config.yaml and .env. Review them and
adjust any environment-specific settings; some charts still contain explicit placeholders that you
must fill in.
Example:
# ... previous content of yaml file
url: "https://replace-me-with-your-url"
# ... rest of yaml
customer-service-catalog/helm/<cluster>/<chart>/values.yaml
Source templates are embedded in the binary under src/internal/catalog/built-in/..., but you should only edit generated files in your repository.
The chart directories where values usually need review are:
- argo-cd
- cert-manager
- external-dns
- external-secrets
- homer-dashboard
- kube-prometheus-stack
- kyverno
- kyverno-policy-reporter
- loki
- longhorn
- metallb
- metrics-server
- oauth2-proxy
- traefik
- velero
3.1 Additional value files and CI value files
Every generated app supports:
values.yamlas the main customer overlay file- optional
additional-values.yamlfor overrides/extra values
kubara's generated ApplicationSet already references both files and ignores missing files, so you can add additional-values.yaml only when needed.
During bootstrap kubara uses additional-values.yaml files when they exist as well.
Merge behavior reminder:
- maps/dictionaries are merged recursively
- lists/arrays replace previous values completely
CI-specific values can be stored in chart-local CI files (for example ci/ci-values.yaml) to keep pipeline-only settings out of runtime overlays.
T Cloud Public CCE: provider-specific Helm adjustments
After kubara generate --helm and before kubara bootstrap, replace the Traefik service annotation placeholder with the shared load balancer ID from Terraform:
cd customer-service-catalog/terraform/<cluster>/infrastructure
terraform output -raw load_balancer_id
Set the value in customer-service-catalog/helm/<cluster>/traefik/values.yaml under traefik.service.annotations["kubernetes.io/elb.id"]. Keep kubernetes.io/elb.class: "union".
ExternalDNS also needs a Kubernetes Secret named tcloudpubliccloudsyaml with a clouds.yaml key. With the default OpenBao and External Secrets setup, copy the ExternalDNS block from customer-service-catalog/terraform/<cluster>/openbao/secrets.tf-example to secrets.tf, set TF_VAR_external_dns_os_username and TF_VAR_external_dns_os_password in set-env.sh, and apply the OpenBao Terraform layer before bootstrap. If you need Terraform value overrides in the OpenBao layer, use Terraform value overrides.
Warning
Don't forget to commit and push your changes to Git!
4. Deploying Argo CD
4.1 Bootstrap the Hub cluster
Warning
This command requires access to a Kubernetes cluster and, by default, uses ~/.kube/config.
To target a specific cluster, provide your own config with --kubeconfig your-kubeconfig
External Secrets needs a ClusterSecretStore.
For T Cloud Public CCE, kubara already renders the OpenBao-backed ClusterSecretStore into external-secrets/values.yaml. Use --with-es-crds; do not pass --with-es-css-file and do not create a separate provider credential secret.
For other providers, create the provider credential secret first and either pass a ClusterSecretStore manifest during bootstrap with --with-es-css-file and --with-es-crds, or apply the ClusterSecretStore manually if the External Secrets CRDs already exist.
If the namespace does not exist yet, create it once before creating the provider credential secret(s):
kubectl create namespace external-secrets
Example provider credential secret(s) for the ClusterSecretStore:
## Bitwarden
kubectl -n external-secrets create secret generic bitwarden-access-token \
--from-literal=token="<BITWARDEN_MACHINE_ACCOUNT_TOKEN>"
## STACKIT Secrets Manager
kubectl -n external-secrets create secret generic stackit-secrets-manager-cred \
--from-literal=username="<USERNAME>" \
--from-literal=password="<PASSWORD>"
Example clustersecretstore.yaml for --with-es-css-file (templating with {{ .cluster.name }} / {{ .cluster.stage }} is supported):
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
labels:
argocd.argoproj.io/instance: {{ .cluster.name }}-external-secrets
name: "{{ .cluster.name }}-{{ .cluster.stage }}"
spec:
provider:
vault:
auth:
userPass:
path: userpass
secretRef:
key: password
name: stackit-secrets-manager-cred
namespace: external-secrets
username: "<USERNAME>"
path: "<VAULT_PATH>"
server: "https://<your-secrets-manager-endpoint>"
version: v2
kubara scopes secret paths by cluster and stage. Namespace-specific secrets use
<cluster-name>/<stage>/<namespace>/<secret>, while cluster-wide secrets use
<cluster-name>/<stage>/cluster_secrets/<secret>. For example, Grafana credentials for the
controlplane production cluster live at
controlplane/production/kube-prometheus-stack/grafana_credentials.
This path layout is the same for every provider and secret backend, including the local OpenBao setup.
kubara bootstrap <cluster-name-from-config-yaml> --with-es-crds --with-prometheus-crds
For providers that need an external ClusterSecretStore manifest, pass it during bootstrap:
kubara bootstrap <cluster-name-from-config-yaml> \
--kubeconfig k8s.yaml \
--with-es-css-file clustersecretstore.yaml \
--with-es-crds --with-prometheus-crds
After a successful bootstrap run, your platform should be operational.
5. Access the Argo CD Dashboard
Argocd Local Login
Username: wizard
Password: From .env (ARGOCD_WIZARD_ACCOUNT_PASSWORD)
- Start port-forwarding:
kubectl port-forward svc/argocd-server -n argocd 8080:443
-
Open your browser at: http://localhost:8080/argocd
-
Log in with the credentials above.
Enjoy your new platform!
What's also possible?
This section will be extended in the future to describe not just technical changes, but also other supported possibilities when bootstrapping.
Bootstrapping Multiple Hub Cluster
You can bootstrap multiple Hub clusters.
Do not reuse the same config.yaml file for multiple Hub clusters.
Why?
During the bootstrap process, the .env file is used to provide credentials.
If you reuse the same .env file, you would have to constantly adjust it for each Hub - which is error-prone.
Since version 0.2.0, this is much easier. You can simply provide a different env file:
kubara init --prep --env-file .another-env
.another-env with the required values. Generate a new config file from it:
kubara --config-file another-config.yaml --env-file .another-env init
This will use the values from .another-env to generate another-config.yaml.
Render Terraform modules and Helm charts for the new Hub cluster:
# default: generates both Helm and Terraform
# use --helm or --terraform to generate only one type
./kubara --config-file another-config.yaml generate
Finally, bootstrap your additional Hub cluster:
kubara bootstrap --config-file another-config.yaml --env-file .another-env <cluster name from another-config.yaml> --with-es-crds --with-prometheus-crds
What's Next?
After bootstrapping your platform, you can: