19bdd3faa9
Adds a `dedup` task service under the "tools" profile so it's kept out of `docker compose up` and run on demand: docker compose run --rm dedup # hashes, then rebuild pairs docker compose run --rm dedup -pairs # only rebuild pairs docker compose run --rm dedup -hashes # only backfill hashes It reuses the app image, .env, volumes and networks, overriding only the entrypoint to /app/dedup. Unlike `docker exec` on the live server, this runs in its own container and is self-documented for cron/CI use. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
171 lines
6.3 KiB
YAML
171 lines
6.3 KiB
YAML
# =============================================================================
|
|
# Tanabata File Manager — Docker Compose
|
|
#
|
|
# Quick start:
|
|
# cp .env.example .env # then edit the secrets
|
|
# docker compose up -d --build
|
|
#
|
|
# Database — two supported modes, selected in .env:
|
|
#
|
|
# 1. Bundled Postgres container (default).
|
|
# COMPOSE_PROFILES=with-db
|
|
# DATABASE_URL=postgres://tanabata:password@db:5432/tanabata?sslmode=disable
|
|
#
|
|
# 2. Postgres already running on the host.
|
|
# COMPOSE_PROFILES= # empty → the db container is not started
|
|
# DATABASE_URL=postgres://tanabata:password@host.docker.internal:5432/tanabata?sslmode=disable
|
|
#
|
|
# Requires Docker Compose v2.20+ (for depends_on.required).
|
|
# =============================================================================
|
|
|
|
services:
|
|
app:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: tfm
|
|
restart: unless-stopped
|
|
|
|
# All application config (secrets, DATABASE_URL, tunables) comes from .env.
|
|
env_file: .env
|
|
|
|
# Pin STATIC_DIR to the path baked into the image. .env intentionally leaves
|
|
# it unset; pinning here guarantees in-container SPA serving can't be
|
|
# disabled by an empty value leaking in through env_file.
|
|
environment:
|
|
STATIC_DIR: /app/static
|
|
|
|
# Published on loopback only: a reverse proxy on the host (e.g. nginx) fronts
|
|
# the app and proxies to 127.0.0.1:${APP_PORT}. Binding to 127.0.0.1 keeps the
|
|
# app off the LAN/WAN — a plain "PORT:42776" would publish on 0.0.0.0 and, since
|
|
# Docker's DNAT rules sit ahead of the host firewall, bypass ufw/firewalld. The
|
|
# container always listens on 42776 (Dockerfile default); APP_PORT only changes
|
|
# the host-published port. Drop the 127.0.0.1 prefix if exposing it directly.
|
|
ports:
|
|
- "127.0.0.1:${APP_PORT:-42776}:42776"
|
|
|
|
# Two-tier networking. `web` is the app's public-facing bridge (reached via the
|
|
# published loopback port above; it also provides egress, e.g. to a host
|
|
# Postgres via host.docker.internal). `backend` is the private tier the app
|
|
# uses to reach the bundled DB. The DB sits only on `backend`, so nothing on
|
|
# the host-facing side can reach it.
|
|
networks:
|
|
- web
|
|
- backend
|
|
|
|
# Wait for the bundled DB when the with-db profile is active. When using a
|
|
# host Postgres the db service is disabled, and required:false keeps this
|
|
# dependency from erroring or auto-starting it.
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
required: false
|
|
|
|
# Lets DATABASE_URL reach a Postgres on the host via host.docker.internal
|
|
# (needed on Linux; harmless elsewhere).
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
|
|
# Run as this uid:gid. Relevant when the mounts below are bind-mounted to
|
|
# host folders: set PUID/PGID (in .env) to the owner of those folders so the
|
|
# container can write to them. Defaults to the image's tanabata user
|
|
# (42776), which owns the named volumes.
|
|
user: "${PUID:-42776}:${PGID:-42776}"
|
|
|
|
# Storage for originals, the thumbnail cache, and the import drop folder.
|
|
# Each source defaults to a named volume but can be pointed at a specific
|
|
# host folder via FILES_DIR / THUMBS_DIR / IMPORT_DIR in .env (a path turns
|
|
# the mount into a host bind mount; a bare name stays a named volume).
|
|
volumes:
|
|
- "${FILES_DIR:-app_files}:/data/files"
|
|
- "${THUMBS_DIR:-app_thumbs}:/data/thumbs"
|
|
- "${IMPORT_DIR:-app_import}:/data/import"
|
|
|
|
db:
|
|
image: postgres:14-alpine
|
|
restart: unless-stopped
|
|
|
|
# Only started when COMPOSE_PROFILES includes "with-db". Disable it to point
|
|
# the app at a Postgres running on the host instead.
|
|
profiles: ["with-db"]
|
|
|
|
# Private back-end tier only — never on `web`, never published.
|
|
networks:
|
|
- backend
|
|
|
|
environment:
|
|
POSTGRES_DB: ${POSTGRES_DB:-tanabata}
|
|
POSTGRES_USER: ${POSTGRES_USER:-tanabata}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
|
|
|
|
# Defaults to a named volume; set DB_DIR in .env to a host folder to bind
|
|
# mount it instead. Postgres fixes the folder's ownership itself, so DB_DIR
|
|
# needs no PUID/PGID.
|
|
volumes:
|
|
- "${DB_DIR:-db_data}:/var/lib/postgresql/data"
|
|
|
|
# Uncomment to reach the DB from the host (e.g. with psql) for debugging.
|
|
# ports:
|
|
# - "5432:5432"
|
|
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tanabata} -d ${POSTGRES_DB:-tanabata}"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
# One-shot maintenance task for duplicate detection: computes missing
|
|
# perceptual hashes (images + video) and rebuilds the duplicate-pairs table.
|
|
# It is NOT a daemon — the "tools" profile keeps it out of `docker compose up`;
|
|
# run it on demand, and it exits when done:
|
|
#
|
|
# docker compose run --rm dedup # hashes, then rebuild pairs
|
|
# docker compose run --rm dedup -pairs # only rebuild pairs (after uploads)
|
|
# docker compose run --rm dedup -hashes # only backfill hashes
|
|
#
|
|
# Reuses the app image, .env, volumes and networks; only the entrypoint differs
|
|
# (/app/dedup instead of the server). Connects to the same DB the app uses, so
|
|
# the app's DB (bundled or host) must be reachable when it runs.
|
|
dedup:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
profiles: ["tools"]
|
|
env_file: .env
|
|
networks:
|
|
- web
|
|
- backend
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
required: false
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
user: "${PUID:-42776}:${PGID:-42776}"
|
|
volumes:
|
|
- "${FILES_DIR:-app_files}:/data/files"
|
|
- "${THUMBS_DIR:-app_thumbs}:/data/thumbs"
|
|
entrypoint: ["/app/dedup"]
|
|
restart: "no"
|
|
|
|
networks:
|
|
# Public-facing bridge for this app. The explicit bridge name (instead of
|
|
# Docker's random br-<hash>) makes it identifiable on the host for tcpdump and
|
|
# firewall rules.
|
|
web:
|
|
driver_opts:
|
|
com.docker.network.bridge.name: dk-tanabata
|
|
# Private back-end tier (app ↔ DB). internal:true drops the gateway so the DB
|
|
# has no route off-host. Note: Linux caps interface names at 15 chars, and
|
|
# dk-tanabata-bnd is exactly 15 — a longer app name would need a shorter suffix.
|
|
backend:
|
|
internal: true
|
|
driver_opts:
|
|
com.docker.network.bridge.name: dk-tanabata-bnd
|
|
|
|
volumes:
|
|
app_files:
|
|
app_thumbs:
|
|
app_import:
|
|
db_data:
|