Dedicated tree-docs guidebook under vibe/guidebooks/erp/ for the lab's most data-critical app, cross-linked from the applications hub (bidirectional): - README.md : Dolibarr 22.0.4 on Postgres; data-criticality; overview diagram; the Vault-unseal-before-scale recovery ordering (CAUTION). - deployment.md : upstream image + custom entrypoint (MySQL->psql), the 50Gi Longhorn RWX documents PVC, Vault CRDs + the shared app_roles iac, init scripts (conf.php creds, table-ownership), ingress, CI. - backup-and-recovery.md: the Ansible CronJob pg_dump (daily 04:00, 15-day retention) + restore Job (scale-0 -> restore -> scale-1); the cluster recovery ordering (Longhorn -> Vault unseal -> erp scale-up). - operations.md : the read-only bin/arcodange CLI, static/company.json, Deno+Playwright tests, day-2 ops. erp code via full gitea URLs; CLUSTER_RECOVERY.md by name; 2 mermaid diagrams MCP-validated; zero dead links. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.9 KiB
vibe > Guidebooks > Applications
Applications
Status: ✅ Active Last Updated: 2026-06-23 Upstream: Lab ecosystem hub · 01 · factory Downstream: webapp · url-shortener Related: naming-conventions · tools secrets-and-vso · factory postgres-iac · safe-prod-like-environment ADR
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 (Go + external Postgres) and url-shortener (Rust + embedded SQLite). Other apps in the cluster — erp, dance-lessons-coach, telegram-gateway, plausible — instantiate the same pattern; erp has its own ERP guidebook because it carries far more moving parts than the two archetypes.
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).
| 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 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 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.
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
letsencryptcertresolver annotations, while url-shortener's internal ingress requests its cert from cert-manager'sstep-issuer. Both still ridewebsecure+localIp@file; both still expose a.frtwin 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 | url-shortener |
|---|---|---|
| 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 |
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.
Generic app lifecycle
%%{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 <org>/<app>"]:::store
VAULT["tofu apply<br>Vault role + Postgres role"]:::store
ARGO["ArgoCD<br>deploys chart (ns <app>)"]:::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
- The app repo holds the four ingredients: a Dockerfile, a
chart/, an optionaliac/, and.gitea/workflows. - On a push to
main, the dockerimage workflow builds the image and pushes it to the Gitea container registry as<org>/<app>. - For apps with an
iac/, the vault workflow runstofu applyto declare the app's Postgres dynamic-secret role and Kubernetes auth role in Vault. - ArgoCD (factory's app-of-apps) syncs the chart into the
<app>namespace, derivingrepoURL/path/namespacefrom the<app>name. - 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. - Traefik publishes the pod through two ingresses: the
.frpublic route (CrowdSec middleware, via the Cloudflared tunnel) and the.labinternal route (websecure+localIp+ a letsencrypt/step-issuer cert).
Index
| Page | Archetype | Status |
|---|---|---|
| webapp | Canonical Go + external Postgres exemplar — iac/ + Vault dynamic creds, scalable stateless pods |
✅ Active |
| url-shortener | 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 is documented in depth in its own ERP guidebook.
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/andiac/sends agents confidently down dead paths.
Cross-references
- 01 · factory — the ArgoCD app-of-apps that emits one
Applicationper app on this page. - tools secrets-and-vso — the
app_policy+app_rolesmodule pair that turns<app>into Vault policies, roles, and CI identities; the VSO runtime path the Postgres archetype rides. - factory postgres-iac — the per-app PostgreSQL database +
<app>_rolethe webapp archetype depends on. - naming-conventions — the
<app>join key that threads through repo, image, namespace, DB, Vault role, and ServiceAccount. - safe-prod-like-environment ADR — why the lab keeps apps deployed prod-like and the state/recovery trade-offs behind the two archetypes.