MariaDB 11 renamed mysqladmin → mariadb-admin; neither the old healthcheck.sh probe nor mysqladmin is reachable from the Docker healthcheck exec context. Switch to the full-path binary that is confirmed present in the container. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
210 lines
9.7 KiB
YAML
210 lines
9.7 KiB
YAML
# Compose stack for the PhotoPrism-backed photo app: mariadb + photoprism +
|
|
# Go sidecar. The SvelteKit web/ frontend runs separately (Vite in dev,
|
|
# static build in prod) and proxies /api/v1/* to photoprism and
|
|
# /api/sidecar/* to the sidecar.
|
|
#
|
|
# podman-compose --env-file .env \
|
|
# -f docker-compose.yml -f docker-compose.podman.yml up -d
|
|
|
|
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}
|
|
MARIADB_ROOT_PASSWORD: ${PP_DB_ROOT_PASSWORD:?set PP_DB_ROOT_PASSWORD in .env}
|
|
# 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", "/usr/bin/mariadb-admin", "ping", "-h", "127.0.0.1", "--silent"]
|
|
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:
|
|
# Loopback only — the SvelteKit web/ app (Vite dev or built bundle)
|
|
# is the user-facing surface; PhotoPrism's own UI stays off the
|
|
# public interface. Vite proxies /api/v1/* here, and the host-mode
|
|
# sidecar reaches PHOTOPRISM_BASE_URL=http://localhost:2342. Admin
|
|
# access to PP's UI is via SSH tunnel only.
|
|
- "127.0.0.1:${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_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}
|
|
# Indexer concurrency. Defaults to NumCPU/2 (= 3 on a 6-core LXC),
|
|
# but each worker forks TF + ffmpeg + libvips so effective load is
|
|
# much higher — a fresh index of 1.2k photos on M0 pushed the LXC
|
|
# load to 50+ and starved sibling containers. Pin to a low value
|
|
# for shared hosts; raise on dedicated machines.
|
|
PHOTOPRISM_WORKERS: ${PP_WORKERS:-2}
|
|
# podman-compose doesn't expand nested ${A:-${B:-…}}, so keep this
|
|
# one-level. Override both PP_WORKERS and PP_INDEX_WORKERS if you
|
|
# want them to differ.
|
|
PHOTOPRISM_INDEX_WORKERS: ${PP_INDEX_WORKERS:-2}
|
|
# 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).
|
|
# Override in .env: PP_BACKUP_DATABASE=true.
|
|
PHOTOPRISM_DISABLE_BACKUPS: "false"
|
|
PHOTOPRISM_BACKUP_DATABASE: ${PP_BACKUP_DATABASE:-true}
|
|
PHOTOPRISM_DISABLE_EXIFTOOL: "false"
|
|
# OIDC — set in .env when the IdP (Authentik) is wired up.
|
|
# Empty values keep OIDC dormant; the username/password login still works.
|
|
# PhotoPrism's CLI flags are --oidc-uri / --oidc-client / --oidc-secret
|
|
# / --oidc-provider, so the env-var names it actually reads are
|
|
# PHOTOPRISM_OIDC_URI / _CLIENT / _SECRET / _PROVIDER (NOT _ISSUER_URL
|
|
# / _CLIENT_ID / _CLIENT_SECRET / _PROVIDER_NAME — those are silently
|
|
# ignored, OIDC stays dormant, and `photoprism show config` reports
|
|
# blank oidc-uri / oidc-client). PHOTOPRISM_OIDC_REDIRECT is a bool
|
|
# (auto-redirect-from-/library/login), not a URL — PhotoPrism builds
|
|
# the callback from PHOTOPRISM_SITE_URL.
|
|
PHOTOPRISM_OIDC_PROVIDER: ${OIDC_PROVIDER_NAME:-${OIDC_PROVIDER:-}}
|
|
PHOTOPRISM_OIDC_URI: ${OIDC_ISSUER_URL:-${OIDC_URI:-}}
|
|
PHOTOPRISM_OIDC_CLIENT: ${OIDC_CLIENT_ID:-${OIDC_CLIENT:-}}
|
|
PHOTOPRISM_OIDC_SECRET: ${OIDC_CLIENT_SECRET:-${OIDC_SECRET:-}}
|
|
PHOTOPRISM_OIDC_SCOPES: ${OIDC_SCOPES:-openid profile email}
|
|
PHOTOPRISM_OIDC_REGISTER: ${OIDC_REGISTER:-true}
|
|
PHOTOPRISM_OIDC_ROLE: ${OIDC_ROLE:-user}
|
|
PHOTOPRISM_OIDC_REDIRECT: ${OIDC_REDIRECT:-false}
|
|
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/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
|
|
# Second DB connection for poking PhotoPrism's own schema (only
|
|
# used by the user-basepath reconciler today). Stays inert if
|
|
# PP_DB_PASSWORD is empty — the reconciler then silently no-ops.
|
|
PP_DB_HOST: mariadb
|
|
PP_DB_PORT: "3306"
|
|
PP_DB_USER: ${PP_DB_USER:-photoprism}
|
|
PP_DB_PASSWORD: ${PP_DB_PASSWORD:-}
|
|
PP_DB_NAME: ${PP_DB_NAME:-photoprism}
|
|
# Declarative username → originals-relative BasePath mapping.
|
|
# Format: comma-separated `user:path` pairs. Sidecar applies it
|
|
# to auth_users on boot and every 60s, and `mkdir -p`s each
|
|
# target subdirectory so PhotoPrism's ACL filter has somewhere to
|
|
# point. Leave empty to disable.
|
|
# USER_BASEPATHS="test:test, alice:family/alice"
|
|
USER_BASEPATHS: ${USER_BASEPATHS:-}
|
|
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/originals:rw,Z"
|
|
networks: [photoprism-network]
|
|
|
|
networks:
|
|
photoprism-network:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
pp_mariadb_data:
|