Installation

TL;DR — build from source (cargo build --release -p weir-server), grab a pre-built binary from the GitHub Releases, or use the container image (docker compose -f deploy/docker/docker-compose.yml up). or cargo install weir-server (see below) — the crates are published.

Several install paths are covered below — from source, container image, cargo install, or a pre-built release binary. They all produce the same weir-server daemon; the choice is purely about how you want to build, deploy, and update it. (The systemd section below is for running an already-installed binary as a bare-metal service, not a separate install method.)

From source (Cargo)

Prerequisites

  • Rust 1.88+ (edition 2024) — the declared MSRV (rust-version in Cargo.toml, enforced in CI). rustup default stable is enough.
  • A Unix host (Linux or macOS) to run the daemon. There is no Windows build: the whole listener layer is #[cfg(unix)], so a Windows weir-server never serves. Releases through v2.0.4 attached a weir-server.exe anyway; 2.0.5 stopped building it.
  • Producers are a different matter. weir-core is genuinely cross-platform, and since 2.0.3 the client's TCP + mutual-TLS transport is too — the windows CI job builds weir-client --features tls there on every push. A Windows producer against a Linux/macOS daemon over the mTLS listener is a supported configuration; only WeirClient::connect, the Unix-socket constructor, stays Unix-only.
  • ~500 MB free disk (build artifacts).

Storage prerequisites — the part that decides whether weir works

Everything above is about getting a binary. This is about whether that binary can keep the promise it makes. weir's entire guarantee is "a Durable ack means the record is on stable storage", and that reduces to one fsync on the WAB directory's filesystem — so the WAB's storage is weir's durability.

  • Put the WAB on a local block device. Not NFS, not SMB, not EFS, not a virtiofs share. Those are untested here and unwise regardless: the argument rests on fsync semantics a network filesystem does not necessarily provide, and a lie there is a silent lie — weir acks, and the record is not there.
  • Give it its own filesystem, or at least its own headroom. A full disk turns every push into a Nack. Set wab_max_bytes so weir refuses before the device does, which is a visible failure rather than a confusing one.
  • Run production data paths on Linux. On macOS the WAB record path uses F_BARRIERFSYNC, an ordering barrier that does not force the drive's volatile cache to the medium, so macOS is not power-loss safe at any durability tier — the daemon warns about this at startup. macOS is a fine development platform and a poor durability one. See Platform support.
  • A drive that lies about flushing defeats everything above. Most consumer SSDs have a volatile write cache; fdatasync returning is only a durability claim if the device honours cache-flush. Enterprise drives with power-loss-protected cache, or a battery/capacitor-backed controller, are what makes the Durable tier mean what it says on hardware you do not control.
  • Buffered is not power-loss durable, by design. It acks before any fsync. A process crash survives — the bytes are in the page cache and the kernel writes them out — but a power cut or kernel panic is a loss window. Choose it per record where the data is replaceable; the tier is a per-record wire byte, so one connection can mix both.

Build

git clone https://github.com/miki-przygoda/weir.git
cd weir
cargo build --release -p weir-server

The binary lands at ./target/release/weir-server. It's a single statically-linked-ish executable (links libc and libdl; everything else is in the binary). Strip with strip target/release/weir-server if you care about size (~9.5 MB → ~8.3 MB).

Minimal build (smaller dep tree)

The default build enables the http-sink, mysql-sink, and postgres-sink features, which pull in the full MySQL and PostgreSQL client stacks (and several duplicate-version transitive crates — cargo tree -d shows them, largely pinned by mysql_async / postgres-protocol). If you only need one sink, build with just that feature for a much smaller dependency tree and faster compile:

# HTTP sink only (no SQL client stacks):
cargo build --release -p weir-server --no-default-features --features http-sink
# noop only (soak-testing, no sinks compiled in):
cargo build --release -p weir-server --no-default-features

Available sink features: http-sink, mysql-sink, postgres-sink, clickhouse-sink (the noop sink is always compiled in). Trimming the default feature set is a breaking change deferred to a future major version, so the full set stays the default for now — use --no-default-features to opt out today.

Install to PATH

sudo install -m 0755 target/release/weir-server /usr/local/bin/

Or symlink for development:

sudo ln -s "$(pwd)/target/release/weir-server" /usr/local/bin/weir-server

Verify

weir-server --help    # prints the full flag/option reference
weir-server --wab-dir /tmp/wab-test --socket-path /tmp/weir-test.sock
# Ctrl-C to stop.

See quickstart.md for the full first-run walkthrough.

Updating

cd /path/to/weir
git pull
cargo build --release -p weir-server
sudo install -m 0755 target/release/weir-server /usr/local/bin/

Restart the daemon to pick up a new binary or any config change. The only hot-reload is SIGHUP, which reloads TLS cert/key/CA only (and only on builds with the tls feature) — see docs/operations/configuration.md. Every other setting requires a restart, so plan for a graceful restart window (default 30 s, see shutdown_timeout_secs).

Building other crates

cargo build --release -p weir-client    # client library (lib)
cargo build --release                   # the whole workspace

To exercise the daemon end-to-end without writing producer code, build and run the push_simple example:

cargo run --release -p weir-client --example push_simple -- \
    --socket /run/weir/weir.sock --count 5

weir-core (wire protocol types) is built transitively.

Container

The repository ships a multi-stage Dockerfile under deploy/docker/Dockerfile. The image is hardened for production use (non-root user, pinned UID 10001, STOPSIGNAL, HEALTHCHECK, supply-chain considerations). See container hardening for the security review.

