---
title: "Build from source"
description: "One command, and the only path on an architecture we do not publish."
url: "https://saved.sh/docs/workers/install/source"
---

The worker is open source, which is the point: it is the one component that runs on your
machine holding your credentials, so you can read the code that handles them.

```
https://github.com/savedhq/local-worker
```

Building it yourself is also the answer to two practical problems: an architecture we publish
no binary for, and a container image whose tag does not list yours.

## Build the binary [#build-the-binary]

Go 1.26 or newer, and nothing else. There is no C toolchain to find and no generation step to
run first.

```bash
git clone https://github.com/savedhq/local-worker
cd local-worker

go build -o local-worker ./cmd
```

To run it straight out of the tree while you are reading it:

```bash
go run ./cmd
```

Either way it reads `config.yaml` from the working directory, so put one in the directory you
run from. See [Configuration](/docs/workers/configuration).

## Match the released build [#match-the-released-build]

The releases are built with these flags. Use them and you get the same binary we do, which is
what makes a checksum comparison meaningful.

```bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
  go build -trimpath -ldflags="-s -w" -o local-worker-linux-amd64 ./cmd
```

`CGO_ENABLED=0` is what makes the result statically linked, so it does not care which libc the
target host has. `-trimpath` keeps your local paths out of the binary.

Swap `GOOS` and `GOARCH` for anything Go supports. The six targets we publish are
`linux`, `darwin` and `windows` on `amd64` and `arm64`, but `go tool dist list` is a much
longer list and none of the rest need anything special from us.

## Build the container image [#build-the-container-image]

**The Dockerfile does not compile anything.** It copies a prebuilt binary from `dist/`, so
that file has to exist before you build. This is deliberate: compiling Go inside a
multi-architecture build runs the toolchain under emulation, which turns seconds into tens of
minutes.

```bash
ARCH=amd64

mkdir -p dist
CGO_ENABLED=0 GOOS=linux GOARCH="$ARCH" \
  go build -trimpath -ldflags="-s -w" -o "dist/app-${ARCH}" ./cmd

docker build --build-arg TARGETARCH="$ARCH" -t local-worker:dev .
```

The image adds `curl`, `ca-certificates`, `postgresql-client`, `mysql-client` and `redis` on
top of Alpine, and runs as uid `65532` with no working directory set. Everything on the
[container page](/docs/workers/install/container) applies to your build exactly as it does to
ours, including the config landing at `/config.yaml`.

For both architectures at once, into your own registry:

```bash
for a in amd64 arm64; do
  CGO_ENABLED=0 GOOS=linux GOARCH="$a" \
    go build -trimpath -ldflags="-s -w" -o "dist/app-${a}" ./cmd
done

docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/local-worker:v0.1.0 --push .
```

Buildx sets `TARGETARCH` per platform, so the right binary is copied into each. The `apk add`
layer still runs under emulation for the non-native architecture, which costs a minute or two
and is the only slow part left.

## Run the tests [#run-the-tests]

```bash
go test ./...
```

## What you are trusting either way [#what-you-are-trusting-either-way]

Building from source changes what you are trusting, and it is worth being precise about how
much. It removes our release pipeline from the chain. It does not remove the dependencies in
`go.sum`, the Go toolchain, or the base image if you build the container.

The thing worth reading, if you read one thing, is `internal/activity`: the dump, the
encryption and the upload. That is where your data is handled, and it is under a thousand
lines.

## Next [#next]

<Cards>
  <Card href="/docs/workers/install/container" title="Container" description="Running the image you just built." />

  <Card href="/docs/workers/configuration" title="Configuration" description="Every key the config file accepts." />

  <Card href="/docs/security/threat-model" title="Threat model" description="What the worker is trusted with, and what it is not." />
</Cards>
