relay-runner initCreates:
- CA certificate and key in
~/.local/share/relay/(macOS:~/Library/Application Support/relay/) - Server certificate signed by the CA
- Default
config.toml
relay-runner init --cloudAdditional steps beyond local mode:
- Creates data directory structure (
projects/,workspaces/for git worktrees,sessions/) - Initializes SQLite database with WAL mode
- Generates a bearer token (printed to stdout — save it securely)
- Writes cloud-mode config with
cloud_mode = true
Config file location: <state_dir>/config.toml
# Enable cloud mode — gRPC CRUD RPCs return FAILED_PRECONDITION,
# clients must use REST API. AttachSession routes through Docker exec.
cloud_mode = false
# gRPC server port
port = 50051
# REST API port
rest_port = 8080
# Data directory for SQLite, projects, workspaces
# Default: ~/.local/share/relay (Linux) or ~/Library/Application Support/relay (macOS)
data_dir = "/var/lib/relay-data"
# Maximum concurrent terminal sessions
max_sessions = 50
# VT parser scrollback buffer size (lines)
scrollback_lines = 10000
# Shells available for session creation
shell_allowlist = ["/bin/bash", "/bin/zsh", "/bin/sh"]
[tls]
cert_path = "/path/to/server.crt"
key_path = "/path/to/server.key"
ca_cert_path = "/path/to/ca.crt" # For mTLS client verification
require_mtls = false # Reject non-mTLS clients
[auth]
token_hash = "..." # Argon2 hash — generate with: relay-runner hash-token <token>
require_mtls = false
[docker]
socket_path = "/var/run/docker.sock"
default_image = "ubuntu:24.04"
allowed_images = [] # Empty = all images allowed
exec_timeout_secs = 30
memory_limit = "2g"
memory_swap = "2g" # Equal to memory_limit disables swap
cpu_limit = 2.0
# seccomp_profile = "/path/to/seccomp.json" # None = Docker default
[git]
clone_timeout_secs = 300
allowed_hosts = [] # Empty = all hosts allowed (SSRF validation still applies)
[mdns]
enabled = true
instance_name = "Relay Runner"
# Session profiles — terminal initialization templates
[[profiles]]
name = "claude-default"
# init_command = "claude"
# env_vars = { "NODE_ENV" = "production" }Two methods, can coexist:
# Generate hash for config
relay-runner hash-token "my-secret-token"
# Add to [auth] section: token_hash = "<output>"
# Client usage
curl -H "Authorization: Bearer my-secret-token" http://host:8080/api/v1/healthrelay-runner init # Creates CA
relay-runner add-client "MacBook Pro" # Issues client cert, prints PEMClient receives:
- Certificate + key PEM (stdout)
- Fingerprint and 6-digit verification code (stderr)
The verification code uses TOFU (Trust On First Use) — the client compares it with the server's advertised code.
relay-runner rotate-token
# Prints new token to stdout, updates config.toml automaticallyrelay-runner rotate-ca
# Old CA saved as ca.crt.old / ca.key.old
# Re-issue client certs: relay-runner add-client <name>docker compose up -dThe docker-compose.yml includes:
- runner — the relay-runner server with resource limits (512MB RAM, 2 CPUs)
- docker-socket-proxy — restricts Docker API access to safe endpoints only (containers, images, exec)
Volumes:
runner-state— CA, certs, configrunner-data— SQLite database, project clones, workspaces
Ports:
50051— gRPC (terminal I/O)8080— REST API
The Docker socket proxy (tecnativa/docker-socket-proxy) allows only:
- Container lifecycle (create, start, stop, remove)
- Image inspection
- Exec attach (for PTY)
- Info, version, events, ping
All other Docker APIs (auth, secrets, networks, volumes, build, swarm, etc.) are denied.
sudo cp relay-runner /usr/local/bin/
sudo cp systemd/relay-runner.service /etc/systemd/system/
sudo systemctl enable --now relay-runnerEnable with mdns.enabled = true. Advertises via Bonjour (mdns-sd crate).
Docker limitation: mDNS requires network_mode: host on Linux. On macOS, use the native runner for mDNS during development.
Structured JSON logging via tracing + tracing-subscriber. Control verbosity with RUST_LOG:
RUST_LOG=info cargo run # Standard
RUST_LOG=debug cargo run # Verbose
RUST_LOG=relay_runner=trace cargo run # Trace runner onlyAudit logging: all mutating REST requests (POST, PUT, PATCH, DELETE) are logged with method, path, source IP, and response status.