Files
factory/doc/runbooks/new-web-app
Gabriel Radureau c00c4cdd5c feat(multi-env): Phase B — make factory machinery env-capable (no activation)
ADR-0002 Phase B. Makes postgres/iac, argocd, and the conventions docs
multi-environment-capable WITHOUT activating any sandbox yet — every app
stays prod-only, so this change is behaviour-neutral:
  - postgres/iac `tofu plan` is a no-op (proven: the elision flatten keys
    are bare app names, db=<app>, role=<app>_role — identical addresses)
  - the argocd apps.yaml render is byte-identical (181→181 lines, diff
    empty) since no app declares `envs`

postgres/iac:
- variables.tf: `applications` becomes set(object({name, envs=optional(["prod"])}))
- main.tf: a `local.app_instances` flatten of applications × envs keyed by the
  elided instance id (env=prod → "<app>"); per-app resources iterate it and
  reference each.key / each.value.{database,role}. For prod-only apps every
  resource address + attribute is unchanged. (main.tf also got a full
  `tofu fmt` pass — the pgbouncer function block reindents 4→2 spaces, which
  is cosmetic; the correctness gate is the CI tofu plan, not the text diff.)
- terraform.tfvars: string entries → { name = "..." } objects.

argocd/templates/apps.yaml:
- after the prod Application, a `range $app_attr.envs` loop renders one extra
  Application per non-prod env: name/namespace `<app>-<env>`, shared repoURL,
  helm.valueFiles [values.yaml, values-<env>.yaml], per-env syncPolicy override.
  Renders nothing while no app sets `envs` → prod render unchanged.

docs:
- doc/runbooks/new-web-app/conventions.md (FR, authoritative): new section
  "Plusieurs environnements pour une même app" — elision rule, suffix rule,
  snake-case owner-role exception, erp/erp-sandbox table, ADR-0002 link.
- vibe/guidebooks/lab-ecosystem/naming-conventions.md (EN mirror): the env
  coordinate section + a "Two sandbox models" section reconciling the
  separate-cluster (ADR-0001, names repeat) vs in-cluster sibling (ADR-0002,
  <env> suffix) strategies; Last Updated bumped; ADR-0002 cross-links.

Activation (erp gets envs=["prod","sandbox"] in postgres tfvars + argocd
values + erp/iac) is Phase D, gated by its own plan review.

Refs ADR-0002 (factory#15). Phase A = tools#2 (merged). Phase C = erp#11 (merged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-28 16:28:28 +02:00
..

Factory > Doc > Runbooks > Nouvelle application web

Mettre en service une nouvelle application web

Last Updated: 2026-05-31 Status: Procédure courante Related: Conventions de nommage · Checklist · ADR CI/CD · ADR Vault

C'est quoi ?

Ce runbook décrit, de zéro, comment faire vivre une nouvelle application web sur la plateforme Arcodange. Le pattern est GitOps : l'app habite son propre dépôt Gitea, sa base de données et ses accès Vault sont provisionnés par Terraform/OpenTofu, et ArgoCD déploie son chart Helm dans un namespace dédié. Les identifiants Postgres ne sont jamais écrits en clair : ils sont générés à la volée par Vault et injectés dans le pod par le Vault Secrets Operator (VSO).

La mécanique est répartie sur trois dépôts — le dépôt plateforme factory, le dépôt des services partagés tools, et le nouveau dépôt de l'app — avec des dépendances d'ordre strictes (voir plus bas). Les exemples de référence sont erp (image publique + DB) et webapp (image maison + DB).

Sert à :

  1. Créer un dépôt Gitea et son squelette (chart/, iac/, .gitea/workflows/).
  2. Provisionner la base de données, son rôle propriétaire, et les accès Vault (statiques + dynamiques).
  3. Déployer l'app via ArgoCD et l'exposer derrière Traefik (interne .lab ou public .fr + CrowdSec).

Carte de bout en bout

%%{init: {'theme': 'base'}}%%
flowchart TB
    classDef step fill:#2563eb,stroke:#1e40af,color:#fff
    classDef plat fill:#059669,stroke:#047857,color:#fff
    classDef gitops fill:#7c3aed,stroke:#6d28d9,color:#fff
    classDef run fill:#b45309,stroke:#92400e,color:#fff

    REPO["1 · Dépôt Gitea<br>arcodange-org/app"]:::step
    DB["2 · factory/postgres/iac<br>base app + rôle app_role"]:::plat
    VAULT["3 · tools/hashicorp-vault/iac<br>gitea_cicd_app + policies app / app-ops"]:::plat
    CONTENT["4·5·6 · chart/ + iac/ + .gitea/<br>push → CI build image &amp; tofu apply"]:::step
    ARGO["7 · factory/argocd/values.yaml<br>→ Application ArgoCD (ns app)"]:::gitops
    POD["Runtime · Pod(SA app) → VSO → Vault<br>creds/app → pgbouncer.tools → base app"]:::run

    REPO --> DB
    REPO --> VAULT
    DB --> CONTENT
    VAULT --> CONTENT
    CONTENT --> ARGO
    ARGO --> POD

Ordre des opérations (le point le plus important)

Important

Les étapes ne sont pas interchangeables. Le rôle JWT de CI gitea_cicd_<app> (étape 3) et le rôle Postgres <app>_role (étape 2) doivent exister avant que la CI Terraform de l'app (étape 6 appliquant l'étape 5) ne s'exécute — sinon l'authentification Vault de la CI échoue, ou le module app_roles n'a pas de <app>_role à qui rattacher les credentials dynamiques.

