Aller au contenu

Infrastructure & déploiement

Compute principal sur Railway. Base de données Neon. Secrets Doppler. Pas de Kubernetes dans le chemin de déploiement applicatif.

Diagramme

flowchart TB
  subgraph devops["CI/CD"]
    gh["GitHub Actions<br/>branche develop"]
    doppler["Doppler"]
    flyway["Flyway migrations"]
  end

  subgraph railway["Railway — europe-west4"]
    api_svc["API HTTP<br/>5 replicas"]
    cron["Graphile cron"]
    queue["Graphile queue"]
    w1["Workers domaine"]
    web_svcs["SPAs nginx"]
    ddagent["Agent Datadog"]
    proxy["lw-static-ip-proxy"]
  end

  subgraph managed["Services managés"]
    neon[("Neon Postgres")]
    redis_svc[("Redis")]
    s3_svc[("AWS S3")]
  end

  subgraph mobile_deploy["Mobile natif"]
    eas["Expo EAS"]
    stores["App Store / Play Store"]
  end

  gh --> flyway
  flyway --> api_svc
  doppler --> gh
  doppler --> railway

  api_svc --> neon
  cron --> neon
  w1 --> neon
  api_svc --> redis_svc
  api_svc --> s3_svc
  w1 --> proxy

  eas --> stores
  gh --> eas

Environnements

Env API App invest web Doppler (api project) Railway env
Dev https://api.dev.bricks.co https://app.dev.bricks.co dev dev
Staging https://api.staging.bricks.co À compléter staging staging
Prod https://api.bricks.co https://app.bricks.co production prod

Détail secrets : Doppler — Variables d'environnement.

Sauvegardes Neon prod

Dump nightly de bricks-prod vers S3 (bricks-prod-neon-s3-backup, eu-central-1). Rétention 7 jours glissants sur les dumps datés (noncurrent 1 j sur ce préfixe). bricks-prod-last-good.dump sans expiration, au plus 7 versions non courantes (30 j max). Procédure : Sauvegardes Neon prod (S3).

Déploiement par composant

Composant Image / build Trigger CI Hébergement
API + workers Dockerfile.api → image brickssas/bricks-api:<sha> (Docker Hub privé) api-release-dev.yml (push develop, dispatch) → api-release-prod.yml (workflow_run, 2 approbations API Prod) Railway (10 services par env)
Mobile web Expo build:web + nginx app-invest-web-deploy-prod.yml Railway
front-app legacy Vite + nginx app-invest-legacy-deploy-prod.yml Railway
PDP web Vite + nginx app-pdp-financement-web-deploy-prod.yml (push develop) Railway
BO projects / invest / app-config / notaire Vite + nginx Validate CI ; deploy : À compléter Railway
Mobile natif EAS app-invest-mobile-build-prod.yml Stores + OTA
Storybook Chromatic storybook-chromatic.yml Chromatic
Docs MkDocs Dockerfile.docs docs-validate.yml docs.bricks.internal

Validate PR (app-invest-validate.yml, app-pdp-financement-validate.yml) : lint + tsc + tests. Le bundle web (expo export) est le job Railway, pas un check à chaque commit.

Pipeline de release API

