Ctrl K
ComunidadGuías…

docker-compose.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
name: proxmate

services:
  backend:
    build:
      context: ./backend
    image: proxmate-backend
    restart: unless-stopped
    # Hardening: drop all Linux capabilities, then add back only the few the
    # entrypoint needs. docker-entrypoint.sh starts as root to make the
    # (root-owned) data volume writable by the unprivileged `node` user —
    # `chown -R node:node /data` (CHOWN, plus DAC_OVERRIDE to traverse) — then
    # `su-exec node:node` (SETUID/SETGID) drops to it. Without these the chown
    # fails ("Operation not permitted") and the backend crash-loops. They are
    # used only during the brief root phase; once it re-execs as `node`, the
    # process holds no capabilities. Also block setuid-binary privilege escalation.
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - DAC_OVERRIDE
      - SETUID
      - SETGID
    security_opt:
      - no-new-privileges:true
    environment:
      PORT: "4000"
      NODE_ENV: production
      # SQLite lives on the named volume so data survives container rebuilds.
      DATABASE_URL: file:/data/proxmate.db
      # Where app-database snapshots are written INSIDE the container. Paired
      # with the ${PROXMATE_BACKUP_DIR} mount below; change the host side there,
      # not here. Used as the default for the in-app setting.
      APPDB_BACKUP_DIR: /var/backups/proxmate
      # Used for the CORS allow-list — set to your public frontend origin.
      FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
      # REQUIRED and must be stable across restarts (losing it makes stored
      # secrets unreadable). Generate once with: openssl rand -hex 32
      ENCRYPTION_KEY: ${ENCRYPTION_KEY:?Set ENCRYPTION_KEY in .env (openssl rand -hex 32)}
      # Send Secure cookies (HTTPS only). MUST be "true" in production — passkeys
      # and SameSite cookies require a secure context.
      COOKIE_SECURE: ${COOKIE_SECURE:-false}
      # Number of reverse-proxy hops in front of the API, so the real client IP
      # is used for rate-limiting + audit. 1 when behind a single proxy (Caddy/nginx).
      TRUST_PROXY: ${TRUST_PROXY:-0}
      # Header carrying the real client IP for the audit log. Unset defaults to
      # "cf-connecting-ip" (Cloudflare); behind a plain Caddy/nginx proxy set
      # REAL_IP_HEADER=x-forwarded-for in .env. Deliberately `-` not `:-`: an
      # explicitly-EMPTY value must pass through (it means "disable, use the raw
      # socket IP"); only a fully unset var takes the Cloudflare default. The
      # header is only honoured when TRUST_PROXY >= 1, and x-forwarded-for is
      # resolved through TRUST_PROXY's hop count rather than read verbatim.
      REAL_IP_HEADER: ${REAL_IP_HEADER-cf-connecting-ip}
      # Public base URL of the API — used to build the SSO (OIDC) callback URL
      # (${BACKEND_PUBLIC_URL}/api/auth/sso/callback). Set to your public origin.
      BACKEND_PUBLIC_URL: ${BACKEND_PUBLIC_URL:-http://localhost:4000}
      # WebAuthn (passkey) relying-party. Default to the FRONTEND_URL host; set
      # explicitly in production. RP ID = registrable domain (no scheme/port),
      # ORIGIN = the full https:// origin the browser sees.
      WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-}
      WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-}
      # Optional auth rate-limit tuning (defaults: 20 req / 15 min / IP).
      AUTH_RATE_LIMIT_MAX: ${AUTH_RATE_LIMIT_MAX:-20}
      AUTH_RATE_LIMIT_WINDOW_MS: ${AUTH_RATE_LIMIT_WINDOW_MS:-900000}
      # Optional VM-share throttle (defaults: 30 share requests / hour / signed-in user).
      SHARE_RATE_LIMIT_MAX: ${SHARE_RATE_LIMIT_MAX:-30}
      SHARE_RATE_LIMIT_WINDOW_MS: ${SHARE_RATE_LIMIT_WINDOW_MS:-3600000}
      # Optional guest-agent file tools: pushes per 10 min, install-log reads per
      # minute (both per signed-in user) and the largest pushable file (max 8 MiB).
      GUEST_FILE_RATE_LIMIT_MAX: ${GUEST_FILE_RATE_LIMIT_MAX:-30}
      GUEST_FILE_RATE_LIMIT_WINDOW_MS: ${GUEST_FILE_RATE_LIMIT_WINDOW_MS:-600000}
      GUEST_LOG_RATE_LIMIT_MAX: ${GUEST_LOG_RATE_LIMIT_MAX:-30}
      GUEST_FILE_PUSH_MAX_BYTES: ${GUEST_FILE_PUSH_MAX_BYTES:-1048576}
      # Optional public-token throttle for invite lookups and downloads (defaults:
      # 60 requests / 15 min / IP).
      PUBLIC_TOKEN_RATE_LIMIT_MAX: ${PUBLIC_TOKEN_RATE_LIMIT_MAX:-60}
      PUBLIC_TOKEN_RATE_LIMIT_WINDOW_MS: ${PUBLIC_TOKEN_RATE_LIMIT_WINDOW_MS:-900000}
      # Minutes before a firing alert rule can fire again (default 60, minimum 5).
      ALERT_COOLDOWN_MIN: ${ALERT_COOLDOWN_MIN:-60}
      # Minutes a create/restore/rebuild/backup row must be untouched before the
      # restart reconciler settles it (default 15, minimum 1).
      RECONCILE_STUCK_MINUTES: ${RECONCILE_STUCK_MINUTES:-15}
      # Per-account brute-force lockout (defaults: 10 failed logins → 15 min
      # auto-unlock). Admins are emailed on lockout when SMTP is configured.
      AUTH_LOCKOUT_MAX: ${AUTH_LOCKOUT_MAX:-10}
      AUTH_LOCKOUT_MINUTES: ${AUTH_LOCKOUT_MINUTES:-15}
      # Keep Proxmox TLS verification ON against a PRIVATE CA instead of disabling
      # it: mount the CA PEM (docker-compose.override.yml) and point this at it.
      PROXMOX_CA_CERT_FILE: ${PROXMOX_CA_CERT_FILE:-}
      # Which GitHub repo the in-app "Check for updates" reads releases from.
      UPDATE_REPO: ${UPDATE_REPO:-r0073d-l053r/ProxMate}
      # ── Opt-in one-click self-update ──
      # The admin "Install update" button only works when this is "true" AND the
      # host updater (deploy/update.sh + the systemd path unit) is installed.
      # When enabled, also mount the control dir (docker-compose.override.yml).
      # See DEPLOYMENT.md.
      SELF_UPDATE_ENABLED: ${SELF_UPDATE_ENABLED:-false}
      # /metrics auth: in production the endpoint returns 404 unless this is set;
      # scrapers must send `Authorization: Bearer <token>`.
      METRICS_TOKEN: ${METRICS_TOKEN:-}
      # Opt-in: allow admin-configured webhooks / cloud-image mirrors on private
      # LAN addresses (SSRF guard escape hatch). Off by default.
      ALLOW_PRIVATE_OUTBOUND_URLS: ${ALLOW_PRIVATE_OUTBOUND_URLS:-false}
      # Let ProxMate WRITE cloud-init "extras" snippets on demand instead of an
      # admin pre-placing every combination. SNIPPET_DIR must ALSO be mounted in
      # (docker-compose.override.yml) — a path inside the container, on a shared Proxmox
      # storage's snippets/ dir; SNIPPET_STORAGE is that storage's id.
      SNIPPET_DIR: ${SNIPPET_DIR:-}
      SNIPPET_STORAGE: ${SNIPPET_STORAGE:-}
      # Let tenants download a guest backup through the browser. Like the snippet
      # dir this is a CONTAINER path and must be mounted in (docker-compose.override.yml).
      BACKUP_DOWNLOAD_DIR: ${BACKUP_DOWNLOAD_DIR:-}
      # Cap on a restore-from-upload (GB). Unset uses the built-in default.
      RESTORE_UPLOAD_MAX_GB: ${RESTORE_UPLOAD_MAX_GB:-}
      # Schedule overrides for the background jobs (5-field cron). Unset uses the
      # defaults: guest backups Sundays 03:00, access-expiry sweep hourly at :20.
      MATESTATE_CRON: ${MATESTATE_CRON:-}
      ACCESS_EXPIRY_CRON: ${ACCESS_EXPIRY_CRON:-}
      # Same, for app-database snapshots (default nightly 02:30), the cluster
      # balancer and the cloud-image template refresh. Invalid or unset values
      # fall back to the built-in schedules.
      APPDB_BACKUP_CRON: ${APPDB_BACKUP_CRON:-}
      BALANCER_CRON: ${BALANCER_CRON:-}
      TEMPLATE_REFRESH_CRON: ${TEMPLATE_REFRESH_CRON:-}
      # Weekly usage digest to users who opted in (default Mondays 08:00).
      USAGE_DIGEST_CRON: ${USAGE_DIGEST_CRON:-}
      # Comma-separated optional modules to enable (see backend/src/modules/).
      PROXMATE_MODULES: ${PROXMATE_MODULES:-}
      # Log verbosity (pino level: fatal, error, warn, info, debug, trace).
      LOG_LEVEL: ${LOG_LEVEL:-info}
      # Optional GitHub token for the "Check for updates" call (higher API rate limit).
      GITHUB_TOKEN: ${GITHUB_TOKEN:-}
      # Port code-server listens on inside tenant VMs for the ProxMate IDE.
      IDE_GUEST_PORT: ${IDE_GUEST_PORT:-}
    # Report healthy only once /api/health passes (DB reachable + migrations run),
    # so the frontend can wait for a genuinely-ready backend (see depends_on below).
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:4000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 30s
      timeout: 5s
      start_period: 40s
      retries: 3
    volumes:
      - proxmate-data:/data
      # App-database backups (Settings -> Maintenance). ProxMate runs in a
      # container, so it can only write to paths mounted in from the host —
      # creating a folder on the host alone does nothing. The container-side
      # path is fixed; PROXMATE_BACKUP_DIR in .env chooses where on the HOST the
      # snapshots actually land (default: ./backups next to this file).
      - ${PROXMATE_BACKUP_DIR:-./backups}:/var/backups/proxmate
      # Optional mounts (shared snippets dir, guest-backup downloads, the
      # one-click-update control dir, a private Proxmox CA) do NOT go in this
      # file: an update checks the new release out over it. Put them in an
      # untracked docker-compose.override.yml next to this file, which compose
      # merges in automatically — copy docker-compose.override.example.yml and
      # uncomment what you need. Don't edit this file locally; the updater
      # refuses to run over local changes to it rather than discard them.
    ports:
      # Bind to 127.0.0.1 in production (BIND_ADDR=127.0.0.1) so only the local
      # reverse proxy can reach the API; default 0.0.0.0 for simple/dev use.
      - "${BIND_ADDR:-0.0.0.0}:${BACKEND_PORT:-4000}:4000"

  frontend:
    build:
      context: ./frontend
      args:
        # Baked into the client bundle at build time — must be the API URL the
        # user's browser can reach (e.g. https://proxmate.example.com/api).
        NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:4000/api}
        # Public origin for SEO metadata (OG image / canonical / manifest URLs).
        # Must be the externally reachable URL link-preview bots can fetch.
        NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3000}
    image: proxmate-frontend
    restart: unless-stopped
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    environment:
      # Optional: pin the CSP connect-src to your exact API + WebSocket origin,
      # e.g. "'self' https://proxmate.example.com wss://proxmate.example.com".
      # Blank = a default of ('self' https: wss:) plus the NEXT_PUBLIC_API_URL origin
      # and its ws/wss twin. See frontend/src/lib/csp.ts.
      # In .env this value MUST be wrapped in double quotes, or compose stops
      # reading at the first closing single quote and you get just `self`.
      CSP_CONNECT_SRC: ${CSP_CONNECT_SRC:-}
      # Also passed at runtime for parity with the build arg above. Be aware Next
      # INLINES NEXT_PUBLIC_* at build time, so server code reads the value that
      # was baked into the image, not this one — changing it here without a
      # rebuild has no effect. Kept because it costs nothing and keeps `docker
      # compose config` honest about the intended origin.
      NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3000}
    depends_on:
      backend:
        # Wait for the backend to be healthy (migrated + DB up), not just started,
        # so the UI never comes up against an unmigrated/booting API.
        condition: service_healthy
    ports:
      - "${BIND_ADDR:-0.0.0.0}:${FRONTEND_PORT:-3000}:3000"

volumes:
  proxmate-data: