Files
mule-image/docker-compose.photoprism.yml
dtoro 4362e475a7 build(sidecar): containerize + delete the Node prototype
- New sidecar/Dockerfile: multi-stage golang:1.25-alpine → distroless/
  static, ~12 MB final image, static CGO-free binary.
- Wire pp-sidecar into docker-compose.photoprism.yml so the whole
  stack (mariadb + photoprism + sidecar) starts with one
  `podman-compose up`. Container reaches mariadb + photoprism on the
  internal network; the host gets 127.0.0.1:8000 for Vite's proxy.
- New SIDECAR_LISTEN_ADDR env var (default 127.0.0.1 for the host-mode
  dev loop) so the container can bind 0.0.0.0:8000 and let the port
  mapping reach it. Without this the loopback bind was invisible to
  the host.
- Delete sidecar/legacy/server.mjs — the Node prototype's archival
  window is over; git history is its home now.
- Update sidecar/README with compose-first bringup; keep the host
  `go build` flow as the fast-iteration loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:23:36 +02:00

177 lines
7.7 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PhotoPrism stack — replaces the legacy mule-image backend over the course of
# milestones M0M5 (see /home/dtoro/.claude/plans/i-want-you-to-twinkly-galaxy.md).
#
# This compose file is intentionally separate from docker-compose.yml so the
# legacy mule-image stack keeps running side-by-side until M5 cutover, when
# data is migrated and the legacy backend is retired.
#
# docker compose -f docker-compose.photoprism.yml up -d
#
# M0 scope: mariadb + photoprism only. Library mounted READ-ONLY so initial
# indexing cannot mutate originals while we validate. Backwrite, sidecar
# service, web frontend, and reverse proxy land in later milestones.
services:
mariadb:
# Fully-qualified for podman (which refuses short names by default).
# Docker resolves the same digest.
image: docker.io/library/mariadb:11
container_name: pp-mariadb
restart: unless-stopped
command:
- --innodb-buffer-pool-size=512M
- --transaction-isolation=READ-COMMITTED
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max-connections=512
- --innodb-rollback-on-timeout=OFF
- --innodb-lock-wait-timeout=120
environment:
MARIADB_AUTO_UPGRADE: "1"
MARIADB_INITDB_SKIP_TZINFO: "1"
MARIADB_DATABASE: ${PP_DB_NAME:-photoprism}
MARIADB_USER: ${PP_DB_USER:-photoprism}
MARIADB_PASSWORD: ${PP_DB_PASSWORD:?set PP_DB_PASSWORD in .env.photoprism}
MARIADB_ROOT_PASSWORD: ${PP_DB_ROOT_PASSWORD:?set PP_DB_ROOT_PASSWORD in .env.photoprism}
# Loopback-only host port so the mule-sidecar (running as a host process
# in M4) can reach `mule_sidecar.*` over TCP. Not exposed beyond
# 127.0.0.1; the photoprism container still resolves mariadb by service
# name on the photoprism-network bridge.
ports:
- "127.0.0.1:${PP_DB_PORT:-3306}:3306"
volumes:
- pp_mariadb_data:/var/lib/mysql
# The init script creates the mule_sidecar database + user that the Go
# sidecar service will use in M4. Idempotent; no-op on subsequent boots.
# ":Z" is the SELinux private-relabel flag — needed on Fedora/RHEL hosts,
# silently no-op on Debian/Ubuntu and macOS Docker Desktop.
- ./mariadb/init:/docker-entrypoint-initdb.d:ro,Z
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 12
start_period: 60s
networks: [photoprism-network]
photoprism:
image: docker.io/photoprism/photoprism:latest
container_name: pp-app
restart: unless-stopped
depends_on:
mariadb:
condition: service_healthy
# PhotoPrism's container drops to a non-root user via PHOTOPRISM_UID /
# PHOTOPRISM_GID. Match the host user that owns ${PHOTO_DIRS} so the
# process can read originals (and later write sidecars).
user: "${PP_UID:-1000}:${PP_GID:-1000}"
security_opt:
- seccomp:unconfined
- apparmor:unconfined
ports:
- "${PP_PORT:-2342}:2342"
environment:
PHOTOPRISM_ADMIN_USER: ${PP_ADMIN_USER:-admin}
PHOTOPRISM_ADMIN_PASSWORD: ${PP_ADMIN_PASSWORD:?set PP_ADMIN_PASSWORD in .env.photoprism}
PHOTOPRISM_AUTH_MODE: ${PP_AUTH_MODE:-password}
PHOTOPRISM_SITE_URL: ${PP_SITE_URL:-http://localhost:2342/}
PHOTOPRISM_ORIGINALS_LIMIT: ${PP_ORIGINALS_LIMIT:-50000}
PHOTOPRISM_HTTP_COMPRESSION: gzip
PHOTOPRISM_LOG_LEVEL: ${PP_LOG_LEVEL:-info}
# M0 safety: keep originals read-only. Flip to "false" in M2 when the
# right-sidebar enables metadata edits and we want EXIF backwrite.
PHOTOPRISM_READONLY: ${PP_READONLY:-true}
PHOTOPRISM_EXPERIMENTAL: "false"
PHOTOPRISM_DISABLE_CHOWN: "true"
PHOTOPRISM_DISABLE_WEBDAV: ${PP_DISABLE_WEBDAV:-false}
PHOTOPRISM_DISABLE_SETTINGS: "false"
PHOTOPRISM_DISABLE_TLS: "true"
PHOTOPRISM_DEFAULT_TLS: "false"
# AI/vision pipeline back on — per plan we re-introduce TF labels + faces.
PHOTOPRISM_TENSORFLOW_OFF: "false"
PHOTOPRISM_DETECT_NSFW: "true"
PHOTOPRISM_UPLOAD_NSFW: "true"
# Database
PHOTOPRISM_DATABASE_DRIVER: mysql
PHOTOPRISM_DATABASE_SERVER: mariadb:3306
PHOTOPRISM_DATABASE_NAME: ${PP_DB_NAME:-photoprism}
PHOTOPRISM_DATABASE_USER: ${PP_DB_USER:-photoprism}
PHOTOPRISM_DATABASE_PASSWORD: ${PP_DB_PASSWORD}
# Sidecars next to originals — read by the migrator at M5.
PHOTOPRISM_SIDECAR_PATH: ""
PHOTOPRISM_SIDECAR_YAML: "true"
# EXIF backwrite — disabled in M0 (READONLY blocks writes anyway).
# Re-enable in M2 by overriding in .env.photoprism: PP_BACKUP_DATABASE=true.
PHOTOPRISM_DISABLE_BACKUPS: "false"
PHOTOPRISM_BACKUP_DATABASE: ${PP_BACKUP_DATABASE:-true}
PHOTOPRISM_DISABLE_EXIFTOOL: "false"
# OIDC — set in .env.photoprism when the IdP (Authentik) is wired up.
# Empty values keep OIDC dormant; the username/password login still works.
PHOTOPRISM_OIDC_PROVIDER_NAME: ${OIDC_PROVIDER_NAME:-}
PHOTOPRISM_OIDC_ISSUER_URL: ${OIDC_ISSUER_URL:-}
PHOTOPRISM_OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
PHOTOPRISM_OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
PHOTOPRISM_OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-}
PHOTOPRISM_OIDC_SCOPES: ${OIDC_SCOPES:-openid profile email}
PHOTOPRISM_OIDC_REGISTER: ${OIDC_REGISTER:-true}
PHOTOPRISM_OIDC_ROLE: ${OIDC_ROLE:-user}
working_dir: /photoprism
volumes:
# Existing photo library — mounted read-only in M0; flip to :rw in M2
# when the right-sidebar starts saving edits. ",Z" relabels for SELinux
# on Fedora/RHEL; silent no-op elsewhere.
- "${PHOTO_DIRS:?set PHOTO_DIRS in .env.photoprism}:/photoprism/originals:${PP_ORIGINALS_MODE:-ro},Z"
- "./pp/storage:/photoprism/storage:Z"
- "./pp/import:/photoprism/import:Z"
networks: [photoprism-network]
# mule-sidecar — Go + Gin + GORM service for endpoints PhotoPrism's API
# does not expose (file rename, folder mutations, heap convert, duplicate
# scan, per-photo marks). Same wire contract as the M3 Node prototype;
# the SvelteKit dev server proxies /api/sidecar/* here.
sidecar:
build:
context: ./sidecar
container_name: pp-sidecar
restart: unless-stopped
depends_on:
mariadb:
condition: service_healthy
photoprism:
condition: service_started
# Match PhotoPrism's UID/GID so renames/folder mutations preserve the
# ownership the indexer expects on the bind-mounted originals.
user: "${PP_UID:-1000}:${PP_GID:-1000}"
ports:
# Loopback only — Vite (host) proxies /api/sidecar/* to this port.
# Behind a reverse proxy in production; never published beyond the
# host.
- "127.0.0.1:${SIDECAR_PORT:-8000}:8000"
environment:
ORIGINALS_ROOT: /photoprism/originals
PHOTOPRISM_BASE_URL: http://photoprism:2342
# Bind on all interfaces inside the container so the host-side
# 127.0.0.1:8000 port mapping can reach the listener. The Go
# binary defaults to 127.0.0.1 for the host-mode dev loop.
SIDECAR_LISTEN_ADDR: 0.0.0.0
SIDECAR_PORT: "8000"
SIDECAR_DB_HOST: mariadb
SIDECAR_DB_PORT: "3306"
SIDECAR_DB_USER: sidecar
# Rotate before any non-local deployment. Provisioned by
# mariadb/init/01-sidecar.sql on first boot of the mariadb volume.
SIDECAR_DB_PASSWORD: ${SIDECAR_DB_PASSWORD:-replace-at-m4-bringup}
SIDECAR_DB_NAME: mule_sidecar
volumes:
# Sidecar mutates originals (rename, folder mutations, heap
# convert) — always rw regardless of PhotoPrism's mount mode.
- "${PHOTO_DIRS:?set PHOTO_DIRS in .env.photoprism}:/photoprism/originals:rw,Z"
networks: [photoprism-network]
networks:
photoprism-network:
driver: bridge
volumes:
pp_mariadb_data: