ERHADocumentationOuvrir la console

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, un Dockerfile dans 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
typeLa 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é.
extensionsPHP seulement. Les extensions à ajouter : celles que demande composer.lock. opcache, intl, zip, pdo_pgsql et pdo_mysql sont déjà dans l'image.
buildLes 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).
webPHP seulement. document_root (public) et front_controller (index.php).
startLa 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 pose PORT avec la valeur de la clé port du 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 .env versionné 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 variables avec generate : 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 lancez composer install --no-dev dans cette image. Installez chaque extension ext-* demandée par composer.lock (composer check-platform-reqs --lock --no-dev doit 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.yamlDans la console
Les applications du dépôt, leur dossier, leur Dockerfile et leur portLe choix d'héberger par ERHA, la branche de production, le domaine
Les services (type et version), les relationsLa 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 workersLes secrets et les autres variables (section Variables)
Les routes vers des sous-domainesLes 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éTypePar défautRègles
roottexte/Dossier de l'application dans le dépôt. .. est refusé.
dockerfiletexteDockerfileChemin du Dockerfile, depuis root. Sans effet quand type est déclaré.
type, extensions, build, web, startaucunL'image construite par ERHA, sans Dockerfile. Voir « Sans Dockerfile : l'image construite par ERHA ».
portnombre8080Entier de 1 à 65535. Port sur lequel l'application écoute. ERHA pose PORT avec cette valeur.
relationshipsliste de paires relation: serviceaucuneChaque service nommé doit exister dans services. Voir « Relations et variables d'environnement ».
envliste de paires NOM: valeuraucuneVariables fixes. Voir plus bas.
variablesliste de variablesaucuneLes variables que le code lit sans valeur dans le fichier. Voir « Variables attendues ».
hooksdeploy, post_deployaucunUne commande par hook. Voir « Hooks ».
cronsliste de cronsaucunChaque cron a spec et command. Voir « Crons ».
workersliste de workersaucunChaque 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 par QOVERY_ est refusé.
  • La valeur est un texte, un nombre ou un booléen. true devient "true", false devient "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 ».
  • env ne 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éValeursCe qu'elle fait
generatesecret, laravel_key, rsa_keypairERHA 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.
formathex, base64 (par défaut)Pour generate: secret.
bytes16 à 128, 32 par défautPour generate: secret : le nombre d'octets aléatoires.
public, passphraseun nom de variablePour 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, helpun nom, une adresse https://La variable vient d'un service tiers : la console dit où la créer.
secrettrue (par défaut), falsefalse pour une valeur publique, lisible dans la console.
sharedfalse (par défaut), truetrue : une seule valeur pour tous les environnements du projet. Sinon chaque environnement (production, preview, tenant) a la sienne.
descriptiontexteMontré à côté de la variable.

Ce que fait ERHA :

  • Une variable generate déclarée dans le fichier, ou repérée dans un .env sans 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 sans generate et 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
typeObligatoire. 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.
previewBases 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).
environmentsLes 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

TypeVersionsPar défautEn productionDans une previewDans un tenant
postgresql14, 15, 1616Une 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.
mysql88Une 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.
redis77Un Redis en conteneur, privé, sans sauvegarde.Un Redis propre à la preview, vide au départ.Un Redis à lui.
s3aucuneaucuneUn bucket privé compatible S3 (Scaleway, région nl-ams).Un bucket propre à la preview, vide, supprimé avec elle.Un bucket à lui.
mercureaucuneaucuneUn 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.
maileraucuneaucuneRien 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 :

TypeTaillesPar défautAutres réglages
postgresql, mysqls, m, l, xl (instances Scaleway DB-DEV-S, DB-DEV-M, DB-DEV-L, DB-GP-XS)sDisque de 5 à 1000 Go, 5 Go par défaut. Haute disponibilité, coupée par défaut.
redisxs (128 Mo), s (256 Mo), m (512 Mo), l (1 Go)sAucun.
s3, mercure, maileraucuneAucun.

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 serviceVariables poséesForme de <NOM>_URL
postgresql<NOM>_URL, <NOM>_HOST, <NOM>_PORT, <NOM>_NAME, <NOM>_USER, <NOM>_PASSWORDpostgresql://user:password@host:port/name
mysqlles mêmesmysql://user:password@host:port/name
redis<NOM>_URL, <NOM>_HOST, <NOM>_PORT, <NOM>_PASSWORDredis://:password@host:port
s3<NOM>_ENDPOINT, <NOM>_REGION, <NOM>_BUCKETpas d'URL. <NOM>_ENDPOINT vaut https://s3.nl-ams.scw.cloud.
mercure<NOM>_URL, <NOM>_PUBLIC_URL, <NOM>_JWT_SECRETl'adresse du hub, terminée par /.well-known/mercure. <NOM>_PUBLIC_URL a la même valeur.
mailerdans 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).

