---
title: "Install"
description: "One binary, or a container, on the host that can reach your sources."
url: "https://saved.sh/docs/workers/install"
---

The worker is a single static binary with no runtime dependencies of its own. Put it on a
host that can reach your sources, give it a `config.yaml`, and run it.

Install it **after** you have provisioned the worker, because the config it needs contains
the key that provisioning prints once. See [Registration](/docs/workers/registration).

<Callout type="warn" title="Pre-1.0">
  The current release is [`v0.1.0`](https://github.com/savedhq/local-worker/releases), and it
  is marked pre-release. It was tagged before the release pipeline was fixed, so that tag
  carries no binaries and its image is `linux/arm64` only. Until the next tag,
  [build from source](/docs/workers/install/source).
</Callout>

## Pick a path [#pick-a-path]

Every path ends the same way: a binary and a `config.yaml` in a directory, and something that
keeps the process running.

<Cards>
  <Card href="/docs/workers/install/linux" title="Linux" description="Binary under systemd. The usual answer for a server." />

  <Card href="/docs/workers/install/macos" title="macOS" description="Binary under launchd, and why a laptop is a poor host." />

  <Card href="/docs/workers/install/windows" title="Windows" description="Binary under Task Scheduler, or a service wrapper." />

  <Card href="/docs/workers/install/container" title="Container" description="The image, which ships the dump tools already." />

  <Card href="/docs/workers/install/source" title="Build from source" description="Go 1.26, one command, and the only path on some architectures." />
</Cards>

## What the host needs [#what-the-host-needs]

| Requirement          | Detail                                                                                           |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| **Outbound network** | The worker dials out. Nothing dials in, and no port needs opening                                |
| **Disk**             | Room for the dump and its encrypted copy at the same time, so budget roughly twice the dump size |
| **Tools**            | `pg_dump`, `mysqldump` or `curl`, but only for the source types you use. `redis` needs none      |
| **Reachability**     | A route to the sources themselves, which is the whole reason it runs where it does               |
| **A clock**          | More than a few minutes of skew fails certificate validation                                     |

The binary is self-contained. The **dump tools are not**: a Postgres backup shells out to
`pg_dump`, and that has to be installed separately. See
[external tools](/docs/workers/configuration#external-tools) for which source type needs
what, and note that the container image ships all four.

## Egress [#egress]

Three destinations, all outbound TCP 443, all initiated by the worker.

| Destination                                   | Why                                                           |
| --------------------------------------------- | ------------------------------------------------------------- |
| Your `api_url`, normally `api.saved.sh`       | Startup identity, presigned upload URLs, and the confirm call |
| `hub.saved.sh`                                | The orchestration hub it polls for work                       |
| The object storage host in each presigned URL | Where the encrypted artifact is PUT                           |
| Your own sources                              | Whatever the backups point at, on their own ports             |

<Callout>
  There is no inbound rule to write and no allowlist of ours to maintain. That is the point of
  running the worker yourself. See [IP addresses](/docs/reference/ip-addresses).
</Callout>

## Platforms [#platforms]

Released binaries are built with CGO disabled, so they are statically linked and do not care
which libc the host has.

| OS      | Architectures                            | Supervisor to use                                                     |
| ------- | ---------------------------------------- | --------------------------------------------------------------------- |
| Linux   | `amd64`, `arm64`                         | [systemd](/docs/workers/install/linux)                                |
| macOS   | `amd64` (Intel), `arm64` (Apple silicon) | [launchd](/docs/workers/install/macos)                                |
| Windows | `amd64`, `arm64`                         | [Task Scheduler](/docs/workers/install/windows), or a service wrapper |

The **container image is Linux only**, and which architectures a given tag carries is a
property of that tag rather than of the project. Check before you pin one:

```bash
docker buildx imagetools inspect ghcr.io/savedhq/local-worker:v0.1.0
```

Anything not in that table, a BSD or a 32-bit ARM board, is a
[source build](/docs/workers/install/source) away. Go cross-compiles to far more targets than
we publish.

## What it does not have [#what-it-does-not-have]

The worker takes **no flags, no subcommands and no config-path argument**. It reads
`config.yaml` from its working directory and starts. That is the whole interface, and it is
why every page here sets a working directory rather than passing a path.

There is also no version flag. To find out what a host is running, read its startup log or
its image tag. See [knowing what each worker runs](/docs/workers/upgrades#knowing-what-each-worker-runs).

## Next [#next]

<Cards>
  <Card href="/docs/workers/registration" title="Registration" description="Provision the credential this needs." />

  <Card href="/docs/workers/configuration" title="Configuration" description="Write the config.yaml." />

  <Card href="/docs/workers/troubleshooting" title="Troubleshooting" description="What a healthy start looks like, and what the failures mean." />
</Cards>