Build the image

docker build -f deploy/docker/Dockerfile -t weir:latest .

(This is a manual build tagged weir:latest. Note that docker compose builds and tags its own image weir-server:local — a different tag — so the two don't collide and docker rmi weir:latest won't remove the compose image.)

The build uses BuildKit's cache mounts where available. First build takes ~3–5 minutes; subsequent builds with unchanged dependencies are ~30 seconds.

Run with Docker Compose

docker compose -f deploy/docker/docker-compose.yml up

This stores the WAB in a named Docker volume (wab_data, mounted at /var/lib/weir/wab) and exposes the metrics port on 127.0.0.1:9185. Configuration goes in deploy/docker/weir.toml.example — copy to your own path and bind-mount.

Run with bare docker run

For a production-leaning invocation:

docker run \
    --read-only \
    --tmpfs /tmp:rw,noexec,nosuid,size=64m \
    --cap-drop=ALL \
    --security-opt no-new-privileges:true \
    --pids-limit 256 \
    --memory 512m \
    --cpus 2 \
    -v weir-wab:/var/lib/weir/wab \
    -v weir-run:/run/weir \
    -v $(pwd)/weir.toml:/etc/weir/weir.toml:ro \
    -p 127.0.0.1:9185:9185 \
    --name weir \
    weir:latest

See container hardening for the rationale behind each flag.

Image variants

Only one image variant is currently built. Future plans:

  • A distroless variant on gcr.io/distroless/cc-debian12 for the smallest possible attack surface.
  • An alpine variant for size-constrained deployments (~20 MB total).

Neither is published yet.

systemd (bare-metal / VM)

For a non-container host, a ready-to-adapt hardened unit ships in deploy/systemd/:

  • weir.service — graceful-shutdown wiring (TimeoutStopSec aligned to the daemon's shutdown_timeout_secs), RuntimeDirectory/StateDirectory for the socket and WAB dirs, an ExecStartPre that creates the wab/ subdir (the daemon refuses to start if wab_dir is missing), resource limits, and the sandbox the threat model recommends.
  • weir.toml — companion config (no secrets) referenced via --config.
  • weir.env.example — EnvironmentFile pattern for keeping the sink URL / bearer token off-disk and out of argv (set it mode 0600, never commit real secrets).
  • weir-readiness.sh — a liveness+readiness probe over weir-ctl health plus a /metrics scrape.

See deploy/systemd/README.md for install, enable, and shutdown-tuning steps.

cargo install

Status: published. All nine crates are on crates.io (4.0.0 as of 2026-09-17). This works today:

cargo install weir-server

It will fetch the latest published version, build it with the host's Rust toolchain, and install to ~/.cargo/bin/weir-server. The wire protocol and public API are frozen at 1.0 under Semantic Versioning.

Pre-built release binaries

The release.yml GitHub Actions workflow builds and attaches weir-server binaries for x86_64-linux, aarch64-linux, x86_64-macos and aarch64-macos to each version tag's GitHub Release. Download the archive for your platform, or use the from-source or container paths above.

x86_64-windows was removed in 2.0.5. Releases up to and including v2.0.4 carried a weir-server.exe. It was a non-functional stub — no Unix-socket listener, and the mTLS listener is #[cfg(all(unix, ...))] too, so it had no ingest path at all — and it was the only artifact with no smoke test, because the smoke test asserts the binary was built with tls, which on Windows it could not be. If you were downloading it, it was not doing anything. Run the daemon on Linux or macOS; a Windows producer over TCP + mutual TLS is still supported.

Verifying the install

After installing via any method:

# The daemon does not create its directories (Postgres model) — make them first.
mkdir -p /tmp/weir-verify/wab /tmp/weir-verify/run && chmod 0700 /tmp/weir-verify/run
weir-server --wab-dir /tmp/weir-verify/wab --socket-path /tmp/weir-verify/run/weir.sock &
DAEMON_PID=$!

# Wait a moment for startup.
sleep 1

# Metrics endpoint should respond. weir_drain_state is pre-initialised on
# startup, so it has data on the very first scrape (no producer needed).
curl -fsS http://localhost:9185/metrics | grep '^weir_drain_state'

# Clean up.
kill -TERM $DAEMON_PID
wait $DAEMON_PID
rm -rf /tmp/weir-verify

Expected output (exactly one state is 1.0; gauge values render as floats and the lines may appear in any order):

weir_drain_state{state="draining"} 1.0
weir_drain_state{state="retrying_transient"} 0.0
weir_drain_state{state="blocked_dead_letter_full"} 0.0
weir_drain_state{state="stopped"} 0.0

That confirms the daemon started and the metrics endpoint is live. (Counter families like weir_records_accepted_total only emit a data line once a record of that durability tier has been pushed, so they won't appear yet — that's expected.)

Uninstalling

From source / cargo install

sudo rm /usr/local/bin/weir-server
# Or, if you used cargo install:
cargo uninstall weir-server

Container

docker compose -f deploy/docker/docker-compose.yml down
# Compose tags its image weir-server:local; remove that one:
docker rmi weir-server:local
# (If you ran the manual `docker build -t weir:latest .` above, also: docker rmi weir:latest)

Data

The WAB directory and dead-letter directory are not removed by any of the above. If you want a full wipe:

sudo rm -rf /var/lib/weir       # production default
rm -rf /tmp/weir-quickstart     # if you followed quickstart.md

Next steps