# wire — container deployment kit
Artifacts to package + deploy wire in a container. **Optional.** The common
path is the native binary (`curl install.sh | sh`); containers exist mainly
for operators running a public-good relay or fitting wire into an existing
k8s / Fly / Cloud Run stack. If neither applies, use [native install](../INSTALL.md).
The Dockerfile produces a single distroless static image (~7 MB) that runs
every wire role — pick at `CMD` time.
## Build
```bash
# from repo root:
docker build -f deploy/Dockerfile -t wire:local .
# or with podman:
podman build -f deploy/Dockerfile -t wire:local .
```
Multi-arch (linux/amd64 + linux/arm64) via buildx:
```bash
docker buildx build --platform linux/amd64,linux/arm64 -t wire:local --load .
```
## Run
### Public-good relay
```bash
docker run -d --name wire-relay \
-v wire-relay-state:/data \
-p 127.0.0.1:8770:8770 \
--memory=1g --cpus=0.5 \
--restart=unless-stopped \
wire:local
```
Pair with a TLS-terminating edge in front (Cloudflare Tunnel sidecar, Caddy, nginx, k8s ingress). The relay binds 0.0.0.0:8770 inside the container — host port maps decide what's exposed.
### Long-running sync daemon
```bash
docker run -d --name wire-daemon \
-v wire-daemon-state:/data \
--memory=256m --cpus=0.2 \
wire:local wire daemon --interval 5
```
### One-shot CLI ops
```bash
# init + pair-host
docker run -it --rm \
-v wire-state:/data \
wire:local wire init paul --relay https://relay.slancha.ai
docker run -it --rm \
-v wire-state:/data \
wire:local wire pair-host --relay https://relay.slancha.ai
```
### MCP server (for agent runtimes that mount a wire container)
```bash
docker run -i --rm \
-v wire-state:/data \
wire:local wire mcp
```
## docker-compose two-node demo
```bash
docker compose up --build # relay + daemon
docker compose exec daemon wire init paul --relay http://relay:8770
```
See `compose.yml` in repo root.
## Why distroless
- No shell → no shell injection
- No package manager → no `curl | sh` from inside container
- `nonroot` user by default (uid 65532) → no root access on filesystem mounts
- ~7 MB total image vs ~150 MB for ubuntu base — smaller attack surface, faster pulls
- Same binary as bare-metal install — no behavior drift
## Hardening flags worth adding (Docker)
```bash
docker run --read-only \
--tmpfs /tmp \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--pids-limit=200 \
-v wire-state:/data \
wire:local
```
`--read-only` is fine because wire only writes under `/data` (mounted volume). `--cap-drop=ALL` works because the relay opens TCP from non-privileged ports only (8770 > 1024).
## Kubernetes
The image works as-is in any pod spec. Persistent state via PVC mounted at `/data`. Liveness probe: `wire --version` (distroless has no curl). Readiness probe: TCP socket on 8770. ResourceQuota/LimitRange enforce the same caps the systemd unit does.
Helm chart is BACKLOG'd — out of v0.1 scope.
## Fly.io / Railway / Render / Cloud Run
All accept the Dockerfile directly. Set `PORT`/`bind` accordingly:
```bash
# fly.toml
[http_service]
internal_port = 8770
force_https = true
[mounts]
source = "wire_state"
destination = "/data"
```
Cloud Run: relay-server is fine but cold-starts will lose pair-slot state every scale-to-zero. Use min-instances=1 for production.
## Limitations
- The build image needs network for `cargo fetch` of 294 deps (~30s on first build, ~5s with cache via `--mount=type=cache`).
- Distroless has no glibc → wire built must be musl-linked (the `Dockerfile` does this via `rust:1.88-alpine`).
- No `journalctl` inside the container; logs go to stdout/stderr → use `docker logs` or your aggregator.