Skip to content

Architecture

dabba is built so the same modules and gitops repo run from a laptop to the cloud. A few ideas make that work.

Provisioning and configuration meet at the kubeconfig

Section titled “Provisioning and configuration meet at the kubeconfig”

Provisioning (kind / k3d / minikube / cloud) and configuring (the platform) are separated by a single interface: a kubeconfig. The provision step outputs one; the configure step takes only that. So moving between Kubernetes substrates changes only the provisioning module — everything above the kubeconfig (Flux, gateway, TLS, secrets, apps) is identical.

That seam is what docker-host does not have. A bare box running docker compose terminates in no kubeconfig at all, so it shares none of the configure layer: no Flux, no Forgejo, no gateway, no cert-manager. It reaches the same outcome — declared state in git, converged onto a machine, secrets delivered from a store — by a different mechanism, a per-box reconcile loop described in backends/docker/README.md.

The two substrates therefore share a contract, not a flow. What crosses between them is the portable application definition: one Application renders to Kubernetes objects on one side and a compose file on the other, and a conformance test fails the build if either renderer stops honouring a field. Gitops content itself does not cross — kustomizations and HelmReleases mean nothing to a compose host — so moving an environment between them is a migration rather than a change to one line.

dabba up installs an in-cluster Forgejo, seeds it with the gitops content, and points Flux at it. The cluster is self-contained — it carries its own authoritative git, so there’s no external dependency to bootstrap. (Forgejo is pluggable to GitHub or a shared-services hub for larger topologies.)

Per-cluster values (domain, issuer, environment name, generated tokens) live in a cluster-vars ConfigMap that Flux substitutes into the manifests at apply time. The cluster describes its own configuration; the gitops manifests stay generic.

dabba’s relationship to the gitops content is one-way generation — dabba.yaml is the source of truth:

  • Hand-authored — the component manifests and Flux Kustomization templates (with ${...} placeholders). dabba never rewrites these.
  • dabba-generated — the thin selection of which components are active, plus the substituted values. These carry a # GENERATED by dabba — do not edit header.

There’s no bidirectional sync to reason about: the real reconciliation is Flux driving actual state toward the config-derived desired state.

dabba ships no credentials. At up, it generates per-environment random secrets, stores the app credentials in OpenBao (delivered to apps by External Secrets), and stashes the one irreducible local secret — the OpenBao root token — in .dabba/<env>/. Retrieve any of them with dabba secret get.

dabba up doesn’t return success after its own steps — it polls until every Flux Kustomization and HelmRelease is Ready and no pod is crashlooping. If something is wrong, it surfaces the actual error from the failing layer (the Flux condition, the pod’s crash reason) instead of a misleading “ready”.

TierWhat
0Local — kind / k3d / minikube, self-contained, self-signed TLS. Available now.
1Cloud — AWS Fargate EKS, real DNS (external-dns) + ACME TLS via Route53, a load-balancer gateway. Available now (substrate: eks).
1Cloud — Scaleway Kapsule. Available now (substrate: scaleway-kapsule).
1Bare OS — a box with Docker and no Kubernetes, converged by a per-box gitops loop. Available now (substrate: docker-host).
2Multi-cluster, shared-services git hub, PR-driven applies. Roadmap.