Files
factory/vibe/guidebooks/applications/README.md
Gabriel Radureau 4823394e0e docs(vibe): add applications/ guidebook (webapp + url-shortener)
Tree-docs guidebook under vibe/guidebooks/applications/ documenting the common
app pattern and two contrasting archetypes, drilling into lab-ecosystem/01-factory
(bidirectional):

- README.md  : the shared app pattern (repo = Dockerfile + chart + optional iac +
  CI; ArgoCD app-of-apps; the <app> join key; .fr vs .lab ingress conventions) +
  a two-archetype comparison.
- webapp.md  : canonical Go + Postgres exemplar (chart, VaultAuth/Static/Dynamic
  CRDs, inline iac vs the shared app_roles module, CI); notes the current nuance
  that the live pod still uses the static pgbouncer_auth DATABASE_URL.
- url-shortener.md : Rust + SQLite-on-Longhorn-RWO counterpart (single replica,
  no iac/no Vault, CI mirrors the upstream image); the power-cut recovery story.

erp is referenced in prose only (its own guidebook lands next). Sibling-repo code
via full gitea URLs; 2 mermaid diagrams MCP-validated; zero dead links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:58:36 +02:00

119 lines
9.8 KiB
Markdown

[vibe](../../README.md) > [Guidebooks](../README.md) > **Applications**
# Applications
> **Status:** ✅ Active
> **Last Updated:** 2026-06-23
> **Upstream:** [Lab ecosystem hub](../lab-ecosystem/README.md) · [01 · factory](../lab-ecosystem/01-factory.md)
> **Downstream:** [webapp](webapp.md) · [url-shortener](url-shortener.md)
> **Related:** [naming-conventions](../lab-ecosystem/naming-conventions.md) · [tools secrets-and-vso](../tools/secrets-and-vso.md) · [factory postgres-iac](../factory-provisioning/opentofu/postgres-iac.md) · [safe-prod-like-environment ADR](../../ADR/0001-safe-prod-like-environment.md)
This guidebook maps the **deployed applications** — the workloads ArgoCD runs in their own `<app>` namespace — and, more importantly, the **single repeatable pattern** every one of them follows. Once you know the pattern, every app reads as a variation on the same skeleton: a Gitea repo whose contents (Dockerfile + Helm chart + optional Vault IaC + CI) and whose `<app>` name fully determine how it builds, deploys, gets its secrets, and is reached from the network.
Two apps are presented in depth as the canonical archetypes: [webapp](webapp.md) (Go + external Postgres) and [url-shortener](url-shortener.md) (Rust + embedded SQLite). Other apps in the cluster — `erp`, `dance-lessons-coach`, `telegram-gateway`, `plausible` — instantiate the same pattern; `erp` has its own guidebook (forthcoming) and is not linked here yet.
## The common app pattern
Every application is a self-contained Gitea repo under the `arcodange-org` (or `arcodange`) org that carries the same four ingredients. The `<app>` name — the repo name — is the join key that threads through all of them (see the [naming-conventions concept](../lab-ecosystem/naming-conventions.md)).
| Ingredient | Path in the app repo | What it is | Required? |
|---|---|---|---|
| **Dockerfile** | `Dockerfile` | Multi-stage build producing the runtime image, pushed to the Gitea container registry as `gitea.arcodange.lab/<org>/<app>` | ✅ always |
| **Helm chart** | `chart/` | `Chart.yaml` + `values.yaml` + `templates/` (deployment, service, ingress, serviceaccount, hpa, config, NOTES, optional PVC, optional Vault CRDs) — the unit ArgoCD syncs | ✅ always |
| **Vault IaC** | `iac/` | OpenTofu that declares the app's Vault objects: a Postgres dynamic-secret role keyed on `<app>` + a Kubernetes auth role bound to the `<app>` ServiceAccount. The canonical form pulls the [`app_roles` module](../tools/secrets-and-vso.md) from `tools`; the privileged `app_policy` half is declared centrally so the app repo never holds it | 🟡 only apps needing Postgres / Vault KV |
| **CI workflows** | `.gitea/workflows/` | A `dockerimage` job that builds + pushes the image on every `main` push, and (when `iac/` exists) a `vault` job that runs `tofu apply` against Vault, gated to changes under `iac/*.tf` | ✅ image build · 🟡 vault apply |
### How a chart becomes a running app
Factory's [ArgoCD app-of-apps](../lab-ecosystem/01-factory.md) emits **one `Application` CRD per app**, and every field is derived mechanically from the `<app>` name:
| Application field | Value | Source |
|---|---|---|
| `repoURL` | `https://gitea.arcodange.lab/<org>/<app>` | `<app>` + optional org override |
| `path` | `chart` | fixed convention |
| `namespace` | `<app>` (`CreateNamespace=true`) | `<app>` |
| `syncPolicy` | `automated` with `prune: true` + `selfHeal: true` | app-of-apps default |
The same `<app>` name is also the Postgres database/role name, the Vault role name, the KV path prefix, and the ServiceAccount name — one string keying the whole stack. See [naming-conventions](../lab-ecosystem/naming-conventions.md).
### Ingress convention — `.fr` public vs `.lab` internal
Every app that serves HTTP exposes itself through two Traefik ingresses with a fixed split by domain suffix:
| Ingress | Domain | Traefik entrypoint | Middlewares | TLS / cert | Reached via |
|---|---|---|---|---|---|
| **Public** | `<app>.arcodange.fr` | `web` | `kube-system-crowdsec@kubernetescrd` (CrowdSec bouncer) | terminated at the edge | the **Cloudflared tunnel** — the public web entrypoint |
| **Internal** | `<app>.arcodange.lab` | `websecure` | `localIp@file` (LAN-only allow-list) | a cert from **either** the Traefik `letsencrypt` resolver **or** cert-manager's `step-issuer` (`StepClusterIssuer`) | the LAN directly |
> [!NOTE]
> The two archetypes differ only in cert mechanism, not in the convention: webapp's internal ingress carries the `letsencrypt` certresolver annotations, while url-shortener's internal ingress requests its cert from cert-manager's `step-issuer`. Both still ride `websecure` + `localIp@file`; both still expose a `.fr` twin behind the CrowdSec middleware.
## Two archetypes compared
The deployed apps fall into two shapes. Pick the matching archetype's page when adding or modifying an app.
| Aspect | [webapp](webapp.md) | [url-shortener](url-shortener.md) |
|---|---|---|
| Language / build | **Go** (golang:1.23 → alpine runtime) | **Rust** (cargo-chef → `scratch` runtime) |
| State | **External Postgres**, reached through the `tools` **pgbouncer** pooler with credentials delivered by **VSO** | **Embedded SQLite** on a `/data` file |
| Persistence | none in-cluster (DB lives on `pi2`) | a **Longhorn RWO PVC** (`storageClassName: longhorn`, `helm.sh/resource-policy: keep`) mounted at `/data` |
| Replicas | **scalable** (stateless pods; HPA-ready) | **single** — RWO volume cannot be shared across pods |
| `iac/` + Vault | **yes** — declares a Postgres dynamic-secret role + a k8s auth role; pod consumes **dynamic, rotating** DB creds via `VaultAuth` + `VaultDynamicSecret` + `VaultStaticSecret` CRDs | **none** — no Vault objects, no DB role |
| Recovery | restore from the **PostgreSQL backup** (factory `05_backup``/mnt/backups`) | **Longhorn block-device recovery** of the PVC (raw replica `.img` files) — see [ansible recover](../factory-provisioning/ansible/06-recover.md) |
The choice is essentially *"shared/scalable state that survives a single node"* (Postgres, webapp shape) versus *"self-contained single-writer state co-located with the pod"* (SQLite-on-Longhorn, url-shortener shape). The trade-off and why both are kept prod-like is recorded in the [safe-prod-like-environment ADR](../../ADR/0001-safe-prod-like-environment.md).
## Generic app lifecycle
```mermaid
%%{init: {'theme': 'base'}}%%
flowchart LR
classDef src fill:#2563eb,stroke:#1e40af,color:#fff
classDef proc fill:#059669,stroke:#047857,color:#fff
classDef store fill:#7c3aed,stroke:#6d28d9,color:#fff
classDef net fill:#b45309,stroke:#92400e,color:#fff
REPO["app repo<br>Dockerfile + chart/ + iac/ + .gitea/workflows"]:::src
IMG["image pushed<br>gitea registry &lt;org&gt;/&lt;app&gt;"]:::store
VAULT["tofu apply<br>Vault role + Postgres role"]:::store
ARGO["ArgoCD<br>deploys chart (ns &lt;app&gt;)"]:::proc
POD["pod<br>Postgres via pgbouncer + VSO<br>OR SQLite on Longhorn PVC"]:::proc
TR["Traefik ingress<br>.fr public / .lab internal"]:::net
REPO -- "dockerimage CI" --> IMG
REPO -- "vault CI (iac apps only)" --> VAULT
IMG --> ARGO
VAULT -. "creds for DB apps" .- POD
ARGO --> POD
POD --> TR
```
1. The **app repo** holds the four ingredients: a Dockerfile, a `chart/`, an optional `iac/`, and `.gitea/workflows`.
2. On a push to `main`, the **dockerimage** workflow builds the image and pushes it to the Gitea container registry as `<org>/<app>`.
3. For apps with an `iac/`, the **vault** workflow runs `tofu apply` to declare the app's Postgres dynamic-secret role and Kubernetes auth role in Vault.
4. **ArgoCD** (factory's app-of-apps) syncs the chart into the `<app>` namespace, deriving `repoURL`/`path`/`namespace` from the `<app>` name.
5. The **pod** comes up; a Postgres-backed app receives rotating DB credentials through pgbouncer + VSO, while a SQLite-backed app mounts its Longhorn PVC at `/data`.
6. **Traefik** publishes the pod through two ingresses: the `.fr` public route (CrowdSec middleware, via the Cloudflared tunnel) and the `.lab` internal route (`websecure` + `localIp` + a letsencrypt/step-issuer cert).
## Index
| Page | Archetype | Status |
|---|---|---|
| [webapp](webapp.md) | Canonical **Go + external Postgres** exemplar — `iac/` + Vault dynamic creds, scalable stateless pods | ✅ Active |
| [url-shortener](url-shortener.md) | **Rust + embedded SQLite** counterpart — single replica on a Longhorn RWO PVC, no Vault | ✅ Active |
`erp` and the other apps (`dance-lessons-coach`, `telegram-gateway`, `plausible`) follow the same pattern; `erp` will be cross-linked here once its dedicated guidebook ships.
## Maintenance rule
> [!IMPORTANT]
> **When an app's repo changes shape, its page here changes in the same PR.** If you alter the chart structure, the ingress convention, the Vault wiring, the persistence model, or the CI workflows of a deployed app, update this hub and the relevant archetype page in the same change. A reference map that drifts from the real `chart/` and `iac/` sends agents confidently down dead paths.
## Cross-references
- [01 · factory](../lab-ecosystem/01-factory.md) — the ArgoCD app-of-apps that emits one `Application` per app on this page.
- [tools secrets-and-vso](../tools/secrets-and-vso.md) — the `app_policy` + `app_roles` module pair that turns `<app>` into Vault policies, roles, and CI identities; the VSO runtime path the Postgres archetype rides.
- [factory postgres-iac](../factory-provisioning/opentofu/postgres-iac.md) — the per-app PostgreSQL database + `<app>_role` the webapp archetype depends on.
- [naming-conventions](../lab-ecosystem/naming-conventions.md) — the `<app>` join key that threads through repo, image, namespace, DB, Vault role, and ServiceAccount.
- [safe-prod-like-environment ADR](../../ADR/0001-safe-prod-like-environment.md) — why the lab keeps apps deployed prod-like and the state/recovery trade-offs behind the two archetypes.