# App Repo Deployment Guide This guide explains the recommended way to deploy an application to this cluster. ## Recommended Model Use two repos: - `HetznerTerra` (this repo): cluster, addons, shared infrastructure, Flux wiring - `your-app-repo`: application source, Dockerfile, CI, Kubernetes manifests or Helm chart Why: - cluster lifecycle stays separate from app code - app CI can build and tag images independently - this repo remains the source of truth for what the cluster is allowed to deploy ## Current Cluster Assumptions - Flux is already installed and reconciles this repo from `main` - `clusters/prod/flux-system/kustomization-apps.yaml` points at `./apps` - `apps` is suspended by default - private access is through Tailscale - runtime secrets should come from Doppler via External Secrets ## Deployment Options ### Option A: Separate app repo Recommended for most real applications. Flow: 1. App repo builds and pushes an image. 2. This repo defines a `GitRepository` pointing at the app repo. 3. This repo defines a `Kustomization` pointing at a path in the app repo. 4. Flux pulls the app repo and applies the manifests. ### Option B: In-repo app manifests Only use this when the application is tiny or tightly coupled to the platform. Flow: 1. Put Kubernetes manifests directly under `apps/` in this repo. 2. Unsuspend the top-level `apps` Kustomization. This is simpler, but mixes platform and app changes together. ## App Repo Structure Suggested layout: ```text your-app-repo/ ├── src/ ├── Dockerfile ├── .gitea/workflows/ └── deploy/ ├── base/ │ ├── namespace.yaml │ ├── deployment.yaml │ ├── service.yaml │ ├── externalsecret.yaml │ └── kustomization.yaml └── prod/ ├── kustomization.yaml └── patch-*.yaml ``` If you prefer Helm, replace `deploy/base` and `deploy/prod` with a chart path and point Flux at that instead. ## What the App Repo Should Own - application source code - image build pipeline - image tag strategy - Deployment / Service / Ingress or Tailscale-facing Service manifests - app-specific `ExternalSecret` manifests - app-specific namespace ## What This Repo Should Own - cluster-level permission to deploy the app - the `GitRepository` and top-level `Kustomization` that attach the app repo to the cluster - whether the `apps` layer is suspended or active ## Recommended First App Integration In this repo, add Flux objects under `apps/` that point to the app repo. Example files to add: - `apps/gitrepository-my-app.yaml` - `apps/kustomization-my-app.yaml` - update `apps/kustomization.yaml` Example `apps/gitrepository-my-app.yaml`: ```yaml apiVersion: source.toolkit.fluxcd.io/v1 kind: GitRepository metadata: name: my-app namespace: flux-system spec: interval: 1m ref: branch: main secretRef: name: flux-system url: ssh://git@://.git ``` Example `apps/kustomization-my-app.yaml`: ```yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: my-app namespace: flux-system spec: interval: 10m prune: true sourceRef: kind: GitRepository name: my-app path: ./deploy/prod wait: true timeout: 5m dependsOn: - name: infrastructure ``` Then update `apps/kustomization.yaml`: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - gitrepository-my-app.yaml - kustomization-my-app.yaml ``` ## App Secrets Recommended path: 1. Put runtime values in Doppler. 2. In the app manifests, create an `ExternalSecret` that reads from `doppler-hetznerterra`. 3. Reference the resulting Kubernetes Secret from the Deployment. Example app-side `ExternalSecret`: ```yaml apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: my-app-env namespace: my-app spec: refreshInterval: 1h secretStoreRef: name: doppler-hetznerterra kind: ClusterSecretStore target: name: my-app-env creationPolicy: Owner data: - secretKey: DATABASE_URL remoteRef: key: MY_APP_DATABASE_URL ``` ## Image Delivery Recommended flow: 1. App repo CI builds a container image. 2. CI pushes it to a registry. 3. The app repo updates the Kubernetes image tag in `deploy/prod`. 4. Flux notices the Git change and deploys it. Keep the first version simple. Do not add image automation until the basic deploy path is proven. ## Exposing the App Pick one: ### Private app over Tailscale Best fit for this cluster right now. Create a Service like the existing Rancher/Grafana/Prometheus pattern: ```yaml apiVersion: v1 kind: Service metadata: name: my-app-tailscale namespace: my-app annotations: tailscale.com/hostname: my-app tailscale.com/tags: "tag:prod" tailscale.com/proxy-class: infra-stable spec: type: LoadBalancer loadBalancerClass: tailscale selector: app.kubernetes.io/name: my-app ports: - name: http port: 80 protocol: TCP targetPort: 3000 ``` Use `http://my-app.` or your chosen hostname. ### Cluster-internal only Create only a `ClusterIP` Service. ### Public ingress Not recommended as the first app path in this repo. Get the private path working first. ## Enabling the Apps Layer The cluster-wide `apps` Kustomization is suspended by default. When you are ready to let Flux deploy app attachments from `apps/`, unsuspend it: ```bash kubectl -n flux-system patch kustomization apps --type=merge -p '{"spec":{"suspend":false}}' ``` Or commit a change to `clusters/prod/flux-system/kustomization-apps.yaml` changing: ```yaml suspend: true ``` to: ```yaml suspend: false ``` ## First Deploy Checklist Before deploying the first app, make sure: 1. app image builds successfully 2. app repo contains valid `deploy/prod` manifests 3. this repo contains the `GitRepository` + `Kustomization` attachment objects 4. required Doppler secrets exist 5. `apps` is unsuspended if you are using the top-level `apps` layer ## Verification Commands From a machine with cluster access: ```bash kubectl -n flux-system get gitrepositories,kustomizations kubectl get ns kubectl -n my-app get deploy,svc,pods,externalsecret,secret ``` If private over Tailscale: ```bash kubectl -n my-app get svc my-app-tailscale -o wide ``` ## Minimal Recommendation If you want the simplest, lowest-risk first deploy: 1. create a separate app repo 2. add `deploy/base` + `deploy/prod` 3. add a `GitRepository` + `Kustomization` in this repo under `apps/` 4. keep the app private with a Tailscale `LoadBalancer` Service 5. use Doppler + `ExternalSecret` for runtime config That matches the current cluster design with the least surprise.