[01] Dépôt Gitea sous arcodange-org (hérite les secrets CI d'org)
        │
        ├──> [02] factory/postgres/iac   → base <app> + <app>_role + user_lookup()
        │
        └──> [03] tools/hashicorp-vault/iac → gitea_cicd_<app> (JWT CI) + policies <app> / <app>-ops
                    │   (02 et 03 indépendants entre eux, mais TOUS DEUX avant 05)
                    ▼
[04+05+06] Contenu du dépôt : chart/ + iac/ + .gitea/workflows/ (+ Dockerfile)
        │   push → CI « dockerimage » build l'image · CI « vault » applique iac/
        │   → creds/<app> (rôle DB dynamique) + rôle K8s <app> + secrets KV
        ▼
[07] factory/argocd/values.yaml → ArgoCD crée l'Application → déploie le chart dans le namespace <app>
        ▼
Runtime : Pod(SA <app>) → VSO → VaultAuth(role <app>) → creds/<app>
          → user PG dynamique héritant de <app>_role → pgbouncer.tools → base <app>

Prérequis plateforme (déjà en place)

Ces fondations existent et ne sont pas à refaire pour chaque app :

Brique Rôle
Mounts Vault kvv2, postgres, transit, auth kubernetes tools/hashicorp-vault/iac/main.tf Moteurs de secrets + auth K8s
Connexion Vault→Postgres (via pgbouncer.tools, user credentials_editor) idem Permet à Vault d'émettre des users PG dynamiques
Rôle JWT de bootstrap gitea_cicd + app OAuth2 Gitea (gitea_app_id) gitea_oidc_auth.yml Échange OIDC Gitea → JWT Vault dans la CI
Bot tofu_module_reader (clé SSH dans kvv1/gitea/tofu_module_reader) factory/iac/gitea_tofu_ci_user.tf Laisse la CI cloner le module partagé tools en SSH
Secrets Actions d'organisation (HOMELAB_CA_CERT, vault_oauth__sh_b64, PACKAGES_TOKEN) org Gitea arcodange-org Hérités par tout dépôt de l'org

Index des étapes

# Page Ce qu'on y fait Statut
Conventions de nommage Le nom <app> réutilisé à l'identique partout (à lire en premier)
01 Dépôt Gitea Créer le dépôt sous arcodange-org + squelette
02 Base de données factory/postgres/iac → base <app> + rôle <app>_role
03 Vault plateforme tools/hashicorp-vault/iacgitea_cicd_<app> + policies
04 Chart Helm Le chart de l'app (DB via pgbouncer, secrets VSO, ingress)
05 Terraform de l'app iac/ → module app_roles (creds dynamiques + rôle K8s)
06 Workflows CI .gitea/workflows/ : tofu apply + build image
07 Enregistrement ArgoCD factory/argocd/values.yaml → Application + déploiement
08 Checklist Récapitulatif ordonné + definition of done

Légende de statut

actif · 🟡 dégradé/beta · 🔴 critique/EOL · ⚠️ problème connu · désactivé

Comment éditer ce runbook

  1. Ajouter une page → la créer depuis le template tree-docs adéquat et ajouter sa ligne dans l'index ci-dessus.
  2. Garder les liens croisés bidirectionnels → toute dépendance citée dans une page (Upstream/Downstream) doit avoir sa réciproque sur l'autre page.
  3. Mettre à jour Last Updated: ci-dessus après tout changement de structure.
  4. Les exemples cités (erp, webapp) sont vivants : revérifier les snippets contre le code réel avant de s'y fier aveuglément.