# briven — canonical self-host compose (build-from-source).
#
# Post ADR-0002 (docs/ADR/0002-converge-on-doltgres.md): the platform
# runs on TWO database engines, on purpose:
#
#   - control plane  = DoltGres database briven_control (product line: all Doltgres).
#       sign-in, orgs, projects, billing, secrets, auth. drizzle migrations
#       run at api boot. NEVER points at DoltGres.
#   - data plane     = DoltGres (dolthub/doltgresql), env BRIVEN_DATA_PLANE_URL.
#       Postgres-wire, accessed with the `pg` driver, one DATABASE per
#       customer project. Realtime polls DOLT_HASHOF('HEAD') (no LISTEN/NOTIFY).
#
# This file supersedes the two earlier conflicting composes:
#   - the old Dolt-MySQL build (dolthub/dolt-sql-server + BRIVEN_URL mysql) —
#     wrong engine, broke the api. REMOVED.
#   - the all-pgvector build (data plane as a 2nd Postgres DB) — wrong data
#     plane. REPLACED by the real DoltGres service below.
#
# Dokploy clones the repo and runs `docker compose build` against the local
# Dockerfiles — no external registry, no GHCR, no docker.sock. Per
# docs/DOCKER.md §7 / infra/CLAUDE.md: every long-running service caps its
# log volume via the *briven-logging anchor; no watchtower, no docker_sd,
# no registry polling on the host.
#
# Single-machine layout, ~25 concurrent customer projects. Past that, split
# the control plane onto one host and the data plane (doltgres + minio) onto
# another.
#
# Required env (drop a `.env` next to this file — see .env.example):
#
#   BRIVEN_DOMAIN                  e.g. briven.example.com
#   BRIVEN_BETTER_AUTH_SECRET      openssl rand -hex 32
#   BRIVEN_AUDIT_IP_PEPPER         openssl rand -hex 32
#   BRIVEN_ENCRYPTION_KEY          openssl rand -hex 32
#   BRIVEN_RUNTIME_SHARED_SECRET   openssl rand -hex 32
#   BRIVEN_POSTGRES_PASSWORD       control-plane Postgres superuser password
#   BRIVEN_DOLTGRES_PASSWORD       data-plane DoltGres superuser password
#   BRIVEN_MINIO_ROOT_PASSWORD     MinIO root / S3 secret key
#   (optional) BRIVEN_MITTERA_*, BRIVEN_*_CLIENT_ID/SECRET, BRIVEN_POLAR_*,
#              BRIVEN_OLLAMA_*, BRIVEN_MINIO_BUCKET/REGION, BRIVEN_OPEN_SIGNUPS
#
# After first boot:
#   1. Create the first user via the magic-link flow on https://${BRIVEN_DOMAIN}
#   2. Promote to admin in the control plane:
#      docker exec -it briven-postgres psql -U postgres -d briven_control \
#        -c "UPDATE users SET is_admin = true WHERE id = '...';"
#   3. Create your first project via the dashboard (provisions a DoltGres DB).

# Explicit project name so compose resources are deterministically `briven_*`
# (containers already pin container_name: briven-*). Without this, the project
# name defaults to the parent directory ("dokploy"), which would create
# `dokploy_*` volumes — confusingly close to the Dokploy platform's own naming
# and a trap for host-maintenance tooling. Build AND up must share this name.
name: briven

x-logging: &briven-logging
  driver: json-file
  options:
    max-size: '10m'
    max-file: '3'

