ERHA Cloud
L'hébergement par ERHA et le fichier .erha/config.yaml qui le décrit.
ERHA Cloud est l'hébergement fourni par ERHA. Une application hébergée par ERHA tourne sur notre infrastructure, en Europe (Amsterdam). ERHA crée l'hébergement, construit l'image, déploie à chaque push sur la branche de production, crée les bases de données et les autres services, déclare les domaines et facture ce qui tourne.
Pour qu'une application soit hébergée par ERHA, choisissez « Par ERHA » dans Paramètres de l'application, section Déploiement, puis lancez la mise en place. Une landing est servie par Vercel. Les autres applications tournent sur ERHA Cloud.
Cette page est la référence du fichier .erha/config.yaml. Elle dit ce que chaque clé fait, ce qu'ERHA crée pour chaque service, et quelles variables il pose.
Ce qu'il faut dans le dépôt
Un fichier, parfois deux :
- Un fichier
.erha/config.yamlà la racine du dépôt (pas dans le dossier de l'application). Il décrit toutes les applications du dépôt hébergées par ERHA, les services qu'elles utilisent et leurs routes. Sans ce fichier, ERHA déploie l'application seule, sans service. - Pour une application PHP, Node, Python ou Ruby, rien d'autre : la section de l'application déclare sa technologie (
type: php:8.4) et ERHA construit l'image lui-même (voir « Sans Dockerfile : l'image construite par ERHA »). Pour une autre technologie, ou une image très particulière, unDockerfiledans le dossier de l'application.
Sans Dockerfile : l'image construite par ERHA
Quand la section d'une application porte type, ERHA écrit la recette de l'image et la construit à chaque déploiement, sur ses images de base : PHP (nginx et PHP-FPM, Composer), Node (yarn et pnpm), Python (uv) ou Ruby (bundler). Ces images écoutent sur 8080, tournent sans droits root, et ERHA les met à jour (correctifs de sécurité). Le dépôt ne garde pas de Dockerfile, ni de configuration nginx ou supervisord. Le .dockerignore du dossier de l'application sert toujours.
| Clé | Règles |
|---|---|
type | La technologie et sa version : php:8.2 à php:8.5, node:22, node:24, node:26, python:3.13, python:3.14, ruby:3.4, ruby:4.0. php:7.4 et php:8.1 restent proposées pour les applications qui ne peuvent pas encore changer de version : elles ne reçoivent plus de correctifs de sécurité. |
extensions | PHP seulement. Les extensions à ajouter : celles que demande composer.lock. opcache, intl, zip, pdo_pgsql et pdo_mysql sont déjà dans l'image. |
build | Les commandes lancées dans /app après la copie du code, dans l'ordre. Par défaut : composer install --no-dev --optimize-autoloader (PHP), l'installation avec le lock du dépôt, devDependencies comprises, puis npm run build --if-present (Node), uv sync --frozen --no-dev ou requirements.txt (Python), bundle install puis les assets de Rails (Ruby). |
web | PHP seulement. document_root (public) et front_controller (index.php). |
start | La commande qui démarre le serveur sur $PORT. Aucune pour PHP. npm start par défaut pour Node, puma pour Ruby. Obligatoire pour Python. |
Les variables publiques qu'un build de front lit (NEXT_PUBLIC_*, VITE_*, NUXT_PUBLIC_*, PUBLIC_*, REACT_APP_*, EXPO_PUBLIC_*), déclarées sous env ou variables, sont données au build. Les règles qui suivent, sauf celles propres au Dockerfile, valent aussi.
Règles du Dockerfile
- L'application écoute sur le port donné par la variable
PORT, sur toutes les interfaces (0.0.0.0). ERHA posePORTavec la valeur de la cléportdu fichier (8080 par défaut). - ERHA vérifie que ce port accepte une connexion TCP, 30 secondes après le démarrage puis toutes les 10 secondes. Après trois échecs, l'instance est relancée.
- L'image démarre seule, sans docker compose ni autre service à côté.
- Aucun secret dans l'image : pas de clé en dur, pas de fichier qui porte un secret. Un
.dockerignoreécarte.git, les dépendances installées, les builds locaux et les fichiers d'environnement locaux (.env.local,.env.*.local). - Un
.envversionné qui ne porte que des valeurs par défaut reste dans l'image quand le framework en a besoin pour démarrer (Symfony refuse de démarrer sans lui). Les vraies valeurs viennent des variables posées par ERHA et de la section Variables, qui l'emportent sur le fichier. - Le démarrage du conteneur ne fait que lancer le serveur. Ni migration, ni génération de clé, ni tâche qui touche la base : chaque instance passe par là, à chaque redémarrage et à chaque montée en charge.
- Les migrations vont dans
hooks.deploy: ERHA les lance une fois par déploiement, avant la mise en ligne. - Aucune clé ne se génère dans l'image ni au démarrage (clés JWT, clés de chiffrement) : deux instances auraient deux clés différentes. Déclarez-la sous
variablesavecgenerate: ERHA la crée une fois par environnement (voir « Variables attendues »). - L'étape qui installe les dépendances utilise la même version du langage et les mêmes extensions que l'image finale. Une image d'outil (
composer,node,python) a sa propre version : le lock peut y être refusé, ou une dépendance peut manquer à l'exécution. - PHP : installez Composer dans l'image finale, après les extensions (
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer), et lancezcomposer install --no-devdans cette image. Installez chaque extensionext-*demandée parcomposer.lock(composer check-platform-reqs --lock --no-devdoit passer au build). - L'image contient
/bin/sh. Les hooks, les crons et les workers lancent leur commande avec/bin/sh -c.
Ce que règle la console, ce que dit le fichier
Dans le fichier .erha/config.yaml | Dans la console |
|---|---|
| Les applications du dépôt, leur dossier, leur Dockerfile et leur port | Le choix d'héberger par ERHA, la branche de production, le domaine |
| Les services (type et version), les relations | La taille et le nombre d'instances des applications |
Les variables fixes et publiques (env) | La taille, le disque et la haute disponibilité des services |
| Les hooks, les crons et les workers | Les secrets et les autres variables (section Variables) |
| Les routes vers des sous-domaines | Les tenants, leurs ressources et leurs domaines |
Le contenu des bases dans les previews (preview.data) | Les previews (« Un environnement de test pour chaque changement »), les règles d'anonymisation, le fichier SQL de départ |
Les ressources ne s'écrivent jamais dans le fichier. Les clés size, instances, disk et ha sont ignorées si elles y sont encore : elles ne provoquent pas d'erreur, et la valeur de la console s'applique.
Faire écrire les fichiers par un agent
- Magic Install, dans l'onglet Déploiement du projet ou dans Outils > Hébergement, ouvre une tâche. L'agent lit le dépôt, écrit le Dockerfile et la section de l'application dans
.erha/config.yaml, sans retirer celles des autres applications, puis vous rend la tâche pour relecture. - « Ajouter un service », dans l'onglet Déploiement, ouvre aussi une tâche Magic Install. L'agent ajoute le service et la relation dans le fichier du dépôt. La taille du service se choisit ensuite dans la console.
Les secrets ne vont jamais dans le fichier : l'agent les déclare sous variables, sans valeur, et ERHA vous dit lesquels remplir.
Quand ERHA lit et applique le fichier
ERHA lit le fichier sur la branche de production : à la mise en place, après chaque déploiement (au commit déployé), et quand vous cliquez sur « Relire et appliquer » dans l'onglet Déploiement. Une preview lit le fichier de la branche de sa merge request.
Après une lecture sans erreur, ERHA applique le fichier :
- il crée les services, les hooks, les crons, les workers et les domaines qui manquent ;
- il met à jour ceux qui ont changé ;
- il supprime les hooks, les crons, les workers et les caches Redis retirés du fichier ;
- il garde les bases de données et les buckets retirés du fichier : ils sont marqués « N'est plus dans le fichier », à supprimer à la main ;
- il attend que les bases répondent, puis redéploie.
L'onglet Déploiement montre le fichier tel qu'ERHA l'a lu, ses erreurs, et l'état de chaque élément : en ligne, en création, en échec.
Exemple complet
Une API Symfony et un front Next.js dans le même dépôt, avec PostgreSQL, Redis, un bucket S3, un hub Mercure, l'envoi d'emails, un cron et un worker.
applications:
api: # nom libre : minuscules, chiffres, tirets
root: apps/api # dossier de l'application dans le dépôt
dockerfile: Dockerfile # chemin depuis root
port: 8080 # ERHA pose PORT=8080
relationships: # nom de la relation: nom du service
database: db # DATABASE_URL, DATABASE_HOST...
cache: cache # CACHE_URL..., et REDIS_URL
storage: files # STORAGE_BUCKET..., et AWS_REGION...
mercure: hub # MERCURE_URL, MERCURE_PUBLIC_URL, MERCURE_JWT_SECRET
mailer: mail # MAILER_DSN, dans les previews seulement
env: # valeurs fixes et publiques
APP_ENV: prod
FILES_PUBLIC_URL: "https://{storage.bucket}.s3.{storage.region}.scw.cloud"
hooks:
deploy: php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
post_deploy: php bin/console cache:pool:clear cache.global_clearer
crons:
reports:
spec: "0 4 * * *" # chaque jour à 4h UTC
command: php bin/console app:reports:send
workers:
messenger:
command: php bin/console messenger:consume async --time-limit=3600
front:
root: apps/front
port: 3000
services:
db:
type: postgresql:16
preview:
data: anonymized # copie anonymisée de la production
cache:
type: redis:7
files:
type: s3
hub:
type: mercure
mail:
type: mailer
routes:
"https://{default}/": front
"https://api.{default}/": api
Le fichier a trois parties : applications (obligatoire), services et routes. Toute autre clé à la racine est une erreur.
Applications
Une entrée par application du dépôt hébergée par ERHA. Le nom de l'entrée est libre : il commence par une lettre minuscule, puis des minuscules, des chiffres et des tirets, 40 caractères au plus. Il sert à nommer les crons, les workers et les routes. Il n'a pas besoin d'être le nom de l'application dans ERHA.
ERHA rattache une entrée à une application du projet par son dossier : la clé root doit être égale au dossier de l'application, réglé dans Paramètres de l'application, section Code source. Les deux valeurs sont comparées sans les / du début et de la fin : apps/api, /apps/api et apps/api/ sont le même dossier. Une application à la racine du dépôt a root: / (la valeur par défaut). Si aucune entrée n'a le dossier de l'application, la lecture donne l'erreur application_not_declared.
| Clé | Type | Par défaut | Règles |
|---|---|---|---|
root | texte | / | Dossier de l'application dans le dépôt. .. est refusé. |
dockerfile | texte | Dockerfile | Chemin du Dockerfile, depuis root. Sans effet quand type est déclaré. |
type, extensions, build, web, start | aucun | L'image construite par ERHA, sans Dockerfile. Voir « Sans Dockerfile : l'image construite par ERHA ». | |
port | nombre | 8080 | Entier de 1 à 65535. Port sur lequel l'application écoute. ERHA pose PORT avec cette valeur. |
relationships | liste de paires relation: service | aucune | Chaque service nommé doit exister dans services. Voir « Relations et variables d'environnement ». |
env | liste de paires NOM: valeur | aucune | Variables fixes. Voir plus bas. |
variables | liste de variables | aucune | Les variables que le code lit sans valeur dans le fichier. Voir « Variables attendues ». |
hooks | deploy, post_deploy | aucun | Une commande par hook. Voir « Hooks ». |
crons | liste de crons | aucun | Chaque cron a spec et command. Voir « Crons ». |
workers | liste de workers | aucun | Chaque worker a command. Voir « Workers ». |
size et instances sont ignorées : la taille et le nombre d'instances se règlent dans la console (voir « Ressources »).
La clé env
- Le nom d'une variable commence par une lettre ou
_, puis des lettres, des chiffres et_, 255 caractères au plus. Un nom qui commence parQOVERY_est refusé. - La valeur est un texte, un nombre ou un booléen.
truedevient"true",falsedevient"false". - Une valeur peut reprendre un champ d'une relation de l'application, entre accolades :
{relation.champ}. La liste des champs est dans « Relations et variables d'environnement ». envne contient que des valeurs fixes, publiques et identiques dans tous les environnements (APP_ENV: prod). Ce qui change entre la production, une preview ou un tenant (adresses, identifiant public d'un client OAuth) se pose dans Variables. Un secret (clé d'API, mot de passe d'un service externe) aussi.
Variables attendues : la clé variables
La clé variables liste les variables que le code lit et que ni env ni une relation ne pose. Elle ne porte aucune valeur : elle dit qui la fournit. ERHA lit aussi les fichiers .env, .env.example, .env.dist et .env.sample du dossier de l'application et y repère les variables sans valeur utilisable (vide, changeme, your-key, un chemin vers une clé que l'image n'a pas). Ce que le fichier déclare l'emporte sur ce qu'ERHA devine.
applications:
api:
variables:
APP_SECRET: { generate: secret, format: hex, bytes: 16 }
JWT_SECRET_KEY: { generate: rsa_keypair, public: JWT_PUBLIC_KEY, passphrase: JWT_PASSPHRASE }
STRIPE_SECRET_KEY: { service: Stripe, help: https://dashboard.stripe.com/apikeys }
GOOGLE_CLIENT_ID: { secret: false }
PARTNER_TOKEN: { description: Donné par le partenaire logistique }
| Clé | Valeurs | Ce qu'elle fait |
|---|---|---|
generate | secret, laravel_key, rsa_keypair | ERHA crée la valeur. secret : une chaîne aléatoire. laravel_key : la forme base64:… de APP_KEY. rsa_keypair : une clé privée RSA 4096 chiffrée, sa clé publique et sa passphrase. |
format | hex, base64 (par défaut) | Pour generate: secret. |
bytes | 16 à 128, 32 par défaut | Pour generate: secret : le nombre d'octets aléatoires. |
public, passphrase | un nom de variable | Pour generate: rsa_keypair : les variables de la clé publique et de la passphrase. Par défaut, le nom de la clé suivi de _PUBLIC et _PASSPHRASE. |
service, help | un nom, une adresse https:// | La variable vient d'un service tiers : la console dit où la créer. |
secret | true (par défaut), false | false pour une valeur publique, lisible dans la console. |
shared | false (par défaut), true | true : une seule valeur pour tous les environnements du projet. Sinon chaque environnement (production, preview, tenant) a la sienne. |
description | texte | Montré à côté de la variable. |
Ce que fait ERHA :
- Une variable
generatedéclarée dans le fichier, ou repérée dans un.envsans valeur utilisable, est créée à l'application du fichier dans chaque environnement qui ne l'a pas, en secret. Une variable repérée qui a déjà une valeur dans le dépôt attend un clic sur « Générer » : la remplacer déconnecterait les personnes connectées. - ERHA ne garde aucune valeur. Une variable générée est marquée comme telle chez l'hébergeur et n'est jamais remplacée par le fichier.
- « Faire tourner » (Paramètres de l'application > Variables) remplace une variable générée par une nouvelle valeur, la paire entière pour une clé RSA, puis redéploie la production. Ce qui a été signé avec l'ancienne valeur n'est plus accepté.
- Les autres variables attendues sans valeur en production sont listées dans Variables avec « Remplir », qui ouvre le formulaire à la bonne portée. Les admins de l'espace reçoivent un email quand une variable manque, une fois par variable, et Activité garde une ligne jusqu'à ce qu'elle soit posée.
- Une clé qui chiffre des données déjà stockées ne se déclare pas avec
generate: une nouvelle valeur rendrait ces données illisibles. Déclarez-la sansgenerateet posez la valeur actuelle.
Services
Un service est une ressource utilisée par une ou plusieurs applications du projet : une base, un cache, un bucket, un hub Mercure, l'envoi d'emails. Le nom du service est libre, avec les mêmes règles qu'un nom d'application. Un projet peut avoir plusieurs services du même type : déclarez un service par base, avec un nom différent.
| Clé | Règles |
|---|---|
type | Obligatoire. Le type, suivi de : et de la version pour les types qui en ont une : postgresql:16, mysql:8, redis:7. Sans version, ERHA prend la version par défaut. s3, mercure et mailer s'écrivent sans version. |
preview | Bases de données. Le contenu de la base dans les previews : data et seed. Voir « Previews ». Sur un autre service, seul data: empty est accepté (c'est déjà le comportement). |
environments | Les environnements où le service existe : production (la production et les tenants) et preview. Par défaut, les deux, sauf mailer qui n'existe que dans les previews. Exemple : environments: [production, preview] sur un mailer donne aussi une boîte Mailpit en production, qui garde les emails au lieu de les envoyer. |
size, disk et ha sont ignorées : elles se règlent dans la console.
Types, versions et ce qu'ERHA crée
| Type | Versions | Par défaut | En production | Dans une preview | Dans un tenant |
|---|---|---|---|---|---|
postgresql | 14, 15, 16 | 16 | Une instance PostgreSQL managée (Scaleway), sauvegardes activées. La base porte le nom du service, avec _ à la place de -. | Une base à part dans l'instance PostgreSQL que l'espace garde pour ses previews, avec son propre utilisateur. | Une instance managée à lui, comme en production. |
mysql | 8 | 8 | Une instance MySQL managée (Scaleway), sauvegardes activées. | Une base à part dans l'instance MySQL des previews de l'espace. | Une instance managée à lui. |
redis | 7 | 7 | Un Redis en conteneur, privé, sans sauvegarde. | Un Redis propre à la preview, vide au départ. | Un Redis à lui. |
s3 | aucune | aucune | Un bucket privé compatible S3 (Scaleway, région nl-ams). | Un bucket propre à la preview, vide, supprimé avec elle. | Un bucket à lui. |
mercure | aucune | aucune | Un hub Mercure en conteneur (dunglas/mercure), avec une adresse publique. ERHA génère sa clé JWT et la garde chiffrée. CORS ouvert à toutes les origines. | Un hub propre à la preview, avec sa propre clé. | Un hub à lui. |
mailer | aucune | aucune | Rien par défaut. Posez MAILER_DSN dans Variables (voir « Emails en production »). Avec environments: [production, preview], une boîte Mailpit. | Une boîte Mailpit : les emails y sont gardés, jamais envoyés. | Comme en production. |
Les ressources de chaque service (taille, disque, haute disponibilité) se règlent dans la console. Leurs valeurs par défaut :
| Type | Tailles | Par défaut | Autres réglages |
|---|---|---|---|
postgresql, mysql | s, m, l, xl (instances Scaleway DB-DEV-S, DB-DEV-M, DB-DEV-L, DB-GP-XS) | s | Disque de 5 à 1000 Go, 5 Go par défaut. Haute disponibilité, coupée par défaut. |
redis | xs (128 Mo), s (256 Mo), m (512 Mo), l (1 Go) | s | Aucun. |
s3, mercure, mailer | aucune | Aucun. |
Relations et variables d'environnement
Une relation donne à une application l'accès à un service, sous un nom choisi par l'application. Le nom de la relation suit les mêmes règles qu'un nom d'application.
relationships:
database: db
analytics: stats
Relation vers une autre application
Une relation peut aussi nommer une autre application du fichier. Elle donne <NOM>_URL (l'adresse publique de cette application, https://…) et <NOM>_HOST, dans le même environnement : l'adresse de production en production, celle du tenant dans un tenant, celle de la preview dans une preview. Les placeholders {<relation>.url} et {<relation>.host} s'utilisent dans env, par exemple pour une variable publique lue au build d'un front :
applications:
web:
root: web
relationships:
api: api
env:
NEXT_PUBLIC_API_URL: "{api.url}"
api:
root: api
Ces valeurs ne sont pas secrètes. Une application ne peut pas se nommer elle-même.
Variables de chaque relation
Pour chaque relation, ERHA pose des variables dont le nom commence par celui de la relation, en majuscules, avec _ à la place de -. La relation analytics ci-dessus donne ANALYTICS_URL, ANALYTICS_HOST, etc.
| Type de service | Variables posées | Forme de <NOM>_URL |
|---|---|---|
postgresql | <NOM>_URL, <NOM>_HOST, <NOM>_PORT, <NOM>_NAME, <NOM>_USER, <NOM>_PASSWORD | postgresql://user:password@host:port/name |
mysql | les mêmes | mysql://user:password@host:port/name |
redis | <NOM>_URL, <NOM>_HOST, <NOM>_PORT, <NOM>_PASSWORD | redis://:password@host:port |
s3 | <NOM>_ENDPOINT, <NOM>_REGION, <NOM>_BUCKET | pas d'URL. <NOM>_ENDPOINT vaut https://s3.nl-ams.scw.cloud. |
mercure | <NOM>_URL, <NOM>_PUBLIC_URL, <NOM>_JWT_SECRET | l'adresse du hub, terminée par /.well-known/mercure. <NOM>_PUBLIC_URL a la même valeur. |
mailer | dans une preview : <NOM>_DSN, <NOM>_HOST, <NOM>_PORT. En production : aucune. | <NOM>_DSN vaut smtp://host:1025 dans une preview. |
Le nom de la base et son utilisateur ne sont pas les mêmes en production et dans une preview. Lisez toujours <NOM>_NAME et <NOM>_URL, jamais un nom écrit en dur.
Pour l'instant, ERHA ne crée pas de clés d'accès pour un bucket S3. Posez-les dans Variables : <NOM>_ACCESS_KEY et <NOM>_SECRET_KEY, ou AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY selon ce que lit votre code.
Variables du framework
ERHA pose aussi les variables que le framework de l'application attend, pour la première relation de chaque type de service. Le framework est celui qu'ERHA détecte dans le dépôt (affiché dans l'onglet Déploiement).
| Service | Symfony | Laravel | Spring | Autres (Node, Next.js, Rails, Django...) |
|---|---|---|---|---|
postgresql, mysql | DATABASE_URL, avec ?serverVersion=<version>&charset=utf8 à la fin | DB_CONNECTION (pgsql ou mysql), DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD | SPRING_DATASOURCE_URL (jdbc:postgresql://host:port/name ou jdbc:mysql://...), SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD | DATABASE_URL |
redis | REDIS_URL | REDIS_HOST, REDIS_PORT, REDIS_PASSWORD | SPRING_DATA_REDIS_HOST, SPRING_DATA_REDIS_PORT, SPRING_DATA_REDIS_PASSWORD | REDIS_URL |
mailer (previews) | MAILER_DSN | MAIL_MAILER (smtp), MAIL_HOST, MAIL_PORT | SPRING_MAIL_HOST, SPRING_MAIL_PORT | MAILER_DSN |
mercure | MERCURE_URL, MERCURE_PUBLIC_URL, MERCURE_JWT_SECRET | les mêmes | les mêmes | les mêmes |
s3 | AWS_REGION, AWS_DEFAULT_REGION, AWS_ENDPOINT_URL | les mêmes, plus AWS_BUCKET, AWS_ENDPOINT et AWS_USE_PATH_STYLE_ENDPOINT (true) | AWS_REGION, AWS_DEFAULT_REGION, AWS_ENDPOINT_URL | les mêmes |
Une variable vide n'est pas posée. C'est le cas de AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY tant qu'ERHA ne crée pas les clés S3, et du nom d'utilisateur et du mot de passe de Mailpit, qui n'en demande pas.
Un autre nom de variable
Si votre code attend un autre nom, écrivez-le dans env avec les champs de la relation entre accolades :
env:
REPORTS_DSN: "postgresql://{analytics.user}:{analytics.password}@{analytics.host}:{analytics.port}/{analytics.name}"
| Type de service | Champs disponibles |
|---|---|
postgresql, mysql | url, scheme (postgresql ou mysql), host, port, name, user, password |
redis | url, host, port, password |
s3 | endpoint, region, bucket, access_key, secret_key |
mercure | url, public_url, jwt_secret |
mailer | dsn, host, port, user, password |
access_key et secret_key sont acceptés mais restent vides pour l'instant. Un champ qui n'existe pas pour le type du service, ou une relation que l'application n'a pas, donne l'erreur env_placeholder_unknown.
Secrets et priorités
- Une variable qui contient un mot de passe, une clé, un DSN ou une URL avec identifiants est posée comme secret : elle ne se relit pas dans la console.
- Pour un même nom dans une application,
envl'emporte sur les variables du framework, qui l'emportent sur les variables de la relation. Exemple : avec Symfony, la relationdatabaseposeDATABASE_URL, puis la variable du framework la remplace par la même adresse avecserverVersion. - Une variable qui a la même valeur pour toutes les applications est posée au niveau de l'environnement : toutes les applications de l'environnement la voient.
- Si deux applications posent le même nom avec des valeurs différentes (deux bases, donc deux
DATABASE_URL), ERHA pose la variable au niveau de chaque application, avec sa propre valeur, et sur ses hooks, ses crons et ses workers. Chaque application lit donc sa propre base. - Une variable posée à la main dans Variables n'est jamais remplacée par le fichier : ni au niveau de l'environnement, ni au niveau d'une application. Une variable posée à la main au niveau de l'environnement n'est pas non plus remplacée par une variable du fichier posée au niveau d'une application. Une variable posée à la main au niveau de l'application l'emporte sur celle du fichier.
- Une variable retirée du fichier est retirée de l'environnement et des applications. Une variable posée à la main n'est jamais retirée.
- ERHA pose aussi
SENTRY_ENVIRONMENTdans chaque environnement :productionpour la production principale,tenant-<clé>pour un tenant,mr-<n>pour la preview de la merge request !n. Le SDK Sentry du serveur la lit tout seul, et le suivi des erreurs relie chaque erreur à son environnement (adresse, branche, client du tenant). Une valeur posée à la main l'emporte.
Hooks, crons et workers
Les hooks, les crons et les workers tournent avec l'image de l'application (même Dockerfile, même branche), ses variables et sa taille. La commande est lancée avec /bin/sh -c.
Hooks
| Hook | Quand | Usage |
|---|---|---|
deploy | À chaque déploiement, avant que la nouvelle version de l'application démarre. | Migrations de base de données. |
post_deploy | À chaque déploiement, après la mise en ligne de l'application. | Vider un cache, prévenir un service. |
Un hook dure une heure au plus et n'est pas relancé s'il échoue. Si deploy échoue, le déploiement s'arrête et l'ancienne version reste en ligne. L'échec et ses logs se lisent dans l'onglet Déploiement.
ERHA range les étapes du déploiement dans cet ordre :
- « ERHA bases » : dans une preview, la création des bases de la preview (et le chargement du fichier SQL de départ).
- « ERHA données » : dans une preview, la copie anonymisée de la production.
- « ERHA avant » : les hooks
deploydes applications. - Les applications.
- « ERHA après » : les hooks
post_deploy, et dans une preview la commandepreview.seed.
La construction de l'image est décrite par le Dockerfile : c'est l'équivalent d'un build hook.
Crons
crons:
reports:
spec: "0 4 * * *"
command: php bin/console app:reports:send
- Le nom du cron suit les mêmes règles qu'un nom d'application.
specetcommandsont obligatoires.speca exactement cinq champs séparés par des espaces : minute, heure, jour du mois, mois, jour de la semaine. Chaque champ ne contient que des chiffres, des lettres,*,/,,,-et?. Les raccourcis comme@dailysont refusés.- L'heure est l'heure UTC.
- Une exécution dure une heure au plus et n'est pas relancée si elle échoue.
Workers
workers:
messenger:
command: php bin/console messenger:consume async --time-limit=3600
- Le nom du worker suit les mêmes règles qu'un nom d'application.
commandest obligatoire. - Un worker tourne en continu, sur une instance, sans port ni adresse publique.
- Il prend la taille de son application, réglée dans la console. La clé
sized'un worker est ignorée. - Un worker qui s'arrête est relancé. Une commande qui s'arrête d'elle-même au bout d'un temps (
--time-limit) libère sa mémoire régulièrement.
Routes et domaines
L'adresse ERHA
Chaque application hébergée par ERHA a une adresse sur le domaine d'ERHA, avec son certificat. Elle est construite à partir du libellé du projet, fixé une fois à partir du nom du projet et de la clé de l'espace :
| Environnement | Application principale | Autre application |
|---|---|---|
| Production | boutique-acme.apps.erha.ai | api-boutique-acme.apps.erha.ai |
| Preview de la merge request !12 | mr-12-boutique-acme.apps.erha.ai | mr-12-api-boutique-acme.apps.erha.ai |
Tenant client1 | client1-boutique-acme.apps.erha.ai | client1-api-boutique-acme.apps.erha.ai |
L'application principale est la première application web du projet, sinon la landing, sinon l'API. Le préfixe d'une autre application vient de son nom dans ERHA. Cette adresse sert tant que l'application n'a pas d'autre adresse publique.
Votre domaine
Le domaine d'une application se règle dans Paramètres de l'application, section Déploiement. Il ne sert que la production : les previews restent sur l'adresse ERHA, et un tenant a ses propres domaines.
ERHA déclare le domaine, génère son certificat et affiche dans l'onglet Déploiement l'enregistrement DNS à créer chez votre registrar : un CNAME pour un sous-domaine (app.boutique.fr), un ALIAS pour un domaine nu (boutique.fr), vers la cible indiquée. ERHA relit le DNS toutes les 5 minutes la première heure, toutes les 30 minutes jusqu'à 48 heures, puis toutes les 6 heures jusqu'à 30 jours. « Vérifier maintenant » relance la lecture.
La partie routes
Une route envoie une adresse vers une application du fichier. Elle sert à ajouter des sous-domaines au domaine de production.
routes:
"https://{default}/": front
"https://api.{default}/": api
- Une route commence par
https://, peut avoir un ou plusieurs sous-domaines (minuscules, chiffres, tirets), puis contient{default}. {default}est le domaine de l'application vers laquelle pointehttps://{default}/. Sans cette route, c'est le domaine de la première application du projet qui en a un. Sans aucun domaine réglé, les routes ne déclarent rien.- La valeur est le nom d'une entrée de
applications. - Deux routes vers la même adresse sont refusées (
route_duplicate). - ERHA déclare chaque sous-domaine sur l'application visée, avec son certificat et son enregistrement DNS, comme le domaine principal.
- Une route ne s'applique qu'à la production. Les previews et les tenants ne lisent pas les routes.
- Sans route, chaque application répond sur son adresse ERHA et sur le domaine réglé dans ses paramètres.
Pas encore appliqués :
- les redirections (
redirect) ; - les routes avec un chemin (
https://{default}/api).
Ils sont acceptés à la lecture, mais l'onglet Déploiement les marque en échec et ERHA ne les crée pas. Une route avec un chemin ne retire jamais le domaine de son adresse : avec "https://{default}/": web et "https://{default}/api": api, le domaine reste déclaré sur web, et seule la route /api n'est pas appliquée. Ne les déclarez pas pour l'instant. Faites la redirection dans l'application ou chez votre DNS, et servez une autre application sur un sous-domaine plutôt que sur un chemin.
Ressources et tenants
Ressources
Les ressources se règlent dans la console, jamais dans le fichier.
- Application : Paramètres de l'application > Déploiement > Ressources. La taille d'une instance et le nombre d'instances (de 1 à 5 ; au-delà d'une instance, ERHA ajuste le nombre à la charge). Changer la taille redéploie l'application.
- Services : onglet Déploiement du projet > l'environnement > Ressources des services. La taille, le disque et la haute disponibilité de chaque service.
- Hooks, crons et workers : la taille de leur application.
| Taille | CPU | Mémoire |
|---|---|---|
xs | 0,1 vCPU | 256 Mo |
s | 0,25 vCPU | 512 Mo |
m | 0,5 vCPU | 1 Go |
l | 1 vCPU | 2 Go |
xl | 2 vCPU | 4 Go |
Sans réglage, une application web prend s, une API m, une landing xs, les autres s.
Tenants
Un tenant est une production complète du projet, pour un de vos clients par exemple : toutes les applications et tous les services, sur la branche de production, dans un environnement à part. Il se crée dans l'onglet Déploiement, bouton « Nouveau tenant ».
- Il lit le même fichier que la production : mêmes services, mêmes hooks, mêmes crons, mêmes workers.
- Il a ses propres bases de données, son Redis, son bucket et son hub Mercure. Rien n'est partagé avec la production.
- Il a son adresse ERHA (
<libellé>-<projet>.apps.erha.ai) et ses propres domaines (20 au plus), qui pointent vers l'application principale. Les routes du fichier ne s'y appliquent pas. - Ses ressources se règlent dans sa vue : taille et nombre d'instances par application, taille, disque et haute disponibilité par service.
- Comme en production, une base retirée du fichier est gardée.
- Supprimer un tenant supprime son environnement, ses bases et ses domaines. Les données de ses bases sont perdues.
Previews
Avec « Un environnement de test pour chaque changement » coché dans les paramètres d'une application, ERHA crée une preview pour chaque merge request ouverte vers la branche de travail. Une preview est un environnement à part, avec les applications hébergées par ERHA du même dépôt qui ont une section dans le fichier de la branche de la merge request, et leurs propres services.
- ERHA relit
.erha/config.yamlsur la branche de la merge request : une preview peut essayer une modification du fichier. Si le fichier de la branche a une erreur, la preview n'est pas créée. - Dix previews au plus par projet.
- Chaque application tourne sur une instance.
- L'adresse est
mr-<numéro>-<projet>.apps.erha.ai, jamais votre domaine. - Une preview se redéploie à chaque push sur la merge request. Avec « Déployer les previews quand la CI passe », elle attend que le pipeline réussisse.
- À la fermeture ou à la fusion de la merge request, ERHA supprime la preview, ses bases et ses buckets.
Dans une preview :
- Chaque base est une base à part dans une instance que l'espace garde pour ses previews, une par moteur et par version, avec son propre utilisateur. L'instance est créée à la première preview qui en a besoin.
- Redis, Mercure et les buckets S3 sont propres à la preview et vides au départ.
- Les emails arrivent dans une boîte Mailpit de la preview, lisible depuis l'onglet Déploiement avec l'identifiant et le mot de passe affichés. Ils ne partent jamais. La boîte garde 2000 emails au plus.
Contenu des bases : preview.data
services:
db:
type: postgresql:16
preview:
data: seed
seed: php bin/console doctrine:fixtures:load --no-interaction --append
preview.data | Contenu de la base | Limites |
|---|---|---|
empty (par défaut) | Une base vide. Les migrations du hook deploy créent le schéma. | Aucune. |
seed | Une base vide, puis le fichier SQL déposé dans l'onglet Déploiement (.sql ou .sql.gz), chargé à la création de la base. Puis la commande preview.seed, lancée une fois après la mise en ligne, à l'étape « ERHA après ». | Le fichier et la commande sont facultatifs. Ils ne servent qu'une fois, à la création de la preview. |
anonymized | Une copie de la production au moment où la preview est créée, anonymisée. | PostgreSQL seulement. Demande la lecture du schéma de production (voir plus bas). |
La clé preview.seed est la commande lancée avec l'image de l'application. Elle n'est lue qu'avec data: seed.
Anonymisation
Quand une base PostgreSQL a data: anonymized et que les previews sont activées, ERHA ajoute un job à la production. Chaque nuit (3h17 UTC), il lit la liste des colonnes de la base (jamais les données) et l'envoie à ERHA. Tant que ce schéma n'a pas été lu, la copie attend. « Lire maintenant » relance la lecture.
ERHA propose une règle pour les colonnes qu'il reconnaît par leur nom et leur type : emails, prénoms et noms, téléphones, adresses, IBAN et numéros de carte, adresses IP, mots de passe, jetons et clés, dates de naissance, numéros d'identité. Une colonne qu'aucune règle ne reconnaît est copiée telle quelle.
Les règles se relisent et se changent dans l'onglet Déploiement, section Données des previews, colonne par colonne :
| Règle | Effet |
|---|---|
| Garder | La valeur est copiée telle quelle. |
| Vider | La valeur devient NULL. Une colonne texte obligatoire devient une chaîne vide. Une colonne obligatoire d'un autre type garde sa valeur. |
| Valeur fictive | Une fausse valeur du même genre : email, prénom, nom, téléphone, adresse IP, sinon un texte au hasard. |
| Empreinte | La valeur est remplacée par son empreinte SHA-256. |
| Décaler la date | La date bouge de 30 jours au plus. |
| Exclure les lignes | Pour une table : son schéma est copié, pas ses lignes. |
La copie est faite une fois, par un job de la preview, avec greenmask, à partir de ces règles : aucun modèle ne lit les données. Une colonne ajoutée en production depuis la dernière lecture est copiée telle quelle jusqu'à la lecture suivante.
Codes d'erreur
Chaque erreur de lecture a un chemin dans le fichier (par exemple applications.api.port) et un code. L'onglet Déploiement les affiche. ERHA n'applique pas un fichier qui a une erreur : corrigez-la, poussez, puis cliquez sur « Relire et appliquer ».
| Code | Ce qu'il veut dire | Comment corriger |
|---|---|---|
yaml_invalid | Le fichier n'est pas du YAML valide. La ligne est indiquée. | Corrigez la syntaxe à cette ligne : indentation de deux espaces, guillemets autour des valeurs qui contiennent : ou {. |
root_invalid | À la racine : le fichier n'est pas une suite de clés. Sur root : le dossier contient ... | Commencez le fichier par applications:. Écrivez le dossier depuis la racine du dépôt, sans ... |
unknown_key | Une clé n'existe pas à cet endroit. Les clés possibles sont indiquées. | Retirez la clé ou corrigez son nom. |
required | Une valeur obligatoire manque ou est vide : applications, le type d'un service, le spec ou la command d'un cron, la command d'un worker, le redirect d'une route. | Ajoutez la valeur. |
type_invalid | La valeur n'a pas le bon type : une liste au lieu d'une suite de clés, un objet au lieu d'un texte. | Écrivez une suite de clés (nom: valeur) ou un texte, selon l'endroit. |
name_invalid | Un nom d'application, de service, de relation, de cron ou de worker ne suit pas la règle. | Une lettre minuscule d'abord, puis des minuscules, des chiffres et des tirets, 40 caractères au plus. |
application_not_declared | Aucune entrée de applications n'a le dossier de cette application. | Ajoutez une entrée avec root égal au dossier de l'application, ou corrigez le dossier dans ses paramètres. |
port_invalid | Le port n'est pas un entier de 1 à 65535. | Écrivez un nombre, sans guillemets. |
service_type_unknown | Le type de service n'existe pas. | Utilisez postgresql, mysql, redis, s3, mercure ou mailer. |
service_version_unknown | La version n'est pas proposée pour ce type, ou le type n'a pas de version. | Prenez une version du tableau des services (la plus proche au-dessus de la vôtre), ou retirez la version pour s3, mercure et mailer. |
service_environments_invalid | environments est vide ou nomme un environnement inconnu. | Écrivez production, preview, ou les deux. |
not_for_type | preview est donnée pour un service qui n'est pas une base. | Retirez preview de ce service. |
preview_data_invalid | preview.data n'est pas une valeur possible. | Utilisez empty, seed ou anonymized. |
preview_anonymized_postgresql_only | anonymized est demandé pour une base MySQL. | Utilisez empty ou seed pour MySQL. |
relationship_unknown_service | La relation nomme un service absent de services. | Déclarez le service, ou corrigez son nom dans la relation. |
env_key_invalid | Le nom de la variable n'est pas valide, ou commence par QOVERY_. | Lettres, chiffres et _, sans commencer par un chiffre ni par QOVERY_. |
env_placeholder_unknown | Une valeur entre accolades ne correspond à aucune relation de l'application, ou à aucun champ de ce type de service. | Utilisez {relation.champ} avec une relation de cette application et un champ de la liste. |
cron_spec_invalid | spec n'a pas cinq champs, ou un champ contient un caractère refusé. | Écrivez cinq champs cron, par exemple "0 4 * * *", entre guillemets. |
route_url_invalid | Une route ne commence pas par https://, ne contient pas {default} ou contient un caractère refusé ; ou un redirect ne commence pas par https://. | Écrivez la route sous la forme "https://{default}/" ou "https://sous-domaine.{default}/". |
route_duplicate | Deux routes visent la même adresse. | Gardez une seule route par adresse. |
route_application_unknown | Une route nomme une application absente de applications. | Utilisez le nom d'une entrée de applications. |
variable_generate_invalid | generate n'est pas une valeur possible. | Utilisez secret, laravel_key ou rsa_keypair. |
variable_format_invalid | format n'est pas une valeur possible. | Utilisez hex ou base64. |
variable_bytes_invalid | bytes n'est pas un entier de 16 à 128. | Écrivez un nombre, sans guillemets. |
variable_help_invalid | help ne commence pas par https://. | Écrivez l'adresse complète en https. |
Les codes size_invalid, instances_invalid et disk_invalid ne sont plus donnés : les clés de ressources sont ignorées.
Emails en production
Le service mailer ne crée rien en production. Il sert aux previews, où il donne une boîte Mailpit qui garde les emails.
En production, posez votre serveur SMTP dans Variables (Paramètres de l'application > Variables), en secret :
- Symfony et la plupart des frameworks :
MAILER_DSN, par exemplesmtp://utilisateur:motdepasse@smtp.exemple.fr:587. - Laravel :
MAIL_MAILER,MAIL_HOST,MAIL_PORT,MAIL_USERNAME,MAIL_PASSWORD. - Spring :
SPRING_MAIL_HOST,SPRING_MAIL_PORT,SPRING_MAIL_USERNAME,SPRING_MAIL_PASSWORD. - Si votre code lit
<RELATION>_DSN, posez cette variable.
Faites de même dans chaque tenant qui envoie des emails.
Facturation
L'hébergement est facturé chaque mois sur une ligne « ERHA Cloud » par application : un forfait au prorata des jours hébergés, plus ce qui a tourné (CPU et mémoire réservés multipliés par les instances et le temps, disque, minutes de build). Une preview est comptée sur la ligne de l'application ; un tenant a sa propre ligne et son propre forfait.
Chaque service créé par ERHA Cloud a aussi sa ligne : « ERHA Cloud · <service> · <projet> · <mois> (<n> jours) ». Il est facturé au prorata des jours où il existe chez l'hébergeur, à la taille réglée dans la console ce jour-là. Prix hors taxes, par mois :
| Service | Prix |
|---|---|
| PostgreSQL ou MySQL | S 19 €, M 49 €, L 95 €, XL 239 € |
| Haute disponibilité d'une base | + 50 % du prix de sa taille |
| Disque d'une base | 0,25 € par Go |
| Redis | XS 5 €, S 9 €, M 15 €, L 29 € |
| Bucket S3 | 0,03 € par Go stocké |
| Mercure | 5 € |
| Base des previews de l'espace | 19 € (une base S), les mois où elle existe |
La boîte mail des previews (mailer) n'est pas facturée. Une base ou un bucket retiré du fichier reste chez l'hébergeur et reste facturé jusqu'à sa suppression ; la ligne le dit. Le volume stocké d'un bucket n'est pas encore lu chez l'hébergeur : un bucket est facturé 0 € tant que ce volume n'est pas mesuré. Les services d'une preview ne sont pas facturés à part.
Le détail est dans Facturation. L'onglet Déploiement montre le prix par mois de chaque base et de chaque cache de la production, à côté de sa taille, aux admins de l'espace. L'hébergement ne compte dans aucun plafond : un site hébergé ne s'arrête jamais à cause d'un plafond.