ServiceSymfonyLaravelSpringAutres (Node, Next.js, Rails, Django...)
postgresql, mysqlDATABASE_URL, avec ?serverVersion=<version>&charset=utf8 à la finDB_CONNECTION (pgsql ou mysql), DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORDSPRING_DATASOURCE_URL (jdbc:postgresql://host:port/name ou jdbc:mysql://...), SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORDDATABASE_URL
redisREDIS_URLREDIS_HOST, REDIS_PORT, REDIS_PASSWORDSPRING_DATA_REDIS_HOST, SPRING_DATA_REDIS_PORT, SPRING_DATA_REDIS_PASSWORDREDIS_URL
mailer (previews)MAILER_DSNMAIL_MAILER (smtp), MAIL_HOST, MAIL_PORTSPRING_MAIL_HOST, SPRING_MAIL_PORTMAILER_DSN
mercureMERCURE_URL, MERCURE_PUBLIC_URL, MERCURE_JWT_SECRETles mêmesles mêmesles mêmes
s3AWS_REGION, AWS_DEFAULT_REGION, AWS_ENDPOINT_URLles mêmes, plus AWS_BUCKET, AWS_ENDPOINT et AWS_USE_PATH_STYLE_ENDPOINT (true)AWS_REGION, AWS_DEFAULT_REGION, AWS_ENDPOINT_URLles 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 serviceChamps disponibles
postgresql, mysqlurl, scheme (postgresql ou mysql), host, port, name, user, password
redisurl, host, port, password
s3endpoint, region, bucket, access_key, secret_key
mercureurl, public_url, jwt_secret
mailerdsn, 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, env l'emporte sur les variables du framework, qui l'emportent sur les variables de la relation. Exemple : avec Symfony, la relation database pose DATABASE_URL, puis la variable du framework la remplace par la même adresse avec serverVersion.
  • 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_ENVIRONMENT dans chaque environnement : production pour 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

HookQuandUsage
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 :

  1. « ERHA bases » : dans une preview, la création des bases de la preview (et le chargement du fichier SQL de départ).
  2. « ERHA données » : dans une preview, la copie anonymisée de la production.
  3. « ERHA avant » : les hooks deploy des applications.
  4. Les applications.
  5. « ERHA après » : les hooks post_deploy, et dans une preview la commande preview.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.
  • spec et command sont obligatoires.
  • spec a 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 @daily sont 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. command est 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é size d'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 :

EnvironnementApplication principaleAutre application
Productionboutique-acme.apps.erha.aiapi-boutique-acme.apps.erha.ai
Preview de la merge request !12mr-12-boutique-acme.apps.erha.aimr-12-api-boutique-acme.apps.erha.ai
Tenant client1client1-boutique-acme.apps.erha.aiclient1-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 pointe https://{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.
TailleCPUMémoire
xs0,1 vCPU256 Mo
s0,25 vCPU512 Mo
m0,5 vCPU1 Go
l1 vCPU2 Go
xl2 vCPU4 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.yaml sur 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.dataContenu de la baseLimites
empty (par défaut)Une base vide. Les migrations du hook deploy créent le schéma.Aucune.
seedUne 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.
anonymizedUne 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ègleEffet
GarderLa valeur est copiée telle quelle.
ViderLa valeur devient NULL. Une colonne texte obligatoire devient une chaîne vide. Une colonne obligatoire d'un autre type garde sa valeur.
Valeur fictiveUne fausse valeur du même genre : email, prénom, nom, téléphone, adresse IP, sinon un texte au hasard.
EmpreinteLa valeur est remplacée par son empreinte SHA-256.
Décaler la dateLa date bouge de 30 jours au plus.
Exclure les lignesPour 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 ».

CodeCe qu'il veut direComment corriger
yaml_invalidLe 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_keyUne clé n'existe pas à cet endroit. Les clés possibles sont indiquées.Retirez la clé ou corrigez son nom.
requiredUne 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_invalidLa 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_invalidUn 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_declaredAucune 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_invalidLe port n'est pas un entier de 1 à 65535.Écrivez un nombre, sans guillemets.
service_type_unknownLe type de service n'existe pas.Utilisez postgresql, mysql, redis, s3, mercure ou mailer.
service_version_unknownLa 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_invalidenvironments est vide ou nomme un environnement inconnu.Écrivez production, preview, ou les deux.
not_for_typepreview est donnée pour un service qui n'est pas une base.Retirez preview de ce service.
preview_data_invalidpreview.data n'est pas une valeur possible.Utilisez empty, seed ou anonymized.
preview_anonymized_postgresql_onlyanonymized est demandé pour une base MySQL.Utilisez empty ou seed pour MySQL.
relationship_unknown_serviceLa relation nomme un service absent de services.Déclarez le service, ou corrigez son nom dans la relation.
env_key_invalidLe 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_unknownUne 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_invalidspec 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_invalidUne 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_duplicateDeux routes visent la même adresse.Gardez une seule route par adresse.
route_application_unknownUne route nomme une application absente de applications.Utilisez le nom d'une entrée de applications.
variable_generate_invalidgenerate n'est pas une valeur possible.Utilisez secret, laravel_key ou rsa_keypair.
variable_format_invalidformat n'est pas une valeur possible.Utilisez hex ou base64.
variable_bytes_invalidbytes n'est pas un entier de 16 à 128.Écrivez un nombre, sans guillemets.
variable_help_invalidhelp 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 exemple smtp://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 :

ServicePrix
PostgreSQL ou MySQLS 19 €, M 49 €, L 95 €, XL 239 €
Haute disponibilité d'une base+ 50 % du prix de sa taille
Disque d'une base0,25 € par Go
RedisXS 5 €, S 9 €, M 15 €, L 29 €
Bucket S30,03 € par Go stocké
Mercure5 €
Base des previews de l'espace19 € (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.

Une question sur ERHA ?Dans la console, Ask ERHA répond sur le produit, ses pages et la facturation.Ouvrir la console