docs(readme): sync quickstart compose block to docker-compose.yml verbatim

The 'copy this wholesale' docker-compose.yml block in the README had
drifted from the real make-of-record file: it hardcoded 5432/8765
(missing the DB_PORT/SERVER_PORT configurability from de8f71b2), lost
the loopback-only db publishing (b03ed9a2), carried its own hand-tailored
db-init service with inline SQL instead of the z_rls_role.sh script
version shipped in 313268a7, and still contained the vestigial
./uploads mount on the db service (dropped in the previous commit).

Replace the whole yaml block with a byte-for-byte copy of
docker-compose.yml so the quickstart can no longer drift — the
documented optional envs, loopback bind, db-init script path, and
env_file wiring all now match what actually ships. Verified by diffing
the fenced block against the file.
This commit is contained in:
John O'Keefe committed 2026-10-29 12:21:07 -04:00
1 parent ab4bddc784
commit cf73eec2e4
1 file changed
+63 -41
+63 -41
View File
@@ -36,6 +36,7 @@ mkdir uploads
```yaml ```yaml
services: services:
# PostgreSQL Database
db: db:
image: postgres:15-alpine image: postgres:15-alpine
container_name: bookhoard_db container_name: bookhoard_db
@@ -43,17 +44,27 @@ services:
POSTGRES_DB: bookhoard POSTGRES_DB: bookhoard
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DBPASS} POSTGRES_PASSWORD: ${DBPASS}
PGPORT: 5432 # PGPORT makes Postgres listen on DB_PORT (kept in sync with the host mapping + app's DATABASE_PORT)
PGPORT: ${DB_PORT:-5432}
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./uploads:/app/uploads - ./database/schema:/docker-entrypoint-initdb.d
ports:
- "127.0.0.1:${DB_PORT:-5432}:${DB_PORT:-5432}"
- "[::1]:${DB_PORT:-5432}:${DB_PORT:-5432}"
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3
start_period: 10s start_period: 10s
env_file:
- .env
# One-shot bootstrap: creates the bookhoard_srv runtime role and
# transfers object ownership to it (RLS prerequisite). Idempotent —
# safe on every startup; the app waits for its success. Passwords
# accept inline values or .env, like the rest of this file.
db-init: db-init:
image: postgres:15-alpine image: postgres:15-alpine
container_name: bookhoard_db_init container_name: bookhoard_db_init
@@ -61,61 +72,65 @@ services:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_DB: bookhoard POSTGRES_DB: bookhoard
PGHOST: db PGHOST: db
PGUSER: postgres DBPASS: ${DBPASS}
PGPASSWORD: ${DBPASS}
DBSRVPASS: ${DBSRVPASS} DBSRVPASS: ${DBSRVPASS}
entrypoint: ["bash", "-c"] volumes:
command: - ./database/schema/z_rls_role.sh:/z_rls_role.sh:ro
- | command: bash /z_rls_role.sh
set -e
: "${DBSRVPASS:?DBSRVPASS required in .env}"
psql -v ON_ERROR_STOP=1 -v role_pass="$DBSRVPASS" <<'EOSQL'
DO $$$$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'bookhoard_srv') THEN
CREATE ROLE bookhoard_srv LOGIN;
END IF;
END $$$$;
ALTER ROLE bookhoard_srv LOGIN PASSWORD :'role_pass';
DO $$$$
DECLARE r RECORD;
BEGIN
FOR r IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' LOOP
EXECUTE format('ALTER TABLE public.%I OWNER TO bookhoard_srv', r.tablename);
END LOOP;
FOR r IN SELECT sequencename FROM pg_sequences WHERE schemaname = 'public' LOOP
EXECUTE format('ALTER SEQUENCE public.%I OWNER TO bookhoard_srv', r.sequencename);
END LOOP;
FOR r IN SELECT p.oid::regprocedure AS fn FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'public' AND p.prokind = 'f' LOOP
EXECUTE format('ALTER FUNCTION %s OWNER TO bookhoard_srv', r.fn);
END LOOP;
END $$$$;
ALTER SCHEMA public OWNER TO bookhoard_srv;
ALTER DATABASE bookhoard OWNER TO bookhoard_srv;
EOSQL
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
restart: "no" restart: "no"
# Bookhoard Application
# In production this image is pulled from the Gitea container registry.
# Override IMAGE_TAG in .env to pin or rollback a specific version (defaults to "latest").
app: app:
image: git.linuxhg.com/bookhoard/bookhoard:${IMAGE_TAG:-latest} image: git.linuxhg.com/bookhoard/bookhoard:${IMAGE_TAG:-latest}
container_name: bookhoard container_name: bookhoard
restart: unless-stopped restart: unless-stopped
environment: environment:
# Database Configuration
# Runtime role = bookhoard_srv (non-superuser OWNER, subject to RLS
# via FORCE). POSTGRES_USER above is the bootstrap account ONLY —
# the app must NEVER connect as it (superusers bypass RLS).
DATABASE_HOST: db DATABASE_HOST: db
DATABASE_PORT: 5432 DATABASE_PORT: ${DB_PORT:-5432}
# Runtime role: bookhoard_srv (created above, confirmed by db-init).
# NEVER connect the app as the bootstrap superuser — it is subject
# to row-level security (per-user library visibility) by design.
DATABASE_USER: bookhoard_srv DATABASE_USER: bookhoard_srv
DATABASE_PASSWORD: ${DBSRVPASS} DATABASE_PASSWORD: ${DBSRVPASS}
DATABASE_NAME: bookhoard
# Application Configuration
JWT_SECRET: ${JWT_SECRET} JWT_SECRET: ${JWT_SECRET}
SERVER_PORT: 8765 SERVER_PORT: ${SERVER_PORT:-8765}
# IMPORTANT: Device sync requires full URL with protocol
# Local: http://localhost:8765
# Local network: http://192.168.1.X:8765
# Domain: https://bookhoard.example.com
BASE_URL: ${BASE_URL:-http://localhost:8765}
# Mark session cookies Secure; set true behind a TLS-terminating reverse proxy (Caddy/nginx/traefik)
COOKIE_SECURE: ${COOKIE_SECURE:-false}
# Rate Limiting Configuration
TEST_MODE: ${TEST_MODE:-false}
RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-true}
REQUESTS_PER_MINUTE: ${REQUESTS_PER_MINUTE:-10}
# Conversion Service Configuration
BOOKHOARD_CONVERSION_CACHE_DIR: ${BOOKHOARD_CONVERSION_CACHE_DIR:-/app/cache/kepub}
BOOKHOARD_CONVERSION_TOOL: ${BOOKHOARD_CONVERSION_TOOL:-/usr/bin/kepubify}
BOOKHOARD_CONVERSION_CACHE_TTL: ${BOOKHOARD_CONVERSION_CACHE_TTL:-24h}
# Library Maintenance
# Days an archived item (file missing from disk for 2+ scans) is kept,
# with its reading history, before library scans purge it for good.
# Set 0 to keep archived items until purged manually on the library admin page.
ARCHIVE_RETENTION_DAYS: ${ARCHIVE_RETENTION_DAYS:-90}
# System timezone (fallback for server-side time operations)
TZ: ${TZ:-UTC}
ports: ports:
- "8765:8765" - "${SERVER_PORT:-8765}:${SERVER_PORT:-8765}"
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
@@ -124,7 +139,14 @@ services:
volumes: volumes:
- ./uploads:/app/uploads - ./uploads:/app/uploads
- bookhoard_conversion_cache:/app/cache/kepub - bookhoard_conversion_cache:/app/cache/kepub
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER_PORT:-8765}/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# Named Volumes
volumes: volumes:
postgres_data: postgres_data:
bookhoard_conversion_cache: bookhoard_conversion_cache: