# ============================================================================= # 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 networks: # Public-facing bridge for this app. The explicit bridge name (instead of # Docker's random br-) 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: