5.2 KiB
5.2 KiB
Deployment Notes
Runtime shape
Recommended production layout:
- run the Go app as a single container;
- bind the app container only to
127.0.0.1:8080; - terminate HTTPS in front of it with Caddy or Nginx;
- persist
/dataso both SQLite and artifacts survive restarts; - expose only the reverse proxy to the internet.
This repository now includes:
Dockerfiledocker-compose.ymldeploy/Caddyfile.exampledeploy/nginx.update-server.conf.exampledeploy/update-server.env.example
Important deployment assumptions:
- set
APP_BASE_URLto the publichttps://...URL; - leave
TRUST_PROXY_HEADERS=trueonly when the app is actually behind a trusted reverse proxy; - do not mount
/datainto a public web root; - do not serve artifacts directly from Caddy or Nginx. Downloads must continue flowing through
/api/v1/releases/{id}/download.
Data layout
Persist the whole /data mount.
Expected contents:
/data/db.sqlite/data/db.sqlite-wal/data/db.sqlite-shm/data/artifacts/...
The app uses SQLite in WAL mode, so cold file copies must include db.sqlite-wal and db.sqlite-shm unless you use an online SQLite backup.
Docker Compose
- Copy
deploy/update-server.env.exampleto a private env file or export the variables in your shell. - Set at least:
APP_BASE_URLADMIN_EMAILADMIN_PASSWORD
- Start the app:
docker compose up -d --build
Security defaults in the provided compose file:
- non-root container user;
- read-only root filesystem;
- writable
/datavolume only; - writable
tmpfsat/tmpfor multipart form handling; no-new-privileges;- all Linux capabilities dropped;
- app port bound only to loopback.
The current implementation uses database-backed random session tokens, so there is no separate SESSION_SECRET environment variable to set.
Reverse proxy
Use one of the example proxy configs and keep the app on loopback.
Caddy:
- example file:
deploy/Caddyfile.example - automatic HTTPS is the simplest option for first rollout.
Nginx:
- example file:
deploy/nginx.update-server.conf.example - remember to provision certificates separately.
Recommended proxy behavior:
- pass
Host,X-Forwarded-For,X-Forwarded-Host, andX-Forwarded-Proto; - keep
client_max_body_sizeor equivalent aligned withMAX_UPLOAD_BYTES; - optionally IP-allow-list
/adminif the admin UI is only for operators; - do not add any direct
/artifactsstatic mapping.
Proxmox notes
Safe first production rollout on Proxmox:
- Use a VM or an unprivileged LXC dedicated to this service.
- Put persistent app data on a host path such as
/srv/update-server/data. - Put backups on a different filesystem or datastore such as
/srv/update-server/backups. - Run the app container on the guest and keep it bound to
127.0.0.1:8080. - Run Caddy or Nginx on the same guest or on a separate reverse-proxy guest.
- Expose only ports
80and443publicly. - Keep
APP_BASE_URLon the final public HTTPS hostname before testing cookies or HSTS.
Operational advice for Proxmox:
- snapshots are useful, but they are not a replacement for app-aware backups;
- if you use LXC, make sure the mounted
/datapath is writable by the container user; - keep the reverse proxy and app logs outside the repo checkout;
- test one admin login, one upload, and one authenticated download after each upgrade.
Backup
Preferred live-backup flow:
- Use SQLite's online backup command so you get a consistent
db.sqlitesnapshot without stopping the app. - Back up
/data/artifactsseparately.
Example with Docker:
backup_dir=/srv/update-server/backups/$(date +%Y%m%d-%H%M%S)
mkdir -p "${backup_dir}"
docker exec update-server sqlite3 /data/db.sqlite ".backup '/tmp/db.sqlite'"
docker cp update-server:/tmp/db.sqlite "${backup_dir}/db.sqlite"
docker exec update-server rm -f /tmp/db.sqlite
tar -C /srv/update-server/data -czf "${backup_dir}/artifacts.tar.gz" artifacts
If you take a cold backup instead:
- stop the container;
- copy
/data/db.sqlite,/data/db.sqlite-wal,/data/db.sqlite-shm, and/data/artifacts; - start the container again.
Restore
Restore procedure:
- stop the app container;
- restore
db.sqlite; - if the restored database came from SQLite
.backup, remove any staledb.sqlite-walanddb.sqlite-shmfiles before starting; - restore
/data/artifacts; - start the container;
- verify
GET /healthz, admin login, and at least one authenticated client download.
Example:
docker compose stop app
cp /srv/update-server/backups/20260415-120000/db.sqlite /srv/update-server/data/db.sqlite
rm -f /srv/update-server/data/db.sqlite-wal /srv/update-server/data/db.sqlite-shm
tar -C /srv/update-server/data -xzf /srv/update-server/backups/20260415-120000/artifacts.tar.gz
docker compose up -d app
Post-deploy checklist
GET /healthzreturns200;- admin login works through HTTPS;
Set-Cookieon/admin/loginincludesSecure,HttpOnly, and the/adminpath;- one release upload succeeds;
- one bearer-authenticated
/api/v1/projectsrequest succeeds; - one
/api/v1/releases/{id}/downloadrequest succeeds through the proxy; - backups complete and can be restored on a staging copy.