Install with Docker Compose¶
One Docker host runs Postgres, the SnatchArr API (which serves the web UI) and one snatch
worker. The bundle lives in deploy/homelab/: compose.yaml and env.example. For
Kubernetes use the Helm chart instead, see Deployment.
You need Docker Engine with the Compose plugin (docker compose version) and openssl to
generate the secrets.
Install¶
- Copy
deploy/homelab/compose.yamlanddeploy/homelab/env.exampleinto an empty directory on the host, for example/opt/snatcharr. Take both files from the release tag you install, so the image pins match that release. - Create
.envfrom the example and fill in every secret, each one generated withopenssl rand -hex 32:
Compose refuses to start while a required secret is empty and names the missing one.
3. Set how the UI is reached. SNATCHARR_BIND=127.0.0.1 publishes it on this host only;
use 0.0.0.0 to reach it from the LAN. Set SNATCHARR_PUBLIC_URL to the address users
open. On plain HTTP set SNATCHARR_SESSION_SECURE=false, since the browser drops secure
cookies there; behind an HTTPS reverse proxy keep it true.
4. Start the stack and wait until every service with a health check reports healthy:
- Open
SNATCHARR_PUBLIC_URL. The first visit lands on the setup screen: create the admin account and enterSNATCHARR_SETUP_SECRETfrom.env. Then add your *arr instances.
All three containers run with a read-only root file system, no Linux capabilities and
no-new-privileges. Only the UI port is published; the worker reaches the API over the
compose network on gRPC port 9090.
Health checks¶
Postgres reports its health through pg_isready, and the API starts only once Postgres is
healthy. The API and worker images are distroless: they carry no shell and no curl, so a
health check has to be a command of the binary itself. Both binaries answer a healthcheck
subcommand (/snatcharr-api healthcheck asks /readyz, /snatch-worker healthcheck asks
/livez; exit 0 = healthy). The images pinned in compose.yaml (0.1.9) predate that
subcommand, so the bundle gets API and worker health checks with the first release that ships
it; Renovate moves the pins to that release.
make e2e-compose-smoke starts this bundle with images built from the checkout, adds both
health checks, and checks that every service turns healthy, that /setup serves the UI and
that /api/v1/auth/setup/status reports a fresh install. CI runs it on every pull request.
Upgrade¶
Read the release notes first, then take a backup (below). Replace compose.yaml with the one
from the new release tag, compare env.example for new variables, and restart:
The API applies its database migrations on start. Do not skip the backup: migrations only move forward, and a downgrade needs the backup taken before the upgrade.
A new Postgres major does not start on a data volume of the previous major. Move the database across as below.
Postgres major upgrade¶
This bundle ships Postgres 18 and mounts its volume at /var/lib/postgresql. An install that
runs Postgres 17, or any earlier major, keeps its data at /var/lib/postgresql/data. The
official image refuses to start 18 on that volume ("in 18+, these Docker images are configured
to store database data in a format which is compatible with pg_ctlcluster"), so nothing
starts empty by accident. The same steps move a later install from 18 to 19; replace 17 with
the major you leave.
- With the old
compose.yamlstill in place, stop the API and worker and dump the database:
docker compose stop api worker
docker compose exec -T postgres pg_dump -U snatcharr --format=custom snatcharr > snatcharr.dump
docker compose down
- Put the new
compose.yamlin place, then move the old data directory aside inside the volume. It stays there until you remove it, so you can go back:
docker compose run --rm --no-deps --entrypoint sh postgres -c "cd /var/lib/postgresql &&
mkdir data-pg17 &&
find . -mindepth 1 -maxdepth 1 ! -name data-pg17 -exec mv {} data-pg17/ ';'"
- Start the new Postgres on the emptied volume, restore the dump and start the rest:
docker compose up -d --wait postgres
docker compose exec -T postgres pg_restore -U snatcharr --no-owner --exit-on-error \
--dbname=snatcharr < snatcharr.dump
docker compose up -d --wait
- Once SnatchArr runs and shows your data, remove the old data directory:
To go back before step 4, put the old compose.yaml back, start it on a fresh volume and
restore snatcharr.dump as in Backup and restore.
make e2e-compose-pg-upgrade runs these steps on an install created on the previous major
(the image api/internal/store/storetest pins as PreviousMajorImage). It compares every
table of the database before and after, checks that setup stays complete and that the admin
logs in with the old password. It also runs twice with a step skipped, once without the move
in step 2 and once without the restore in step 3, and must refuse both. CI runs it on every
pull request.
Backup and restore¶
Back up two things together: the database and .env. Without SNATCHARR_CRYPTO_KEY the *arr
API keys stored in the database cannot be decrypted.
To restore, stop the API and worker so nothing writes, restore into the running Postgres, and start them again:
docker compose stop api worker
docker compose exec -T postgres pg_restore -U snatcharr --clean --if-exists --no-owner \
--exit-on-error --dbname=snatcharr < snatcharr.dump
docker compose up -d --wait
Remove¶
docker compose down stops the stack and keeps the data volume. docker compose down
--volumes also deletes the database.