services:
  # ─── control plane ────────────────────────────────────────────────────
  postgres:
    image: pgvector/pgvector:pg17
    container_name: briven-postgres
    restart: unless-stopped
    logging: *briven-logging
    environment:
      POSTGRES_PASSWORD: ${BRIVEN_POSTGRES_PASSWORD}
      POSTGRES_DB: briven_control
    volumes:
      - postgres_data:/var/lib/postgresql/data
      # Control-plane init only: enables pgvector + pg_trgm on briven_control.
      # No data-plane DB is created here — the data plane is the doltgres
      # service, with a database per project (see ADR-0002).
      - ./postgres-init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres -d briven_control']
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
    networks:
      - briven
    labels:
      - 'briven_logs=true'

  # ─── data plane ───────────────────────────────────────────────────────
  # DoltGres = Postgres-wire, git-for-data. Each customer project is its own
  # DATABASE here, created by the api over the `pg` driver. The default
  # superuser/database is `postgres`/`postgres` (DOLTGRES_* envs override the
  # password).
  #
  # IMAGE IS PINNED BY DIGEST, ON PURPOSE (2026-07-07 maintenance window):
  # `:latest` let a deploy silently swap the database engine under live data
  # (prime suspect in the 2026-07-07 auth.db outage). To upgrade the engine,
  # change the digest here deliberately, in its own reviewed deploy.
  #
  # DATA DIR IS /var/lib/doltgres — NO "ql". The mount below once pointed at
  # /var/lib/doltgresql (typo), so all real data lived in an anonymous volume
  # that a container recreation would orphan. Fixed 2026-07-07 (data migrated
  # into the named volume during the maintenance window). Never change this
  # path without checking `config.yaml` inside the volume.
  doltgres:
    image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
    container_name: briven-doltgres
    restart: unless-stopped
    logging: *briven-logging
    environment:
      DOLTGRES_USER: postgres
      DOLTGRES_PASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
    volumes:
      - doltgres_data:/var/lib/doltgres
      # Dolt-native backups land here (written by the server itself); the
      # dolt-backup service triggers them. See that service for details.
      - doltgres_backups:/backups
    healthcheck:
      # pg_isready ships in the doltgresql image and needs no password.
      test: ['CMD-SHELL', 'pg_isready -h 127.0.0.1 -p 5432 -U postgres']
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - briven
    labels:
      - 'briven_logs=true'

  # ─── data-plane backup ──────────────────────────────────────────────────
  # REAL DoltGres backup (replaces the old placeholder sleep loop).
  #
  # Why NOT pg_dump: tested 2026-06-26 against dolthub/doltgresql:latest
  # (v0.56.6) — `pg_dump` aborts immediately with
  #   "ERROR: SET TRANSACTION is not yet supported"
  # because pg_dump opens a REPEATABLE READ READ ONLY snapshot transaction
  # that DoltGres does not implement. So pg_dump CANNOT back up the data
  # plane. (The control plane is real Postgres and is dumped separately by
  # the host timers in infra/backups/.)
  #
  # What works (verified same day): Dolt's own backup, invoked over the
  # Postgres wire with `SELECT dolt_backup('sync-url', '<file-url>')`. It
  # writes a full, version-history-preserving Dolt archive (manifest +
  # .darc) — re-running it re-syncs in place, so one backup dir per database
  # already contains every commit (time-travel restore, not just a snapshot).
  #
  # This sidecar reuses the doltgresql image (it has `psql`), enumerates the
  # data-plane databases each run, and asks the doltgres SERVER to back each
  # one up into the shared `doltgres_backups` volume.
  #
  # OFF-SITE follow-up: mirroring the `doltgres_backups` volume to MinIO/B2/R2
  # is done today by the host systemd timers in infra/backups/ (mc-based, see
  # briven-backup.sh). Folding an `mc mirror /backups -> minio` step into this
  # service needs an image carrying both psql and mc; tracked as a follow-up.
  dolt-backup:
    # Pinned to the SAME digest as the doltgres service (2026-07-07 window) —
    # the sidecar's psql must always match the server's engine version.
    image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
    container_name: briven-dolt-backup
    restart: unless-stopped
    logging: *briven-logging
    depends_on:
      doltgres:
        condition: service_healthy
    environment:
      PGHOST: doltgres
      PGPORT: '5432'
      PGUSER: postgres
      PGPASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
      BRIVEN_BACKUP_INTERVAL_SECONDS: ${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}
    volumes:
      - doltgres_backups:/backups
      # Read-only view of the server's data dir, ONLY so auth.db (the engine's
      # users/grants file — corrupted once on 2026-07-07, nothing backed it up)
      # can be snapshotted alongside the dolt backups below.
      - doltgres_data:/doltgres-data:ro
    entrypoint: ['/bin/sh', '-c']
    command:
      - |
        set -eu
        echo "dolt-backup: starting (interval=${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}s)"
        while true; do
          ts="$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
          echo "[dolt-backup $$ts] enumerating data-plane databases"
          # All non-template databases on the doltgres server (one per project,
          # plus the default `postgres`). -tA = tuples only, unaligned.
          dbs="$$(psql -tA -d postgres -c \
            "SELECT datname FROM pg_database WHERE datname NOT IN ('template0','template1')")"
          for db in $$dbs; do
            echo "[dolt-backup $$ts] backing up $$db -> file:///backups/$$db"
            if psql -d "$$db" -c \
                 "SELECT dolt_backup('sync-url', 'file:///backups/$$db');" >/dev/null; then
              echo "[dolt-backup $$ts] ok $$db"
            else
              echo "[dolt-backup $$ts] WARN backup failed for $$db"
            fi
          done
          # auth.db snapshot — tiny file, changes only on role/grant edits.
          # Keep the newest 14 copies (2 weeks at the daily default interval).
          if [ -f /doltgres-data/auth.db ]; then
            mkdir -p /backups/auth-db
            if cp /doltgres-data/auth.db "/backups/auth-db/auth.db.$$ts"; then
              echo "[dolt-backup $$ts] ok auth.db snapshot"
              ls -1t /backups/auth-db | tail -n +15 | while read -r old; do
                rm -f "/backups/auth-db/$$old"
              done
            else
              echo "[dolt-backup $$ts] WARN auth.db snapshot failed"
            fi
          else
            echo "[dolt-backup $$ts] WARN auth.db not found in data dir"
          fi
          echo "[dolt-backup $$ts] run complete; sleeping"
          sleep "$${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}"
        done
    networks:
      - briven
    labels:
      - 'briven_logs=true'

  # ─── shared infra ─────────────────────────────────────────────────────
  redis:
    image: redis:7.4-alpine
    container_name: briven-redis
    restart: unless-stopped
    logging: *briven-logging
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    healthcheck:
      test: ['CMD-SHELL', 'redis-cli ping | grep -q PONG']
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - briven
    labels:
      - 'briven_logs=true'

  minio:
    image: minio/minio:latest
    container_name: briven-minio
    restart: unless-stopped
    logging: *briven-logging
    command: server /data --console-address ':9001'
    environment:
      MINIO_ROOT_USER: briven
      MINIO_ROOT_PASSWORD: ${BRIVEN_MINIO_ROOT_PASSWORD}
    volumes:
      - minio_data:/data
    healthcheck:
      test: ['CMD-SHELL', 'curl -fsS http://localhost:9000/minio/health/live || exit 1']
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 20s
    networks:
      - briven
      - dokploy-network
    labels:
      - 'briven_logs=true'
      - 'traefik.enable=true'
      - 'traefik.docker.network=dokploy-network'
      # Public S3 endpoint — browsers PUT/GET with sigv4-presigned URLs the
      # api mints. The api also reaches MinIO internally at http://minio:9000.
      - 'traefik.http.routers.briven-s3.rule=Host(`s3.${BRIVEN_DOMAIN}`)'
      - 'traefik.http.routers.briven-s3.entrypoints=websecure'
      - 'traefik.http.routers.briven-s3.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.briven-s3.service=briven-s3'
      - 'traefik.http.services.briven-s3.loadbalancer.server.port=9000'

  # One-shot bucket creator. `mc mb --ignore-existing` is idempotent, so this
  # runs every deploy and no-ops after the first. restart: 'no' = one-shot, so
  # per infra/CLAUDE.md it does NOT need the logging cap.
  minio-init:
    image: minio/mc:latest
    container_name: briven-minio-init
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
        until /usr/bin/mc alias set minio http://minio:9000 briven ${BRIVEN_MINIO_ROOT_PASSWORD} >/dev/null 2>&1; do
          echo 'waiting for minio...'; sleep 2;
        done;
        /usr/bin/mc mb --ignore-existing minio/${BRIVEN_MINIO_BUCKET:-briven};
        echo 'minio bucket ready: ${BRIVEN_MINIO_BUCKET:-briven}';
      "
    restart: 'no'
    networks:
      - briven

  # ─── application services ─────────────────────────────────────────────
  api:
    build:
      context: ../..
      dockerfile: apps/api/Dockerfile
    container_name: briven-api
    restart: unless-stopped
    logging: *briven-logging
    depends_on:
      postgres:
        condition: service_healthy
      doltgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      BRIVEN_ENV: production
      BRIVEN_API_PORT: '3001'
      BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
      BRIVEN_WEB_ORIGIN: https://${BRIVEN_DOMAIN}
      BRIVEN_TRUSTED_ORIGINS: https://${BRIVEN_DOMAIN},https://api.${BRIVEN_DOMAIN}
      # Control plane — stock Postgres, postgres.js/drizzle.
      BRIVEN_DATABASE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/briven_control?sslmode=disable
      # Data plane — DoltGres, `pg` driver, database-per-project. The api
      # connects to the default `postgres` database and CREATEs per-project
      # databases on this server.
      BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
      BRIVEN_REDIS_URL: redis://redis:6379
      BRIVEN_RUNTIME_URL: http://runtime:3003
      # Without this the api falls back to localhost:3004 and can't reach
      # realtime — surfaces as realtime_stats_failed "Unable to connect".
      BRIVEN_REALTIME_URL: http://realtime:3004
      BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
      BRIVEN_BETTER_AUTH_SECRET: ${BRIVEN_BETTER_AUTH_SECRET}
      BRIVEN_AUDIT_IP_PEPPER: ${BRIVEN_AUDIT_IP_PEPPER}
      BRIVEN_ENCRYPTION_KEY: ${BRIVEN_ENCRYPTION_KEY}
      BRIVEN_MITTERA_API_URL: ${BRIVEN_MITTERA_API_URL:-}
      BRIVEN_MITTERA_API_KEY: ${BRIVEN_MITTERA_API_KEY:-}
      BRIVEN_MITTERA_WEBHOOK_SECRET: ${BRIVEN_MITTERA_WEBHOOK_SECRET:-}
      BRIVEN_GOOGLE_CLIENT_ID: ${BRIVEN_GOOGLE_CLIENT_ID:-}
      BRIVEN_GOOGLE_CLIENT_SECRET: ${BRIVEN_GOOGLE_CLIENT_SECRET:-}
      BRIVEN_GITHUB_CLIENT_ID: ${BRIVEN_GITHUB_CLIENT_ID:-}
      BRIVEN_GITHUB_CLIENT_SECRET: ${BRIVEN_GITHUB_CLIENT_SECRET:-}
      BRIVEN_KONNOS_CLIENT_ID: ${BRIVEN_KONNOS_CLIENT_ID:-}
      BRIVEN_KONNOS_CLIENT_SECRET: ${BRIVEN_KONNOS_CLIENT_SECRET:-}
      BRIVEN_KONNOS_ISSUER: ${BRIVEN_KONNOS_ISSUER:-https://code.konnos.org}
      BRIVEN_DISCORD_CLIENT_ID: ${BRIVEN_DISCORD_CLIENT_ID:-}
      BRIVEN_DISCORD_CLIENT_SECRET: ${BRIVEN_DISCORD_CLIENT_SECRET:-}
      BRIVEN_POLAR_API_BASE: ${BRIVEN_POLAR_API_BASE:-https://api.polar.sh}
      BRIVEN_POLAR_ACCESS_TOKEN: ${BRIVEN_POLAR_ACCESS_TOKEN:-}
      BRIVEN_POLAR_WEBHOOK_SECRET: ${BRIVEN_POLAR_WEBHOOK_SECRET:-}
      BRIVEN_POLAR_PRO_PRODUCT_ID: ${BRIVEN_POLAR_PRO_PRODUCT_ID:-}
      BRIVEN_POLAR_TEAM_PRODUCT_ID: ${BRIVEN_POLAR_TEAM_PRODUCT_ID:-}
      BRIVEN_DOMAIN: ${BRIVEN_DOMAIN}
      BRIVEN_OPEN_SIGNUPS: ${BRIVEN_OPEN_SIGNUPS:-false}
      BRIVEN_OLLAMA_URL: ${BRIVEN_OLLAMA_URL:-}
      BRIVEN_OLLAMA_API_KEY: ${BRIVEN_OLLAMA_API_KEY:-}
      BRIVEN_OLLAMA_MODEL: ${BRIVEN_OLLAMA_MODEL:-qwen2.5-coder:32b}
      BRIVEN_MINIO_ENDPOINT: http://minio:9000
      BRIVEN_MINIO_PUBLIC_ENDPOINT: https://s3.${BRIVEN_DOMAIN}
      BRIVEN_MINIO_ACCESS_KEY: briven
      BRIVEN_MINIO_SECRET_KEY: ${BRIVEN_MINIO_ROOT_PASSWORD}
      BRIVEN_MINIO_BUCKET: ${BRIVEN_MINIO_BUCKET:-briven}
      BRIVEN_MINIO_REGION: ${BRIVEN_MINIO_REGION:-us-east-1}
    healthcheck:
      # /info is documented to never 500 (apps/api/Dockerfile). bun ships in
      # the api image and has a built-in fetch.
      test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3001/info').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
    networks:
      - briven
      - dokploy-network
    labels:
      - 'briven_logs=true'
      - 'traefik.enable=true'
      - 'traefik.docker.network=dokploy-network'
      - 'traefik.http.routers.briven-api.rule=Host(`api.${BRIVEN_DOMAIN}`)'
      - 'traefik.http.routers.briven-api.entrypoints=websecure'
      - 'traefik.http.routers.briven-api.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.briven-api.service=briven-api'
      - 'traefik.http.services.briven-api.loadbalancer.server.port=3001'

  runtime:
    build:
      context: ../..
      dockerfile: apps/runtime/Dockerfile
    container_name: briven-runtime
    restart: unless-stopped
    logging: *briven-logging
    depends_on:
      api:
        condition: service_started
      doltgres:
        condition: service_healthy
    environment:
      BRIVEN_ENV: production
      BRIVEN_RUNTIME_PORT: '3003'
      BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
      BRIVEN_RUNTIME_EXECUTOR: deno
      BRIVEN_RUNTIME_BUNDLE_DIR: /var/lib/briven/bundles
      BRIVEN_API_INTERNAL_URL: http://api:3001
      # Data plane — DoltGres (the old BRIVEN_URL mysql:// was wrong, removed).
      BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
    volumes:
      - runtime_bundles:/var/lib/briven/bundles
    healthcheck:
      # Any HTTP response = process is up (port serving).
      test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3003/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
    networks:
      - briven
    labels:
      - 'briven_logs=true'

  realtime:
    build:
      context: ../..
      dockerfile: apps/realtime/Dockerfile
    container_name: briven-realtime
    restart: unless-stopped
    logging: *briven-logging
    depends_on:
      doltgres:
        condition: service_healthy
    environment:
      BRIVEN_ENV: production
      BRIVEN_REALTIME_PORT: '3004'
      BRIVEN_API_INTERNAL_URL: http://api:3001
      BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
      # Data plane — DoltGres. Realtime polls DOLT_HASHOF('HEAD') per project
      # (no LISTEN/NOTIFY on DoltGres). The old BRIVEN_URL mysql:// was wrong.
      BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
      BRIVEN_REALTIME_POLL_MS: '500'
    healthcheck:
      test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3004/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
    networks:
      - briven
      - dokploy-network
    labels:
      - 'briven_logs=true'
      - 'traefik.enable=true'
      - 'traefik.docker.network=dokploy-network'
      - 'traefik.http.routers.briven-realtime.rule=Host(`realtime.${BRIVEN_DOMAIN}`)'
      - 'traefik.http.routers.briven-realtime.entrypoints=websecure'
      - 'traefik.http.routers.briven-realtime.tls.certresolver=letsencrypt'
      - 'traefik.http.services.briven-realtime.loadbalancer.server.port=3004'

  web:
    build:
      context: ../..
      dockerfile: apps/web/Dockerfile
    container_name: briven-web
    restart: unless-stopped
    logging: *briven-logging
    depends_on:
      api:
        condition: service_started
    environment:
      BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
      BRIVEN_WEB_ORIGIN: https://${BRIVEN_DOMAIN}
      NEXT_PUBLIC_BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
      NEXT_PUBLIC_BRIVEN_HAS_GOOGLE_OAUTH: ${BRIVEN_GOOGLE_CLIENT_ID:+true}
      NEXT_PUBLIC_BRIVEN_HAS_GITHUB_OAUTH: ${BRIVEN_GITHUB_CLIENT_ID:+true}
      NEXT_PUBLIC_BRIVEN_HAS_KONNOS_OAUTH: ${BRIVEN_KONNOS_CLIENT_ID:+true}
      NEXT_PUBLIC_BRIVEN_HAS_DISCORD_OAUTH: ${BRIVEN_DISCORD_CLIENT_ID:+true}
    healthcheck:
      # web runs `next start` on node — use node's built-in fetch.
      test: ['CMD-SHELL', "node -e \"fetch('http://localhost:3000/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
    networks:
      - briven
      - dokploy-network
    labels:
      - 'briven_logs=true'
      - 'traefik.enable=true'
      - 'traefik.docker.network=dokploy-network'
      - 'traefik.http.routers.briven-web.rule=Host(`${BRIVEN_DOMAIN}`) || Host(`app.${BRIVEN_DOMAIN}`)'
      - 'traefik.http.routers.briven-web.entrypoints=websecure'
      - 'traefik.http.routers.briven-web.tls.certresolver=letsencrypt'
      - 'traefik.http.services.briven-web.loadbalancer.server.port=3000'

  docs:
    build:
      context: ../..
      dockerfile: apps/docs/Dockerfile
    container_name: briven-docs
    restart: unless-stopped
    logging: *briven-logging
    depends_on:
      api:
        condition: service_started
    environment:
      # Used by /status + /api/status/incidents.xml to read live incidents.
      BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
    healthcheck:
      test: ['CMD-SHELL', "node -e \"fetch('http://localhost:3002/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
    networks:
      - briven
      - dokploy-network
    labels:
      - 'briven_logs=true'
      - 'traefik.enable=true'
      - 'traefik.docker.network=dokploy-network'
      - 'traefik.http.routers.briven-docs.rule=Host(`docs.${BRIVEN_DOMAIN}`)'
      - 'traefik.http.routers.briven-docs.entrypoints=websecure'
      - 'traefik.http.routers.briven-docs.tls.certresolver=letsencrypt'
      - 'traefik.http.services.briven-docs.loadbalancer.server.port=3002'
      # status.${BRIVEN_DOMAIN} — same docs container; bare `/` rewrites to
      # /status. Other paths pass through (so /api/status/incidents.xml works).
      - 'traefik.http.routers.briven-status.rule=Host(`status.${BRIVEN_DOMAIN}`)'
      - 'traefik.http.routers.briven-status.entrypoints=websecure'
      - 'traefik.http.routers.briven-status.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.briven-status.service=briven-docs'
      - 'traefik.http.routers.briven-status.middlewares=briven-status-rewrite'
      - 'traefik.http.middlewares.briven-status-rewrite.replacepathregex.regex=^/$$'
      - 'traefik.http.middlewares.briven-status-rewrite.replacepathregex.replacement=/status'

volumes:
  postgres_data:
  doltgres_data:
  doltgres_backups:
  redis_data:
  minio_data:
  runtime_bundles:

networks:
  briven:
    driver: bridge
    name: briven
  # Dokploy's ingress network — Traefik watches this for routing + TLS.
  # Routed services (api, realtime, web, docs, s3) attach to it in addition
  # to the internal `briven` network; DBs/redis/runtime stay internal-only.
  dokploy-network:
    external: true
