106 lines
4.7 KiB
YAML
106 lines
4.7 KiB
YAML
# The deployed runtime (US08-03): one API, one worker, one library, one volume.
|
|
#
|
|
# The same image (US08-02) runs both roles, so what is composed here is process
|
|
# topology, not a second application. Three invariants shape it:
|
|
#
|
|
# * one writer — `serve` and `worker` each take the library process lock for their
|
|
# role (US07-05), and both containers mount the *same* data volume, which is what
|
|
# makes the lock file visible to both. Scaling `worker` past 1 is refused by that
|
|
# lock rather than by anyone remembering not to;
|
|
# * one local filesystem — the database, its write-ahead log, the thumbnail cache,
|
|
# and the backups live in the `data` volume, and SQLite in WAL mode requires real
|
|
# local-filesystem locking. A network mount (NFS, SMB, or a cloud volume driver)
|
|
# is unsupported for it; that is a corrupted database, not a slow one;
|
|
# * one library path vocabulary — `PHOTO_PIPELINE_LIBRARY_ROOTS` names the path
|
|
# *inside* the container, which is also the bind mount's target below. A root
|
|
# that is not mounted there makes `serve` and `worker` refuse at startup instead
|
|
# of writing into the container's throwaway layer.
|
|
#
|
|
# Configuration and secrets come from the environment only: copy `.env.example` to
|
|
# `.env` and fill it in. Nothing is baked into the image and nothing with a value in
|
|
# it is committed.
|
|
#
|
|
# cp .env.example .env && $EDITOR .env
|
|
# docker compose up -d --build
|
|
#
|
|
# Operating it is operating the same CLI — `docker compose run --rm --no-deps api
|
|
# <command>` — see README, "Composed runtime".
|
|
|
|
name: photo-pipeline
|
|
|
|
x-runtime: &runtime
|
|
image: ${PHOTO_PIPELINE_IMAGE:-photo-pipeline:dev}
|
|
build:
|
|
context: .
|
|
args:
|
|
# Everything the app renames or rewrites has to stay owned by the host user
|
|
# the library already belongs to.
|
|
UID: ${PHOTO_PIPELINE_UID:-1000}
|
|
GID: ${PHOTO_PIPELINE_GID:-1000}
|
|
user: "${PHOTO_PIPELINE_UID:-1000}:${PHOTO_PIPELINE_GID:-1000}"
|
|
env_file:
|
|
- ${PHOTO_PIPELINE_ENV_FILE:-.env}
|
|
volumes:
|
|
- data:/data
|
|
- "${PHOTO_PIPELINE_LIBRARY_HOST_PATH:?set PHOTO_PIPELINE_LIBRARY_HOST_PATH to the photo library on this host}:${PHOTO_PIPELINE_LIBRARY_ROOTS:?set PHOTO_PIPELINE_LIBRARY_ROOTS to the container-side library path}"
|
|
# Jobs check for cancellation between items and leave a resumable record; a
|
|
# too-short grace period turns an orderly stop into a recovery on next start.
|
|
stop_grace_period: 30s
|
|
|
|
x-environment: &environment
|
|
# Set here rather than left to the file: these two are what the composition itself
|
|
# promises, and an `.env` that disagreed would move the database off the volume or
|
|
# the library off its mount.
|
|
PHOTO_PIPELINE_DATA_DIR: /data
|
|
PHOTO_PIPELINE_LIBRARY_ROOTS: ${PHOTO_PIPELINE_LIBRARY_ROOTS}
|
|
|
|
services:
|
|
# Migrations run to completion before either role accepts work, through the same
|
|
# backup-then-migrate path the roles use (US07-05): a pending upgrade is snapshotted
|
|
# first, and a failed one exits non-zero with the backup named — so `api` and
|
|
# `worker` never start, and the previous database is left intact and restorable.
|
|
migrate:
|
|
<<: *runtime
|
|
command: ["migrate"]
|
|
environment: *environment
|
|
restart: "no"
|
|
|
|
api:
|
|
<<: *runtime
|
|
command: ["serve"]
|
|
environment:
|
|
<<: *environment
|
|
# Published to host loopback below. Inside the container the server must bind
|
|
# the container's own interface for that publish to reach it, which is exactly
|
|
# what makes the access secret mandatory (US08-01) — `serve` refuses to start
|
|
# without one. Exposing the port beyond loopback additionally needs
|
|
# PHOTO_PIPELINE_ALLOWED_HOSTS to name the hostname it is reached under.
|
|
PHOTO_PIPELINE_HOST: 0.0.0.0
|
|
PHOTO_PIPELINE_PORT: 8000
|
|
ports:
|
|
# Host side only: PHOTO_PIPELINE_PORT in `.env` moves the *published* port, and
|
|
# the container always serves 8000, which is what the image's health check probes.
|
|
- "${PHOTO_PIPELINE_PUBLISH_ADDRESS:-127.0.0.1}:${PHOTO_PIPELINE_PORT:-8000}:8000"
|
|
depends_on:
|
|
migrate:
|
|
condition: service_completed_successfully
|
|
restart: unless-stopped
|
|
|
|
# One worker. A second one is refused by the library lock in the shared data
|
|
# volume, which is the point: `docker compose up --scale worker=2` fails loudly
|
|
# instead of running two writers against one library.
|
|
worker:
|
|
<<: *runtime
|
|
command: ["worker", "--id", "worker-1"]
|
|
environment: *environment
|
|
depends_on:
|
|
migrate:
|
|
condition: service_completed_successfully
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
# Local driver on purpose: the database, WAL, thumbnail cache, and backups need a
|
|
# real local filesystem. Do not point this at NFS, SMB, or a network volume driver.
|
|
data:
|
|
driver: local
|