Skip to content

Repository Structure

This document explains how the repository is organised, how Kubernetes manifests flow from Git to a running cluster, and how shared configuration is structured to avoid duplication across clusters.

Top-Level Layout

platform/
├── cluster/          # Cluster-specific Flux Kustomizations and patches
├── components/       # Reusable Kubernetes manifests shared across clusters
├── terraform/        # Infrastructure provisioning
│   ├── environments/ # Per-cluster Terraform configurations
│   └── modules/      # Reusable Terraform modules
├── docs/             # This documentation site
├── container-images/ # Custom container image definitions
└── .gitea/           # CI/CD workflow definitions

Kubernetes: Components and Clusters

components/

Each subdirectory in components/ defines a single platform service. This is the single source of truth for what a service looks like — Helm releases, namespaces, RBAC, HTTPRoutes, and secrets configuration all live here.

Components are intentionally generic. Cluster-specific values (hostnames, IP addresses, credentials, replicas) are not hardcoded here — they are either injected via variable substitution at reconciliation time or patched at the cluster level.

A component typically follows this structure:

components/cert-manager/
├── base/           # HelmRepository, HelmRelease, Namespace
├── config/         # ClusterIssuers and other post-install config
└── kustomization.yaml

Some simpler components are flat (no subdirectories) when they don't need a two-phase install.

cluster/<name>/

Each cluster has its own directory that wires components together and applies cluster-specific patches. Nothing here defines what a service is — it only defines how a service is configured for that particular cluster.

cluster/hetzner-mgmt/
├── kustomization.yaml          # Root: lists all components in deployment order
├── cert-manager/
│   └── kustomization.yaml      # Flux Kustomization pointing to components/cert-manager/base
├── metallb/
│   └── kustomization.yaml      # Same, with cluster-specific IP patches
└── external-secrets/
    └── config/
        └── kustomization.yaml  # Inherits component, patches OpenBao URL and AppRole

How Manifests Are Executed

Flux CD watches this Git repository and reconciles cluster state continuously. The flow from a Git commit to a running workload is:

Git push
  └─▶ Flux source-controller detects change
        └─▶ kustomize-controller reads cluster/<name>/kustomization.yaml
              └─▶ Evaluates each Flux Kustomization resource in order
                    ├─▶ Resolves path (e.g. ./components/cert-manager/base)
                    ├─▶ Applies variable substitutions from cluster-config ConfigMap
                    ├─▶ Applies any patches defined in the cluster kustomization
                    └─▶ Applies rendered manifests to the cluster
                          └─▶ helm-controller reconciles HelmReleases

Deployment Order

The root cluster/<name>/kustomization.yaml lists all components in dependency order. Each component's Flux Kustomization can also declare explicit dependsOn references to ensure sequencing — for example, cert-manager config only runs after cert-manager itself is healthy.

Variable Substitution

A cluster-config ConfigMap in flux-system holds cluster-wide values (floating IP, cluster name, etc.). Flux injects these into manifests at reconciliation time using postBuild.substituteFrom. This allows the same component path to render differently per cluster without any changes to the component itself.

Patches

When a cluster needs to deviate from the component defaults (different replica count, different hostname, different credentials path), it applies a Kustomize patch in the cluster-level kustomization rather than modifying the component. This keeps the component generic and the cluster-specific logic isolated and visible in one place.

Example — kodexet patches the OpenBao server URL in the ClusterSecretStore defined by the external-secrets component:

# cluster/kodexet/external-secrets/config/kustomization.yaml
resources:
  - ../../../../components/external-secrets/config
patches:
  - patch: |-
      kind: ClusterSecretStore
      metadata:
        name: openbao
      spec:
        provider:
          vault:
            server: "https://openbao.prod.skatzi.com"
            auth:
              appRole:
                roleId: "..."
    target:
      kind: ClusterSecretStore
      name: openbao

Terraform: Environments and Modules

terraform/modules/

Modules define reusable infrastructure building blocks. They are the Terraform equivalent of components/ — they describe how to provision a resource but contain no environment-specific values.

terraform/modules/hetzner/talos-cluster/
├── snapshot/    # Talos OS image snapshot
├── network/     # Hetzner private network, subnet, firewall
├── cluster/     # Control plane and worker nodes, Talos config generation
└── components/  # Flux bootstrap and initial cluster setup

terraform/environments/

Each cluster has its own environment directory with sequential numbered stages. Each stage is an independent Terraform root module that reads outputs from the previous stage via remote state.

terraform/environments/hetzner-kodexet-cluster/
├── 0-snapshot/    # Builds the Talos OS image snapshot
├── 1-network/     # Provisions network, subnet, firewall
├── 2-cluster/     # Provisions nodes, generates Talos config, bootstraps Kubernetes
└── 3-components/  # Bootstraps Flux and connects cluster to this Git repository

Stages are applied in order. Each calls the relevant module from terraform/modules/ and passes cluster-specific variables. The module does the work; the environment just provides the inputs.


Documentation Site

The documentation is built with Zensical (MkDocs-compatible) and served as a static site via nginx.

Structure

docs/
├── zensical.toml      # Site config (name, URL, theme)
├── Dockerfile         # Builds static site, serves via nginx-unprivileged
├── requirements.txt   # Python dependencies
└── content/           # All documentation source files
    ├── .pages         # Navigation order (awesome-pages plugin)
    ├── index.md       # Platform introduction — the home page
    ├── principles.md  # Design principles
    ├── contribution/  # How to contribute, component docs, onboarding, ADRs
    ├── services/      # Deployed service documentation
    ├── operations/    # Runbooks and operational procedures
    └── roadmap/       # Planned work

Pages are sorted alphabetically by default. Navigation order configuration is a work in progress.

Building and Deploying

The docs are built and pushed to Harbor by a Gitea Actions workflow on every push to main. The platform cluster pulls the image and serves it at docs.prod.skatzi.com. To build locally:

cd docs
pip install -r requirements.txt
zensical serve