api-release-dev.yml construit Dockerfile.api une fois sur Blacksmith, pousse brickssas/bricks-api:<sha> sur Docker Hub (privé) et livre dev. Son succès sur develop déclenche api-release-prod.yml (workflow_run) : un job preflight vérifie avant l'approbation que l'image existe et liste dans le summary les migrations Flyway et Better Auth en attente, puis deux jobs livrent prod, chacun derrière une approbation API Prod : migrate (Better Auth et Flyway s'exécutent depuis l'image), puis deploy (les 10 services Railway sont pointés sur l'image via la CLI Railway — .github/scripts/railway-deploy-image.sh, railway environment edit —, le job attend le statut SUCCESS de chaque déploiement, puis pose le tag mobile :dev / :prod, « ce qui tourne »). Les deux jobs vivent dans api-release-env.yml (workflow_call) ; API Dev n'a pas de règle d'approbation, dev les enchaîne sans pause. Prod ne construit jamais : un commit n'atteint prod que par une image que dev a fait tourner.

flowchart LR
  trigger["push develop / Run workflow (branche + env)"] --> build["build — Blacksmith 4 vCPU"]
  build --> hub["Docker Hub brickssas/bricks-api:sha"]
  hub --> dev["release dev — migrate puis deploy"]
  dev -- "workflow_run, develop vert" --> preflight["preflight prod — image, Flyway, BA"]
  preflight -- "approbation API Prod" --> migrate["migrate prod — BA, Flyway"]
  migrate -- "approbation API Prod" --> deploy["deploy prod — 10 services"]
  hub -.-> migrate
Avant Après
Builds par push develop 20 (Railway, 1 par service) + 3 sur runner 1
Chemin critique dev ~16 min ~9 min
Déployer une branche api-deployToEnv.yml dev : api-release-dev.yml → Run workflow → branche ; prod : develop uniquement, api-release-prod.yml → Run workflow (+ sha optionnel), l'image doit être passée par dev

Points d'attention :

  • Railway ne lit railway.<service>.json que depuis une arborescence source. Avec une image, le script construit un patch d'environnement (source.image, deploy.*, registryCredentials, limitOverride de chaque JSON) et le commite via railway environment edit : le dépôt reste la source de vérité, un worker ne peut pas démarrer avec le CMD de l'API, et le commit déclenche lui-même les déploiements. limitOverride est écrêté au plafond du plan lu dans l'API (subscriptionPlanLimit, 24 vCPU / 24 Go sur le plan Pro) : les JSON demandent 32, Railway écrêtait déjà depuis une arborescence source, et un patch au-dessus est refusé.
  • RAILWAY_TOKEN est un project token lié à un environnement : c'est lui qui décide dev ou prod, le script vérifie que le nom correspond avant tout appel. Les services sont résolus par nom (serviceName) dans cet environnement : un nom absent fait échouer le job avant tout déploiement.
  • Relire un deploy : GET /probe/version → gitCommitSha = tag de l'image. Rollback : Run workflow prod avec le sha voulu (commit de develop, image existante).
  • Prod ne déploie que des commits de develop : le preflight refuse un sha qui n'en est pas un ancêtre (il exécute des scripts du commit avec les secrets prod, sans approbation), puis vérifie l'image (docker manifest inspect) avant de toucher à la base. Dev rouge → le run prod est skipped ; rattrapage par Run workflow prod, sha du dernier commit passé par dev.

Services Railway (API)

Déployés en parallèle, même liste dev et prod :

  1. API HTTP
  2. Graphile cron worker
  3. Graphile queue worker
  4. Bricks assignation
  5. Bricks P2P creation
  6. Reservation confirmation
  7. Reservation expiration
  8. Played Lemonway P2P
  9. Pending Lemonway P2P
  10. PDF generator

Config par service : projects/api/railway/railway.<service>.json (API : healthcheck /probe/version, 5 replicas ; workers : startCommand: node dist/worker/main.js). BO app-config : projects/bricksoffice-app-config/Dockerfile (réglages service dans le dashboard Railway).

SPAs back-office (Railway)

Aucune GitHub Action ne déploie les back-offices : chaque service Railway est branché au dépôt et lit le fichier de config posé à la racine de l'app.

App Config Railway Dockerfile ARG de build
bricksoffice-projects railway.bricksoffice-projects.json projects/bricksoffice-projects/Dockerfile VITE_API_URL, VITE_APP_INVEST_URL, VITE_APP_PROJECT_ANALYSIS_URL, VITE_APP_BO_INVEST_URL
bricksoffice-invest railway.bricksoffice-invest.json projects/bricksoffice-invest/Dockerfile VITE_API_URL
bricksoffice-notaire railway.bricksoffice-notaire.json projects/bricksoffice-notaire/Dockerfile VITE_API_URL
bricksoffice-technique railway.bricksoffice-technique.json projects/bricksoffice-technique/Dockerfile VITE_API_URL + hub VITE_* (Technique is not a hub tile)

Chaque config déclare ses watchPatterns — l'app plus ses dépendances workspace. Un commit hors de ces chemins ne redéploie pas le service.

Build : 4 étapes (turbo prune → install → turbo run build → image nginx statique). Les VITE_* sont des ARG de build, donc figés dans le bundle : les changer impose un redéploiement. Runtime : seule PORT est lue, par entrypoint.sh qui l'injecte dans nginx.conf.

Le JSON dans le dépôt ne crée pas le service. Un nouveau back-office demande, côté dashboard Railway : création du service, dépôt et branche suivis, chemin du fichier de config, ARG de build, domaine.

Base de données

Provider Neon
Migrations Flyway (projects/api/migration/flyway/) + Better Auth migrate
Branches Reset cron quotidien (neon-daily-production-reset-common-branch.yml)
Scaling Crons API via Neon API (avant/après collectes)

Jobs async

Système Rôle
Graphile Worker Crons + queue dynamique (tables Postgres graphile_worker)
Workers domaine Boucles P2P, assignation, PDF, réservations
Redis Cache uniquement — pas de message broker
GitHub Actions cron Neon branch reset, autres : À compléter

Observabilité

Couche Outil
API traces dd-trace (projects/api/src/tracing.ts)
API logs Pino → agent Datadog Railway
Front RUM Datadog Browser / RN SDK
Storybook Chromatic
Incidents docs/postmortems/

CI/CD (workflows clés)

Workflow Rôle
*-validate.yml tsc / lint / build sur PR
api-release-dev Build image + release dev (push develop, Run workflow de branche) ; déclenche api-release-prod
api-release-prod Preflight, puis migrate et deploy chacun derrière une approbation API Prod, depuis l'image dev (workflow_run)
api-release-env Jobs migrate → deploy partagés dev/prod (workflow_call, inputs env + sha)
app-invest-mobile-ota-update-prod OTA Expo prod (GitHub Action)
.eas/workflows/ota-dev.yml OTA dev de PR + build dev-client conditionnel piloté par fingerprint (EAS Workflow)
neon-* Gestion branches Neon
knip-validate Dépendances inutilisées

Branche d'intégration : develop (deploy prod path-filtered).

À compléter

Sujet Owner Notes
URLs prod BO / PDP / mobile web À compléter
DNS et CDN (S3, SPAs) À compléter
RTO / RPO par service À compléter
Plan incident Railway / Neon / Lemonway À compléter
Coûts infra par environnement À compléter