Compose Manager's project_name file drifted to 'homefeed' while the data lived in 'homefeed-001_backend-data', so its Up button created a fresh empty volume and a duplicate stack. The volume is now external and named explicitly: any project name resolves to the same data, and a missing volume fails loudly instead of starting a blank site. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
98 lines
5.3 KiB
YAML
98 lines
5.3 KiB
YAML
services:
|
|
# Pull-only variant of docker-compose.yml for a deploy host that only has this one
|
|
# file — no backend/frontend source tree alongside it (e.g. Unraid's Compose Manager,
|
|
# which stores just the compose file under /boot/config/plugins/compose.manager/...).
|
|
# Build and push images elsewhere (`docker compose build && docker compose push`
|
|
# against the main docker-compose.yml, which still has the `build:` blocks), then
|
|
# deploy here with `docker compose -f docker-compose.prod.yml up -d`. Compose Manager
|
|
# (and Compose generally) treats a `build:` key as reason to build over pulling even
|
|
# when the image already exists in the registry — omitting it entirely is what makes
|
|
# this variant pull-only. Everything else — environment, ports, volumes, healthcheck,
|
|
# network_mode — is identical to docker-compose.yml; see that file's comments.
|
|
#
|
|
# FRONTEND_ORIGIN/ORIGIN below are hardcoded to salastil.com rather than reading
|
|
# ${PUBLIC_ORIGIN} from .env like docker-compose.yml does — Unraid's Compose Manager
|
|
# doesn't reliably source a project .env file the way plain `docker compose` does,
|
|
# and a stale fallback here breaks the site outright (SvelteKit's SSR fetch rejects
|
|
# the backend's CORS response the moment these two don't match character-for-
|
|
# character — see root README.md's reverse-proxy section). If you fork this file for
|
|
# a different domain, edit both values below directly.
|
|
backend:
|
|
image: ${REGISTRY:-git.salastil.com/salastil}/homefeed-backend:${IMAGE_TAG:-latest}
|
|
environment:
|
|
PORT: "4000"
|
|
FRONTEND_ORIGIN: https://salastil.com
|
|
DB_PATH: /app/data/homefeed.db
|
|
MEDIA_DIR: /app/data/media
|
|
WIDGETS_INSTALLED_DIR: /app/data/widgets-installed
|
|
NODE_ENV: production
|
|
PRIVATE_ACCESS_PASSWORD: ${PRIVATE_ACCESS_PASSWORD:-}
|
|
# The admin password: whatever you set here IS the key the admin panel asks for, and
|
|
# it is the only thing protecting /api/admin/*. Use a long one (16+ characters; the
|
|
# backend warns at startup if it is shorter). It must be listed HERE to reach the
|
|
# container - a value in this project's .env that isn't forwarded by a line like this
|
|
# is silently ignored, and the backend then invents a random key of its own on every
|
|
# recreate (no writable backend/.env inside the container to remember it).
|
|
# If this stack's .env substitution doesn't reliably apply (as PUBLIC_ORIGIN's didn't
|
|
# - see FRONTEND_ORIGIN/ORIGIN above), set it as a literal value in Compose Manager's
|
|
# own environment editor instead. Avoid $ in the value (compose treats it as a variable).
|
|
ADMIN_API_KEY: ${ADMIN_API_KEY:-}
|
|
volumes:
|
|
- backend-data:/app/data
|
|
ports:
|
|
- "${BACKEND_PORT:-4000}:4000"
|
|
- "${FRONTEND_PORT:-3000}:3000"
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
# Keep server-side rendering on the LAN. VITE_BACKEND_URL is baked in as the public
|
|
# https://salastil.com (the browser needs that), but without this entry the frontend's
|
|
# SSR fetches resolve it to Cloudflare, fail over from IPv6 (this network has none), then
|
|
# hairpin back in through the router - a WAN round trip and extra router connections for
|
|
# every render. Pointing it straight at NPM keeps SNI/TLS and the vhost match intact.
|
|
# Declared on backend because the frontend uses network_mode: service:backend and so
|
|
# shares this container's /etc/hosts (Docker rejects extra_hosts on a container-networked
|
|
# service). Like FRONTEND_ORIGIN/ORIGIN, this is deployment-specific: change the name if
|
|
# you fork this file for another domain. The address is the LAN IP of the host running
|
|
# NPM - set LAN_HOST_IP in .env when that machine's address changes, otherwise every
|
|
# server-rendered page 500s with "fetch failed" until this is updated.
|
|
- "salastil.com:${LAN_HOST_IP:-192.168.0.60}"
|
|
healthcheck:
|
|
test:
|
|
[
|
|
"CMD",
|
|
"node",
|
|
"-e",
|
|
"fetch('http://localhost:4000/api/categories').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
|
|
]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
frontend:
|
|
image: ${REGISTRY:-git.salastil.com/salastil}/homefeed-frontend:${IMAGE_TAG:-latest}
|
|
network_mode: "service:backend"
|
|
environment:
|
|
PORT: "3000"
|
|
HOST: "0.0.0.0"
|
|
ORIGIN: https://salastil.com
|
|
ADMIN_PANEL_ENABLED: ${ADMIN_PANEL_ENABLED:-false}
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
backend-data:
|
|
# Pinned to an existing volume by its exact name instead of letting Compose derive
|
|
# "<project>_backend-data". The derived name changes with the project name, and
|
|
# Compose Manager's project name is whatever is in its project_name file - which once
|
|
# drifted from the folder name, so its Up button silently created a fresh, empty
|
|
# database next to the real one. `external` also means Compose will refuse to start
|
|
# (rather than quietly create a blank volume) if this one is ever missing, and that
|
|
# `down -v` can never delete it. On a brand-new host, create it first:
|
|
# docker volume create homefeed-001_backend-data
|
|
external: true
|
|
name: homefeed-001_backend-data
|