Container
The image, which ships the dump tools already.
View as MarkdownThe image is the shortest path to a working worker, because it already contains the four dump
tools: curl, pg_dump and mysqldump. Nothing to install per source type.
ghcr.io/savedhq/local-workerCheck the architecture first
Which architectures a tag carries is a property of that tag. Check before you pin one.
docker buildx imagetools inspect ghcr.io/savedhq/local-worker:v0.1.0v0.1.0 is linux/arm64 only. On an amd64 host it fails at pull time with
no matching manifest for linux/amd64 in the manifest list entries. Until a tag exists that
lists your architecture, use a source build, which also
covers building this image yourself.
Prepare the host
Two paths matter, and both have to belong to the uid the image runs as.
sudo install -d -m 0755 /etc/saved
sudo install -m 0400 -o 65532 -g 65532 config.yaml /etc/saved/config.yaml
sudo install -d -m 0700 -o 65532 -g 65532 /var/lib/savedThen set the staging directory to the one you just created, in that same config:
local_temp_path: /var/lib/savedRun it
docker run -d --name saved-worker \
--restart unless-stopped \
-v /etc/saved/config.yaml:/config.yaml:ro \
-v /var/lib/saved:/var/lib/saved \
ghcr.io/savedhq/local-worker:v0.1.0docker logs -f saved-workerA healthy start logs the tools it resolved, the hub it dialled, and how many backups the config declares. See what healthy looks like.
Four things the image will not forgive
The config mounts at /config.yaml. The image sets no working directory, so the worker
looks in /. Mount it anywhere else and the worker starts with no backups and fails every run
with ConfigDrift.
It runs as uid 65532, and there is no user database entry to fall back on. The mounted
config must be readable by that uid and the staging path writable by it, which is what the
install -o 65532 lines above are for. A config left as root-owned 0600 is invisible to the
process, and the worker exits with missing required config: api_url, token.
Do not mount a fresh named volume at the staging path. Docker creates a named volume
root-owned 0755, uid 65532 cannot write into it, and the first dump fails on a path the
worker was told it could use. A host directory you chowned yourself is predictable; a named
volume is not.
api_url and token can come from the environment (API_URL, TOKEN), but the backups:
map cannot. Any worker that actually serves a source needs the file. Every other top-level
key has an environment equivalent in upper case, LOCAL_TEMP_PATH and DEBUG among them.
Give the staging path room for the dump and its encrypted copy at the same time, so roughly twice the dump size. It is worth a real volume rather than the container filesystem: a large dump filling a writable layer is a bad way to find that out.
Compose
services:
saved-worker:
image: ghcr.io/savedhq/local-worker:v0.1.0
restart: unless-stopped
volumes:
- /etc/saved/config.yaml:/config.yaml:ro
- /var/lib/saved:/var/lib/savedNo ports: section, ever. The worker dials out and nothing dials in.
Podman
The same image works, with one difference that will bite you. Under rootless Podman, uid
65532 in the container is not uid 65532 on the host: it maps through your subuid range. Do
the ownership change inside that mapping rather than on the host.
podman unshare chown 65532:65532 /etc/saved/config.yaml
podman unshare chown 65532:65532 /var/lib/savedAdd :Z to the volume arguments on a system with SELinux enforcing.
Tags
| Tag | What it is |
|---|---|
vX.Y.Z | A tagged release. Use this |
latest | The tip of main |
sha-<short> | One specific commit on main |
Pin a version. latest is the right choice for a machine you are actively testing against
and the wrong one for the host that holds your production backups. See
Upgrades for moving between them.
Kubernetes
The operator has not shipped. Until it does, run the image the way you
would any other single-replica workload: a Deployment with replicas: 1, the config in a
Secret mounted at /config.yaml, and a volume for the staging path.
replicas: 1, and never a rolling update. Two processes sharing one worker credential
both poll the same queue, and a run's steps hand each other a path to a file on local disk.
Set strategy.type: Recreate so a deploy never briefly runs two.
Why.