# 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@<your-git-host>:<port>/<org>/<your-app-repo>.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.<your-tailnet>` 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.
