Operations
Keep the room boring to run.
There are two durable things to protect: one SQLite database and one uploads directory. Everything else can be rebuilt.
Check health and logs
docker compose ps
docker compose logs -f --tail=200 snug caddy
The app’s health check covers SQLite, the media worker, and free space on the data filesystem. A healthy web page does not prove media routing works, so include a real voice call after network changes.
Back up the database and uploads together
SQLite runs in WAL mode. Do not copy the live database file directly. Ask the app to create a consistent snapshot, then copy that snapshot and the uploads directory off the host.
mkdir -p backups
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
docker compose exec -T snug npm run backup:create -- \
--output "/data/backups/snug-$STAMP.sqlite"
docker compose cp \
"snug:/data/backups/snug-$STAMP.sqlite" \
"backups/snug-$STAMP.sqlite"
docker compose cp snug:/data/uploads "backups/uploads-$STAMP"
Store the result somewhere other than this server. Messages can reference uploaded files, so the database and uploads from one backup belong together.
Restore without gambling on the only copy
- Stop the snug app and preserve the failed volume.
- Clone the volume or work on a fresh one.
- Place the chosen snapshot at
/data/snug.sqlite. - Remove stale
-waland-shmcompanions. - Restore the matching uploads tree, then start snug.
Upgrade with a way back
Read the release notes, record the current commit, and take a complete database + uploads backup first. Preserve .env and the named data volumes while the new image builds:
docker compose build --pull snug
docker compose up -d
docker compose ps
docker compose logs --tail=100 snug
Migrations run once at startup and only move forward. A healthy container is the start of verification, not the finish: check public health, send and reload a chat message, upload and retrieve a file, and make a real voice/video call. Keep the pre-upgrade snapshot until those journeys have worked.
Recover an admin from the host
If the founder loses every signed-in device, host access can promote an existing person. This is deliberately a container command, not a web endpoint.
docker compose exec snug npm run admin:list
docker compose exec snug npm run admin:restore -- --nickname "existing name"
The command never creates an identity or prints a sign-in secret. Reload the promoted person’s browser afterward.
Optional local monitoring
The monitoring profile adds Grafana and host/runtime dashboards without publishing the metrics endpoints to the internet.
docker compose --profile monitoring up -d
ssh -L 3000:127.0.0.1:3000 your-server
Open http://localhost:3000 through that tunnel. Metrics cover aggregate counts and system health. They do not include nicknames, message text, IP addresses, or per-person labels.
Fast failure map
| What you see | Where to look |
|---|---|
| Domain does not open | DNS A record, TCP 80/443, then Caddy logs. |
| Chat works, voice does not | UDP/TCP 40000-40400, router forwarding, and SNUG_ANNOUNCED_IPS. |
| Media uses TCP | The UDP range is blocked, mis-forwarded, or announced incorrectly. |
| Container is unhealthy | docker compose logs snug, available disk space, and the media worker. |
| An admin is locked out | Use host admin recovery; do not create a duplicate identity. |
Good operations protect the server. The security guide explains the trust boundary.
Continue to security →