Phare

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.yml dans 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éé, /setup renvoie sur /login et n'est plus accessible

Tous les comptes suivants sont créés via :

  • Invitation workspace (recommandé) — voir section Workspaces
  • Inscription publique sur /signup — uniquement si PHARE_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 :

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

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

env
# 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 :

env
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é
env
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
env
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
env
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 :

  1. Aller dans Réglages → Workspace
  2. Cliquer Inviter dans la section Membres
  3. Saisir l'email + choisir le rôle (admin / member / viewer)
  4. Copier le lien d'acceptation et l'envoyer manuellement (le SMTP est désactivé en MVP)
  5. 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 :

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

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

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

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

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

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

config
# 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 :

bash
# 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) :

bash
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 → SettingsWebhooksAdd 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 :

log
# 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) :

yaml
# 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) :

yaml
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 images
  • registry:write — push + pull
  • builds:trigger — lancer des builds via API
  • rollouts:promote — promote / rollback
  • admin:* — 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.

Doc en construction · contributions bienvenues Retour à la landing