Files
factory/vibe/guidebooks/applications/README.md
Gabriel Radureau 7bf83e75ed docs(vibe): add erp/ guidebook (Dolibarr deployment + backup/recovery + ops)
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>
2026-06-23 22:12:11 +02:00

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 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 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 &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 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/ and iac/ sends agents confidently down dead paths.

Cross-references

  • 01 · factory — the ArgoCD app-of-apps that emits one Application per app on this page.
  • tools secrets-and-vso — 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 — the per-app PostgreSQL database + <app>_role the 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.