Documentation
Comprendre et opérer Phare.
Setup Docker Compose, authentification locale + OIDC, push d'images,
pipelines .phare.yml, triggers webhooks et rollouts en vagues.
Qu'est-ce que Phare ?
Phare est une plateforme self-hosted qui regroupe en un seul produit :
- Registry OCI compatible Docker (push/pull standard)
- CI/CD par fichier
.phare.ymldans tes repos - Triggers Git via webhooks GitHub / GitLab / Gitea
- Rollouts progressifs en vagues (canary, blue-green, gates)
- Multi-tenant avec workspaces, invitations et rôles
Architecture : 4 services (depot Java, forge Go, trigger Go, console Next.js) + Postgres + MinIO + Redis.
Premier lancement
Quand tu démarres Phare pour la première fois, aucun compte n'existe en base. La console détecte ce cas et te redirige automatiquement vers
/setup.
Sur cette page tu crées le compte admin initial :
- Email + nom + mot de passe (12 caractères minimum)
- Le compte devient automatiquement owner du workspace par défaut
-
Une fois créé,
/setuprenvoie sur/loginet n'est plus accessible
Tous les comptes suivants sont créés via :
- Invitation workspace (recommandé) — voir section Workspaces
- Inscription publique sur
/signup— uniquement siPHARE_AUTH_REGISTRATION_ENABLED=true - OIDC — provisioning automatique au premier login si
PHARE_AUTH_OIDC_ENABLED=true
Setup avec Docker Compose
Aucun build à faire — tire directement les images publiques depuis
le registry de Phare lui-même. Crée un fichier docker-compose.yml :
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: phare
POSTGRES_PASSWORD: change-me
POSTGRES_DB: phare
volumes: [pg-data:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U phare"]
interval: 5s
redis:
image: redis:7-alpine
volumes: [redis-data:/data]
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: phareminio
MINIO_ROOT_PASSWORD: change-me-too
volumes: [minio-data:/data]
depot:
image: depot.phare.local/phare/depot:latest
depends_on: [postgres, redis, minio]
ports: ["8080:8080"]
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/phare
SPRING_DATASOURCE_USERNAME: phare
SPRING_DATASOURCE_PASSWORD: change-me
PHARE_S3_ENDPOINT: http://minio:9000
PHARE_S3_ACCESS_KEY: phareminio
PHARE_S3_SECRET_KEY: change-me-too
PHARE_AUTH_LOCAL_ENABLED: "true"
PHARE_AUTH_OIDC_ENABLED: "false"
PHARE_AUTH_REGISTRATION_ENABLED: "false"
forge:
image: depot.phare.local/phare/forge:latest
depends_on: [depot]
ports: ["8081:8081"]
environment:
PHARE_DEPOT_URL: http://depot:8080
trigger:
image: depot.phare.local/phare/trigger:latest
depends_on: [depot, forge]
ports: ["8082:8082"]
environment:
PHARE_DEPOT_URL: http://depot:8080
PHARE_FORGE_URL: http://forge:8081
console:
image: depot.phare.local/phare/console:latest
depends_on: [depot]
ports: ["3000:3000"]
environment:
PHARE_DEPOT_URL: http://depot:8080
PHARE_FORGE_URL: http://forge:8081
PHARE_COOKIE_SECURE: "false" # mettre "true" derrière un proxy HTTPS
volumes:
pg-data:
redis-data:
minio-data: Démarrage :
docker compose pull
docker compose up -d
docker compose logs -f
Une fois les services healthy :
-
Console :
http://localhost:3000→ redirigé vers/setup -
Registry OCI :
http://localhost:8080/v2/ -
API forge :
http://localhost:8081/api/v1/ -
Webhooks trigger :
http://localhost:8082/webhooks/<provider>
Production
Utilise un reverse proxy TLS (Traefik / Caddy / nginx), passe PHARE_COOKIE_SECURE=true, pin les tags d'images sur une version (:0.x.y)
plutôt que :latest, et change tous les change-me.
Authentification — local & OIDC
Phare supporte deux modes d'auth, indépendants ou cumulables, configurés dans le compose :
# depot env
PHARE_AUTH_LOCAL_ENABLED: "true" # email + password local
PHARE_AUTH_OIDC_ENABLED: "false" # OIDC (Authentik, Keycloak, etc.)
PHARE_AUTH_REGISTRATION_ENABLED: "false" # /signup public ouvert ?
PHARE_OIDC_ISSUER: "https://auth.example.com/application/o/phare/"
PHARE_OIDC_CLIENT_ID: "phare-console"
PHARE_OIDC_CLIENT_SECRET: "..." - Local : email + password Argon2id, sessions cookie httpOnly de 7 jours
- OIDC : flow standard auth code + PKCE, mapping email → user, provisioning auto au premier login
- Registration : si
true, n'importe qui peut s'inscrire ; sinon seuls les invitations workspace permettent de créer un compte
Lockout anti-bruteforce : 5 tentatives ratées en
15 min → compte bloqué. Reset auto après expiry, ou manuel via la
table auth_local.
SSO — Gitea, Keycloak, Authentik
Phare supporte 3 providers OIDC en parallèle. Tous les boutons
"Continuer avec…" apparaissent sur /login. Le mapping se fait par email : à la première
connexion, le user est auto-provisionné.
Activation globale :
PHARE_AUTH_OIDC_ENABLED=true
PHARE_AUTH_OIDC_PUBLIC_BASE_URL=https://depot.phare.dinawo.fr
PHARE_COOKIE_DOMAIN=.phare.dinawo.fr # cookie partagé console + depot Gitea
Gitea peut servir de provider OIDC depuis 1.17. Crée une OAuth2
application dans Site Administration → Applications :
- Application Name
- Phare
- Redirect URI
-
https://depot.phare.dinawo.fr/login/oauth2/code/gitea - Confidential Client
- ✅ coché
PHARE_OIDC_GITEA_ISSUER=https://git.dinawo.fr
PHARE_OIDC_GITEA_CLIENT_ID=<client_id Gitea>
PHARE_OIDC_GITEA_CLIENT_SECRET=<client_secret Gitea>
# (optionnel) PHARE_OIDC_GITEA_DISPLAY_NAME="Gitea Dinawo" Keycloak
Crée un client dans ton realm avec Client Authentication=ON et Standard Flow=ON :
- Client ID
- phare
- Client authentication
- On
- Authentication flow
- Standard flow
- Valid redirect URIs
-
https://depot.phare.dinawo.fr/login/oauth2/code/keycloak
PHARE_OIDC_KEYCLOAK_ISSUER=https://keycloak.dinawo.fr/realms/phare
PHARE_OIDC_KEYCLOAK_CLIENT_ID=phare
PHARE_OIDC_KEYCLOAK_CLIENT_SECRET=<credentials → secret> Authentik
Crée un Provider OAuth2/OpenID + une Application dans Authentik :
- Client type
- Confidential
- Redirect URIs
-
https://depot.phare.dinawo.fr/login/oauth2/code/authentik - Signing Key
- n'importe quel cert RS256
- Scopes
- openid, profile, email
PHARE_OIDC_AUTHENTIK_ISSUER=https://authentik.dinawo.fr/application/o/phare/
PHARE_OIDC_AUTHENTIK_CLIENT_ID=<from Authentik provider>
PHARE_OIDC_AUTHENTIK_CLIENT_SECRET=<from Authentik provider> Premier login OIDC = first-run
Si la base est vide (pas encore de /setup fait), le
premier user qui se connecte via OIDC devient automatiquement owner du workspace par défaut. Tous les suivants doivent être invités
via Réglages → Workspace.
Cookie partagé entre sous-domaines
Le flow OIDC pose le cookie phare_session sur le domaine parent (PHARE_COOKIE_DOMAIN=.phare.dinawo.fr) pour qu'il soit partagé entre console et depot. Sans ça,
l'utilisateur serait reconnecté côté depot mais pas côté console.
Workspaces & invitations
Chaque utilisateur appartient à un ou plusieurs workspaces. Toutes les ressources (repositories, builds, rollouts, tokens) sont scopées au workspace actif via Postgres RLS.
Switcher : en haut de la sidebar, choisis le workspace ou crée-en un nouveau.
Inviter un membre :
- Aller dans Réglages → Workspace
- Cliquer Inviter dans la section Membres
- Saisir l'email + choisir le rôle (admin / member / viewer)
- Copier le lien d'acceptation et l'envoyer manuellement (le SMTP est désactivé en MVP)
- L'invité ouvre le lien : preview workspace + accept (ou login/signup si pas de compte)
Rôles
- owner — créateur, accès total + transfert/suppression
- admin — gérer membres, settings, tokens
- member — push/pull images, lancer builds
- viewer — lecture seule
Registry OCI : push & pull
Phare implémente la spec OCI Distribution v1.1, compatible avec docker, buildah,
skopeo, crane.
1. Créer un token API dans Réglages → Tokens avec
le scope registry:write.
2. Login depuis ton poste :
docker login depot.phare.local -u <ton-email> -p <token>
# en CI
echo "$PHARE_TOKEN" | docker login depot.phare.local -u $PHARE_USER --password-stdin 3. Push une image :
docker tag mon-app:latest depot.phare.local/<workspace-slug>/mon-app:v1
docker push depot.phare.local/<workspace-slug>/mon-app:v1 4. Pull :
docker pull depot.phare.local/<workspace-slug>/mon-app:v1 L'image apparaît immédiatement dans la liste Images, scopée à ton workspace actif.
Builds — fichier .phare.yml
Place un fichier .phare.yml à la
racine de ton repo Git :
version: 1
pipeline:
- name: test
image: node:22-alpine
commands: [npm ci, npm run test -- --run]
- name: lint
image: node:22-alpine
commands: [npm run lint]
- name: build
dockerfile: Dockerfile
target: prod
platforms: [linux/amd64, linux/arm64]
tags: ["{branch}-{sha:short}", "latest"]
depends_on: [test, lint]
rollouts:
- branch: main
policy: progressive-10
- branch: rc
policy: direct-full
requires_protection: true Lancer manuellement un build depuis la console : Builds → Nouveau build, ou via API :
curl -X POST http://localhost:8081/api/v1/builds \
-H "Content-Type: application/json" \
-d '{
"image": "acme/api",
"branch": "main",
"sha": "deadbeef",
"triggered_by": "manual",
"triggered_via": "manual",
"commit_message": "test build"
}'
# → {"build_id":"...","build_number":42,"status":"queued"} Variables disponibles dans tags et
commands :
{branch}— branche Git-
{sha},{sha:short}— commit SHA {ts}— timestamp Unix{semver}— extrait du tag Git si présent
Triggers Git — webhooks
Le service phare-trigger reçoit les webhooks de GitHub
/ GitLab / Gitea et déclenche les builds automatiquement selon des
branch policies.
1. Créer un trigger côté Phare :
curl -X POST http://localhost:8082/api/v1/triggers \
-H "Authorization: Bearer $PHARE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "acme/api production",
"provider": "github",
"repo_full_name": "acme/api",
"image": "acme/api",
"secret": "auto",
"rules": [
{ "branch": "main", "policy": "progressive-10" },
{ "branch": "rc", "policy": "direct-full", "requires_protection": true },
{ "branch": "feat/*", "build_only": true }
]
}'
# → {"trigger_id":"...","webhook_url":"http://localhost:8082/webhooks/github/<id>","secret":"shhh-..."} 2. Configurer le webhook côté Git :
# GitHub : Settings → Webhooks → Add webhook
Payload URL: http://trigger.phare.local/webhooks/github/<trigger_id>
Content type: application/json
Secret: <secret retourné par l'étape 1>
Events: push, pull_request, release
# GitLab : Settings → Integrations → Webhooks → idem
# Gitea : Settings → Webhooks → Add webhook → Gitea 3. Vérifier la réception : tu pousses un commit, le trigger reçoit le webhook, vérifie la signature HMAC, matche la branch policy et POST sur l'API forge :
# Voir les builds déclenchés
curl http://localhost:8080/api/v1/pipeline-runs?limit=10 Les branch policies définissent ce qui se passe par branche : build-only (tests/lint sans push), build-and-push (avec tags auto), ou build-push-rollout (chaîne complète vers une policy de rollout).
Sécurité webhooks
Le secret HMAC est obligatoire. Phare rejette tout payload sans
signature valide (X-Hub-Signature-256 pour GitHub, X-Gitlab-Token pour GitLab). Un secret par trigger, jamais réutilisé.
Gitea — intégration pas à pas
Gitea est self-hosted comme Phare — la config webhook se fait en
quelques clics. Si Gitea et Phare sont sur le même réseau Docker,
utilise le nom de service interne (http://trigger:8082) ; sinon le hostname public via Traefik.
1. Créer un trigger côté Phare (UI à venir, pour l'instant API) :
curl -X POST http://localhost:8082/api/v1/triggers \
-H "Authorization: Bearer $PHARE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "gitea/myorg/myapp",
"provider": "gitea",
"repo_full_name": "myorg/myapp",
"image": "myorg/myapp",
"secret": "auto",
"rules": [
{ "branch": "main", "policy": "progressive-10" },
{ "branch": "rc", "policy": "direct-full", "requires_protection": true },
{ "branch": "feat/*", "build_only": true }
]
}'
# → réponse contient webhook_url + secret à copier 2. Côté Gitea, ouvre le repo → Settings → Webhooks → Add Webhook → choisis
Gitea :
- Target URL
-
https://trigger.phare.dinawo.fr/webhooks/gitea/<trigger_id> - HTTP Method
POST- POST Content Type
application/json- Secret
-
<secret retourné étape 1> - Trigger On
- Custom : Push, Pull Request, Release
- Branch filter
-
*(Phare filtre via les rules du trigger) - Active
- ✅ coché
3. Tester : Gitea propose un bouton Test Delivery en bas de la page webhook. Tu dois voir un 200 et le payload reçu côté
Phare :
# logs phare-trigger
docker compose logs -f trigger | grep gitea
# webhook reçu, signature OK, dispatch vers forge
[trigger] gitea push event repo=myorg/myapp branch=main sha=abc1234
[trigger] matched rule branch=main → policy=progressive-10
[forge] build queued id=... number=42 4. Pousser un commit sur main → build
automatique → image push sur depot → rollout démarre.
Mirror local (réseau Docker partagé)
Si Gitea et Phare tournent dans la même stack Docker, mets-les sur
le même network Docker (traefik_proxy par exemple) et
tu peux pointer le webhook directement sur http://phare-trigger:8082/webhooks/gitea/... — pas besoin de TLS interne.
Comparaison avec Drone CI
Si tu viens de Drone, voici les correspondances. Phare s'inspire du modèle pipeline-as-code de Drone mais ajoute le registry, les rollouts en vagues et le multi-tenant.
| Drone | Phare | Notes |
|---|---|---|
| .drone.yml | .phare.yml | syntaxe similaire, schema simplifié |
| kind: pipeline | version: 1 | implicite, un seul pipeline par fichier |
| steps[].image | pipeline[].image | identique |
| steps[].commands | pipeline[].commands | identique |
| depends_on | depends_on | identique, DAG des steps |
| when: | via branch policies | filtrage côté trigger, pas côté yml |
| plugins/docker | step type: build (BuildKit) | intégré, multi-arch natif |
| Registry séparé (DockerHub, Harbor…) | Registry intégré (depot) | push gratuit vers le même Phare |
| Pas de rollout | rollouts: | vagues progressives + gates |
| DRONE_RUNNER_* | phare-forge (1 binaire Go) | scaling horizontal supporté |
Migration .drone.yml → .phare.yml (exemple typique Drone) :
# AVANT — .drone.yml
kind: pipeline
type: docker
name: default
steps:
- name: test
image: node:22-alpine
commands:
- npm ci
- npm test
- name: build
image: plugins/docker
settings:
registry: registry.example.com
repo: registry.example.com/myorg/myapp
tags: [latest, "${DRONE_COMMIT_SHA:0:7}"]
username: { from_secret: docker_user }
password: { from_secret: docker_token }
depends_on: [test]
# APRÈS — .phare.yml
version: 1
pipeline:
- name: test
image: node:22-alpine
commands: [npm ci, npm test]
- name: build
dockerfile: Dockerfile
tags: ["latest", "{sha:short}"]
depends_on: [test]
# registry implicite = ce Phare
# auth implicite = workspace + token du trigger
rollouts:
- branch: main
policy: progressive-10 Plus de gestion de secrets registry, plus de plugin docker à configurer — tu pousses sur le Phare qui t'a déclenché.
Rollouts en vagues
Un rollout déploie une nouvelle version d'une image en pourcentages progressifs (waves), avec gates métriques optionnelles.
Policies par défaut (modifiables par workspace) :
progressive-10:
- { percent: 10, wait: 5min, gate: error_rate < 1% }
- { percent: 50, wait: 15min, gate: p99 < 800ms }
- { percent: 100, wait: 0 }
direct-full:
- { percent: 100, wait: 0 } # bascule immédiate
blue-green:
- { percent: 100, wait: 0, mode: shadow }
- { percent: 100, wait: 0, mode: switch } # cutover après promote manuel Promouvoir / annuler depuis la page Rollouts :
- Promote avance à la wave suivante
- Pause bloque l'avancée auto
- Rollback repasse 100% sur la version précédente
L'intégration runtime (Kubernetes, Docker Swarm, Nomad) est
enfichable via phare-trigger + plugin de déploiement.
Tokens API
Pour les CI/CD, docker login, et l'API REST
programmatique.
Réglages → Tokens → Nouveau token.
Scopes :
registry:read— pull imagesregistry:write— push + pullbuilds:trigger— lancer des builds via APIrollouts:promote— promote / rollbackadmin:*— accès admin complet
Sécurité
Le token clair est affiché une seule fois à la création. Stocke-le dans un gestionnaire de secrets (1Password, Vault, GitHub Secrets…). Si perdu, révoque-le et crée-en un nouveau.
Audit log
Toutes les actions admin (push, pull, rollouts, tokens, invitations, changements de settings) sont loguées en append-only et partitionnées par mois.
Voir page Audit dans la console. Export CSV à venir.
Rétention par défaut : audit_log_admin indéfini, audit_log_pulls 90j. Configurable via PHARE_AUDIT_RETENTION_DAYS.