Install with Docker Compose¶
About 5 minutes from zero to running.
Before you start¶
You'll need:
- Docker and Docker Compose installed. On Linux,
docker --versionanddocker compose versionshould both work. macOS and Windows users can install Docker Desktop. - A directory containing your music library. Stillwater needs to read it, and (for NFO writeback) write
artist.nfoand image files into it. The directory can be local or a network share that's mounted on the Docker host. -
The host user ID and group ID that own that music directory. Find them with:
Note the
uid=andgid=numbers. You'll plug them into the compose file in a moment.
What gets deployed¶
One container running the Stillwater binary. Two storage paths:
/config(a Docker named volume): SQLite database, generated encryption key, optionalconfig.toml, and any backups Stillwater writes itself./music(a bind mount to your host's music directory): the library Stillwater reads from and writes NFO files into.
Stillwater listens on port 1973 inside the container. The compose file publishes it to 1973 on the host, so you'll reach the web UI at http://localhost:1973 once it's up.
The compose file¶
Save this to docker-compose.yml in a new directory (anywhere; the directory just needs to be writable and somewhere you can run docker compose from).
services:
stillwater:
image: ghcr.io/sydlexius/stillwater:latest
container_name: stillwater
ports:
- "1973:1973"
environment:
- PUID=1000
- PGID=1000
- SW_LOG_LEVEL=info
- SW_LOG_FORMAT=json
# SW_ENCRYPTION_KEY is auto-generated on first run if not set.
# SW_BASE_PATH=/stillwater # Uncomment for subfolder reverse proxy.
volumes:
- stillwater-data:/config
- /path/to/your/music:/music:rw
restart: unless-stopped
volumes:
stillwater-data:
Customize the compose file¶
Two required edits before you start the stack, plus one recommended:
-
Replace
/path/to/your/musicwith the absolute path to your music library on the host. For example,/srv/media/musicor/Users/you/Music. -
Set
PUIDandPGIDto the values fromidyou noted earlier. The container drops privileges to this user so files Stillwater writes to your music directory are owned correctly. Mismatch here is the most common source of permission errors. -
(Optional) Pin a version tag instead of
:latest. For production setups:Available tags are listed on the GitHub releases page.
Other knobs you may not need to touch:
- Port. If
1973is taken on your host, change the left side of the port mapping (for example,"3000:1973"to expose Stillwater on port 3000). SW_LOG_FORMAT.jsonis right for log aggregators; switch totextfor friendlier console output during setup.SW_ENCRYPTION_KEY. Stillwater encrypts third-party API keys at rest. On first run it generates a key into/config/encryption.keyand uses it from then on. You only need to set this env var if you're restoring from a backup that was encrypted with a known key.
Bring it up¶
From the directory containing docker-compose.yml:
You should see Stillwater start, run any pending database migrations, and report listening on :1973. Open http://localhost:1973 in a browser. The first-time setup wizard greets you.
Day-to-day operations¶
# Tail logs
docker compose logs -f stillwater
# Restart the container (no data loss)
docker compose restart stillwater
# Stop the container
docker compose stop
# Open a shell inside the container
docker compose exec stillwater sh
Upgrading¶
If you pinned a version tag, edit the image: line first, then run the same two commands.
Backups¶
The stillwater-data named volume holds everything Stillwater needs to restore: database, encryption key, config. Back it up with a one-shot tar container. Compose prefixes named volumes with the project name (the project directory's basename, by default), so the example below discovers the actual volume name dynamically:
VOL=$(docker volume ls --format '{{.Name}}' | grep '_stillwater-data$')
if [ -z "${VOL}" ]; then
echo "No volume matching '*_stillwater-data' found." >&2
echo "Run 'docker volume ls' and pick the right name, or set COMPOSE_PROJECT_NAME / use 'docker compose -p' to disambiguate." >&2
exit 1
fi
if [ "$(printf '%s\n' "${VOL}" | wc -l | tr -d ' ')" -gt 1 ]; then
printf 'Multiple matching volumes:\n%s\n' "${VOL}" >&2
echo "Set VOL=<exact_name> manually before running this command." >&2
exit 1
fi
docker run --rm \
-v "${VOL}:/data:ro" \
-v "$PWD":/backup \
alpine tar czf "/backup/stillwater-$(date +%F).tar.gz" -C /data .
Scheduled backups are on by default. Tune them via the web UI or these env vars:
- SW_BACKUP_ENABLED=true
- SW_BACKUP_INTERVAL=24 # hours between scheduled backups
- SW_BACKUP_RETENTION=7 # number of recent backups to keep
Backups land in /config/backups inside the volume by default.
Just want docker run?¶
For one-off testing or environments where Compose isn't available:
docker run -d \
--name stillwater \
-p 1973:1973 \
-v stillwater-data:/config \
-v /path/to/your/music:/music:rw \
-e PUID=1000 \
-e PGID=1000 \
--restart unless-stopped \
ghcr.io/sydlexius/stillwater:latest
The Compose form above is recommended for anything beyond a quick try; backups, log rotation, and reverse-proxy setups all assume a Compose-managed stack.
Troubleshooting¶
See Installation > Docker / Compose in the troubleshooting docs.