Core process settings
RELAY_ADDR controls the TCP listener, RELAY_DATA_DIR controls the persistent state root, and RELAY_SOCKET controls the local Unix socket path.
main.go exposes a complete control plane: lane policy, edge access, signed-link generation, promotion approval, manual deploys, lane expiry, user accounts with roles, secrets encryption at rest, an audit log, sequential build numbers, token and cookie auth, Unix socket access, sync sessions, deploy history, services, plugins, webhook-driven deploys, owner operations telemetry, guided diagnostics through /api/doctor, and the persistent runtime state behind the dashboard.
RELAY_ADDR=:8080
RELAY_DATA_DIR=./data
RELAY_SOCKET=./data/relay.sock
RELAY_SECRET_KEY=strong-passphrase
RELAY_CORS_ORIGINS=https://dashboard.example.com
RELAY_ENABLE_PLUGIN_MUTATIONS=false
RELAY_BASE_DOMAIN=relay.example.com
RELAY_DASHBOARD_HOST=admin.relay.example.com
RELAY_EDGE_SIGNING_SECRET=replace-me
[email protected]
RELAY_ACME_ADDR=:80The agent stays fairly small on purpose. Environment variables control listen addresses, auth behavior, secrets encryption, lane host generation, dashboard host routing, upload quotas, rollout timing, diagnostics, and language image defaults.
RELAY_ADDR controls the TCP listener, RELAY_DATA_DIR controls the persistent state root, and RELAY_SOCKET controls the local Unix socket path.
GET /api/version reports relayd build metadata and Station runtime version details. GET /api/doctor reports data-dir, Docker, socket, ACME, dashboard host, managed subdomain DNS, Caddy proxy, and webhook health so the CLI and dashboard can guide setup from one source of truth.
User accounts take precedence when any exist. RELAY_TOKEN seeds legacy single-token mode as a fallback. RELAY_CORS_ORIGINS gates browser origins. The dashboard stores the session in an HttpOnly relay_session cookie, and relay-login edge access reuses that identity model.
Set RELAY_SECRET_KEY to any string and all new secrets are stored as AES-256-GCM ciphertext. Unencrypted secrets written before the key was set are still readable during migration.
RELAY_ENABLE_PLUGIN_MUTATIONS stays false by default, while RELAY_GITHUB_WEBHOOK_SECRET acts as the global webhook fallback when no per-app secret matches. Remote plugin installs stay HTTPS-only and can be SHA256-pinned by the caller.
RELAY_BASE_DOMAIN lets Relay derive managed lane hosts, RELAY_DASHBOARD_HOST gives the admin its own hostname behind the global proxy, and rollout timing is controlled by RELAY_ROLLOUT_READY_TIMEOUT_SECONDS and RELAY_ROLLOUT_DRAIN_SECONDS.
RELAY_EDGE_SIGNING_SECRET signs share links for signed-link lanes. If unset, relayd derives the edge signing secret from RELAY_SECRET_KEY or the API token. dev and staging default to relay-login unless app state overrides the access policy.
dev and preview lanes can carry expires_at state derived from lane policy retention. relayd refreshes that timestamp on deploy and on manual starts or restarts, then stops expired lanes from a background worker.
The Appearance section in Server Settings lets operators pick one of six built-in presets or supply a custom CSS block that replaces all dashboard styles. The chosen theme persists in server config and loads via /api/public/theme before the login screen so the branding is consistent across authenticated and unauthenticated pages.
RELAY_MAX_UPLOAD_BYTES caps the size of any single file accepted by the sync protocol. Unset by default, so the limit is whatever the OS allows.
When at least one user account exists, the server validates session tokens from the users table. When no users exist, it falls back to legacy RELAY_TOKEN or token.txt mode. Socket requests are always trusted via filesystem ACL, and relay-login edge access reuses the same authenticated session.
POST /api/auth/setup creates the first owner. POST /api/auth/login issues a session. Roles: owner, deployer, viewer.
relay login opens a browser tab. After login the server generates a short-lived code, the CLI exchanges it at /api/auth/cli/exchange, and saves the bearer token to ~/.relay-state.json.
Authorization: Bearer <token> is accepted for user sessions and legacy tokens alike.
X-Relay-Token is what relay.js sends for HTTP transport. Checked before the session cookie.
POST /api/auth/login and /api/auth/setup set an HttpOnly relay_session cookie for the browser UI.
relay-login lanes accept an authenticated Relay session before proxying the app. signed-link and ip-allowlist are enforced through the same edge auth boundary.
signed-link lanes can now mint time-bounded share URLs through the dashboard or /api/apps/signed-link without handing operators raw edge secret material.
Requests over relay.sock are treated as local auth and rely on socket file permissions instead of a token.
State-changing browser requests require same-origin unless the origin is explicitly allowed by RELAY_CORS_ORIGINS.
GET /api/auth/session
POST /api/auth/login
POST /api/auth/setup
DELETE /api/auth/session
GET /api/auth/me
POST /api/auth/cli/exchange
GET /api/users
POST /api/users
DELETE /api/users/:id
PATCH /api/users/:id
GET /api/version
GET /api/edge/authz
Authorization: Bearer <token>
X-Relay-Token: <token>Deploy history, user accounts, secrets, runtime state, and analytics rows all live in relay.db. If you need to answer what happened, you can usually do it with files in data/ and a SQLite shell.
Deploy records, app_state, sync sessions, encrypted app secrets, user accounts, user sessions, audit log, project_services, lane policy state, promotion records, and analytics rows. Control-plane and analytics paths now use separate SQLite handles against the same file.
Persisted API token when RELAY_TOKEN is not supplied explicitly. Only used when no user accounts exist.
Per-deploy log files that back relay logs and the dashboard.
Per app/env/branch repo and staging directories used by the sync protocol. relay pull streams a tar of the repo/ subdirectory.
Installed JSON buildpack plugins loaded ahead of built-in buildpacks.
Optional local API socket that mirrors the API without serving the UI.
data/
relay.db
token.txt
logs/
workspaces/
<app>__<env>__<branch>/
repo/
staging/
plugins/
buildpacks/
relay.sockAuth, user management, audit, deploy tracking, lane config, edge auth, secrets, companions, events, project inventory, diagnostics, sync pull, webhook-driven deploys, manual deploy entrypoints, and owner operations telemetry are all present in main.go.
GET /api/auth/session returns current auth state including username and role. POST /api/auth/login authenticates with username and password and sets a session cookie. POST /api/auth/setup creates the first owner account. DELETE /api/auth/session logs out. POST /api/auth/cli/exchange trades a short-lived code for a bearer token.
GET lists all user accounts. POST creates a new user with username, password, and role. DELETE removes a user and revokes sessions. PATCH changes a user's role.
GET returns recent audit log entries including actor, action, target, and detail. Requires authentication.
GET /api/deploys lists deploy records including build_number, deployed_by, commit_message, and source. GET /api/deploys/<id> returns a single record. POST /api/deploys can queue a manual deploy from a saved workspace or git branch, and POST /api/deploys/rollback queues a rollback to the previous image.
POST each endpoint with app, env, and branch to directly control the running container without triggering a new build.
Stores repo_url, mode, traffic_mode, host_port, service_port, public_host, access_policy, ip_allowlist, expires_at, engine, and webhook_secret for one app lane. engine accepts docker or station.
Generates a temporary signed share URL for lanes using access_policy=signed-link. relayd signs relay_exp and relay_sig from the lane identity plus the edge signing secret.
Edge proxies call this endpoint before serving app traffic. relayd resolves the lane, loads app state, and enforces public, relay-login, signed-link, or ip-allowlist access centrally.
Lists promotion requests and creates new ones. Relay stores the source image, target lane, approval requirement, target deploy id, rollback id, and health result as the promotion proceeds.
Owners approve a queued promotion request here. Once approved, relayd queues a production deploy from the staged image and watches post-promote health before marking success.
Stores server-level routing settings and appearance config. Gets and sets base_domain, dashboard_host, acme_disabled, theme_name, and theme_css. Responses also include the current doctor report so the dashboard can guide DNS and TLS setup beside the writable config.
Returns the server-side operator report used by relay doctor and the dashboard setup guide. Checks include data-dir writability, secret-key presence, Docker, socket, ACME listener, dashboard host, managed subdomain DNS, Caddy proxy health, and webhook reachability.
GET returns the active theme_name and theme_css without requiring authentication. The dashboard fetches this on startup so the selected appearance applies before the login screen renders, then re-applies after session validation.
Lists, creates, and deletes managed companion services. /api/apps/companions/restart restarts a named companion in place.
GET streams the current server workspace as a tar archive. Used by relay pull to sync the server-side repo back to the local machine. Responds with X-Workspace-Version containing the current repo hash.
GET lists installed buildpack plugins. POST installs a new one when mutation is enabled. POST /api/plugins/buildpacks/install-url installs from a verified HTTPS URL, and DELETE removes a plugin.
Returns the owner-only operations snapshot used by the Operations tab: live Docker CPU, memory, storage, container counts, and latest-deploy versus previous-deploy comparisons across apps and lanes.
Provide the dashboard with live project and deploy state via SSE plus a current inventory endpoint.
Matches repo_url against app_state rows, verifies GitHub signatures, extracts the head commit message, assigns a build number, and queues the same deploy path the CLI uses.
{
"app": "demo",
"env": "dev",
"branch": "main",
"repo_url": "https://github.com/org/repo.git",
"project_root": "apps/api",
"build_context": "apps/api",
"dockerfile": "apps/api/Dockerfile",
"mode": "traefik",
"traffic_mode": "edge",
"host_port": 3005,
"service_port": 3000,
"public_host": "demo-dev-a1b2c3d4.relay.example.com",
"access_policy": "relay-login",
"expires_at": 1776332200000,
"ip_allowlist": "",
"engine": "docker",
"webhook_secret": "super-secret"
}{
"id": "f3a2...",
"build_number": 42,
"deployed_by": "alice",
"commit_message": "fix: auth middleware",
"source": "promote",
"app": "demo",
"env": "staging",
"branch": "main",
"status": "success",
"commit_sha": "a1b2c3d...",
"created_at": "...",
"started_at": "...",
"ended_at": "...",
"image_tag": "...",
"preview_url": "https://..."
}{
"id": "promo_...",
"app": "demo",
"source_env": "staging",
"source_branch": "main",
"source_image": "relay/demo:staging-main-...",
"target_env": "prod",
"target_branch": "main",
"status": "running",
"approval_required": true,
"requested_by": "alice",
"approved_by": "owner-user",
"target_deploy_id": "deploy_...",
"health_status": "healthy"
}RELAY_NODE_IMAGE=node:22
RELAY_NODE_RUN_IMAGE=node:22-slim
RELAY_GO_IMAGE=golang:1.22
RELAY_GO_RUN_IMAGE=debian:bookworm-slim
RELAY_PY_IMAGE=python:3.12
RELAY_PY_RUN_IMAGE=python:3.12-slim
RELAY_JAVA_BUILD_IMAGE=eclipse-temurin:21-jdk
RELAY_JAVA_RUN_IMAGE=eclipse-temurin:21-jre
RELAY_DOTNET_SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0
RELAY_DOTNET_ASPNET_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0
RELAY_RUST_IMAGE=rust:1.78
RELAY_RUST_RUN_IMAGE=debian:bookworm-slim
RELAY_CC_IMAGE=gcc:13
RELAY_CC_RUN_IMAGE=debian:bookworm-slim
RELAY_NGINX_IMAGE=nginx:alpineContainerRuntime covers build, run, remove, inspect, exec, network, volume, image, and log operations. Docker is the default. Station is available as a per-app experimental alternative. Both backends can run on the same server at the same time.
Covers RunDetached, Remove, IsRunning, ContainerIP, PublishedPort, Exec, NetworkConnect, EnsureNetwork, RemoveNetwork, RemoveVolume, Build, RemoveImage, ListImages, and LogStream.
Implements the current runtime operations through Docker CLI with BuildKit enabled. relayd starts with DockerRuntime unless a per-app lane is switched deliberately.
Experimental alternative runtime. Set engine to station in app config only when you deliberately want the secondary path. Other lanes stay on Docker by default.
Station lanes are forced to port mode and edge traffic. Blue-green slots are not supported. Exec, companion services, named volumes, and host-based service name resolution all work.
POST /api/apps/config
{
"engine": "docker"
}
POST /api/apps/config
{
"engine": "station"
}
// station enforces
// mode: "port"
// traffic_mode: "edge"
// no blue-green slots