- README rewritten end-to-end: stack, quick start, dev workflow, full
/api/v1 endpoint table, error and pagination envelopes, auth
quick-start (browser + bot bearer), configuration table, deployment
notes, backup/restore pointer. Stale "next features" section dropped
now that all eight feat branches are in.
- .env.example now lists every env var the backend reads, with
inline explanations:
- COOKIE_SECURE / COOKIE_DOMAIN / SESSION_TTL_DAYS (auth)
- CORS_ALLOWED_ORIGINS (same-origin by default)
- MAX_REQUEST_BYTES / MAX_FILE_BYTES (upload caps)
- Postgres + storage + log vars carried over.
- docker-compose.yml forwards all of the above into the backend
service with `${VAR:-default}` so an unset value falls back to the
same default the code uses, and any `.env` override flows through
without a compose edit.
- docs/backup.md: step-by-step backup, restore, and smoke-test drill
for both stateful volumes (postgres-data + storage-data), plus a
list of what's deliberately *not* in the backup (e.g., .env).
- playwright.config.ts: pins the e2e dev server to port 5174 with
`--strictPort` so it neither reuses nor silently bumps off
collision with another vite instance on 5173. Drops the flaky
manual-start workflow the earlier branches needed.
- docker-compose syntax (both prod and dev) validates cleanly against
.env.example with no undefined-variable warnings.
No version bump — this is documentation, config, and tooling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.8 KiB
Backup and restore
Mangalord persists state in two places:
- Postgres (
postgres-datavolume) — users, mangas, chapters, pages, bookmarks, sessions, tokens. - Storage (
storage-datavolume) — cover and page image bytes. Keys in the DB reference paths inside this volume; a backup that has the DB without the storage (or vice versa) is incomplete.
Always back both up together, and restore them together.
Backing up
Run while the stack is up. The compose service name is postgres; if you renamed it, substitute below.
# 1. Dump Postgres. `--format=custom` produces a single compressed file that
# pg_restore can stream.
docker compose exec -T postgres pg_dump \
-U "${POSTGRES_USER:-mangalord}" \
-d "${POSTGRES_DB:-mangalord}" \
--format=custom \
> mangalord-$(date -u +%Y%m%dT%H%M%SZ).dump
# 2. Tar the storage volume. The simplest way is to mount the named volume
# into an ephemeral container and tar from there.
docker run --rm \
-v mangalord_storage-data:/source:ro \
-v "$PWD":/backup \
alpine \
tar czf "/backup/mangalord-storage-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" -C /source .
Both files are timestamped so consecutive runs don't overwrite. Copy them off-host (rsync, rclone, your object store of choice) — keeping them on the same machine doesn't help if the disk fails.
The volume's prefix (
mangalord_) comes from the compose project name, which defaults to the directory name. Confirm withdocker volume ls | grep storage-data.
Restoring
The procedure is destructive; do it on a fresh stack or after confirming you really want to overwrite the current state.
# 0. Stop the stack so nothing writes during the restore.
docker compose down
# 1. Wipe the existing volumes and recreate them. (Skip this if restoring
# onto a host that doesn't have prior state.)
docker volume rm mangalord_postgres-data mangalord_storage-data || true
# 2. Bring Postgres up alone so we can restore into it before the backend
# starts and runs migrations against the wrong schema.
docker compose up -d postgres
# Wait for it to become healthy.
until docker compose exec -T postgres pg_isready -U "${POSTGRES_USER:-mangalord}" >/dev/null; do
sleep 1
done
# 3. Restore the dump.
docker compose exec -T postgres pg_restore \
-U "${POSTGRES_USER:-mangalord}" \
-d "${POSTGRES_DB:-mangalord}" \
--clean --if-exists \
< mangalord-YYYYMMDDTHHMMSSZ.dump
# 4. Restore the storage tarball.
docker run --rm \
-v mangalord_storage-data:/target \
-v "$PWD":/backup \
alpine \
sh -c "cd /target && tar xzf /backup/mangalord-storage-YYYYMMDDTHHMMSSZ.tar.gz"
# 5. Start the rest of the stack.
docker compose up -d backend frontend
The backend re-runs migrations on startup. They're idempotent (additive), so restoring a dump from an older schema and then starting a newer backend will forward-migrate the data; the reverse is not supported.
Restore drill
It's worth running this end-to-end at least once before you need it for real. A minimal smoke check after restore:
# Health endpoint should respond.
curl -sf http://localhost:8080/api/v1/health
# An expected manga should be visible.
curl -s 'http://localhost:8080/api/v1/mangas?limit=1' | jq '.items[0].title, .page.total'
# A known cover or page should stream back with the right Content-Type.
curl -sI http://localhost:8080/api/v1/files/<known-key>
If .page.total matches your expectation and a known file is reachable, the restore is complete.
What's not in the backup
- The
.envfile — keep this in your password manager / secrets store; the backup deliberately doesn't include it so a leaked archive doesn't leak credentials. - Application logs — stream them to your usual log sink (journald, Loki, whatever) rather than relying on container logs.
- Anything outside the two named volumes.