Files
homefeed/docker-compose.prod.yml
SalastilandClaude Sonnet 5.5 02c8efa7e9
Build and push Docker images / backend (push) Canceled after 0s
Build and push Docker images / frontend (push) Canceled after 0s
Pin the production data volume by name so no project name can swap the database
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]>
2026-10-13 18:22:40 -04:00

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