Skip to main content
Version: 1.7.0

Deployment

Orbit has two deployment shapes, and most applications only ever use the first:

  1. The in-process panelorbit.Module(...) mounted on your Nucleus app. It ships inside your application binary (the UI is embedded with go:embed), so there is nothing separate to deploy: build your app, run your app, open /admin. See the Quick start.
  2. The fleet plane — an optional, standalone admin server that many application nodes stream telemetry to through an embedded agent. This page is about deploying that.

Turning on the Redis live-feed relay (the cluster_* keys in Configuration) does not change shape 1. It adds a Redis instance to your infrastructure; there is still no Orbit process to deploy.

Topology

app node 1 ── orbit/agent ──┐
app node 2 ── orbit/agent ──┼──> admin-server ──> operator's browser
app node N ── orbit/agent ──┘ │
│ :9090 agent listener (bidi stream)
(each app node also keeps its │ :8080 UI listener (behind your SSO proxy)
own in-process /admin panel) │ :9091 metrics listener (opt-in)

Each agent embeds in a framework process and streams events over one Connect-RPC bidirectional stream. The server fans them out to connected operator UIs. Agents dial out to the server; the server never dials into your application nodes.

Install the server binary

The server is a single static binary with the operator UI embedded — no asset pipeline, no database, no Node toolchain needed:

go install github.com/jcsvwinston/orbit/server/cmd/admin-server@latest

@latest resolves the current tag of the orbit/server module. For reproducible installs, pin the tag instead. The current release is stated on the Quick start page, and the module tags cut with each release are listed in the Release notes.

admin-server --version # prints the installed tag (or "devel" for source builds)

Building from a checkout works too (cd server && go build ./cmd/admin-server).

Configuration

Configuration comes from three sources. Highest precedence first:

  1. Command-line flags
  2. Environment variables (NUCLEUS_ADMIN_*)
  3. Built-in defaults

Every flag has an environment-variable counterpart. admin-server --help prints the authoritative list; the tables below cover the flags you will actually reach for.

Listeners

FlagEnvironment variableDefaultWhat it does
--agent-addrNUCLEUS_ADMIN_AGENT_ADDR:9090Address the agent listener binds. Agents dial here.
--ui-addrNUCLEUS_ADMIN_UI_ADDR:8080Address the UI/operator listener binds. Browsers (or your reverse proxy) hit this.
--metrics-addrNUCLEUS_ADMIN_METRICS_ADDR(empty — disabled)Opt-in third listener serving Prometheus /metrics plus /healthz.

Agent authentication

FlagEnvironment variableDefaultWhat it does
--agent-tokenNUCLEUS_ADMIN_AGENT_TOKEN(empty)Shared bearer token every agent must present.
--agent-certNUCLEUS_ADMIN_AGENT_CERT(empty)PEM certificate for the agent listener (enables TLS).
--agent-keyNUCLEUS_ADMIN_AGENT_KEY(empty)PEM key for the agent listener.
--insecure-agent-listenerNUCLEUS_ADMIN_INSECURE_AGENT_LISTENERfalseAllow an unauthenticated agent listener on a non-loopback interface.

The server refuses to start when the agent listener would bind a non-loopback interface with no --agent-token and no TLS. You have three ways out: authenticate the listener, bind it to loopback, or pass --insecure-agent-listener — and that last one only when the network layer already restricts who can reach it. See Security.

Operator authentication

FlagEnvironment variableDefaultWhat it does
--ui-bearerNUCLEUS_ADMIN_UI_BEARER(empty)Fallback bearer token for direct UI access without a reverse proxy.
--ui-auth-headerNUCLEUS_ADMIN_UI_AUTH_HEADERX-Auth-UserTrusted-proxy header carrying the authenticated user.
--ui-email-headerNUCLEUS_ADMIN_UI_EMAIL_HEADERX-Auth-EmailTrusted-proxy header carrying the user's email.
--ui-role-headerNUCLEUS_ADMIN_UI_ROLE_HEADERX-Auth-RoleTrusted-proxy header carrying the operator role; viewer means read-only.
--ui-trusted-cidrsNUCLEUS_ADMIN_UI_TRUSTED_CIDRSloopback onlyComma-separated CIDRs allowed to set the trusted-proxy headers.
--ui-proxy-secretNUCLEUS_ADMIN_UI_PROXY_SECRET(empty)Shared secret the proxy must echo in X-Auth-Proxy-Secret before its headers are honoured.
--ui-read-onlyNUCLEUS_ADMIN_UI_READ_ONLYfalseMake every operator read-only (Data Studio mutations refused).
--ui-certNUCLEUS_ADMIN_UI_CERT(empty)PEM certificate for the UI listener (enables TLS).
--ui-keyNUCLEUS_ADMIN_UI_KEY(empty)PEM key for the UI listener.

Logging and lifecycle

FlagEnvironment variableDefaultWhat it does
--log-levelNUCLEUS_ADMIN_LOG_LEVELinfodebug | info | warn | error.
--log-formatNUCLEUS_ADMIN_LOG_FORMATjsonjson | text (structured logging via slog, to stderr).
--versionPrint the build version and exit.

Both listeners serve HTTP/2 — cleartext (h2c) by default, TLS when a cert/key pair is supplied. /healthz answers unauthenticated on every listener, so load balancers can probe without owning a token. The server shuts down gracefully on SIGINT/SIGTERM.

A systemd unit

# /etc/systemd/system/orbit-admin-server.service
[Unit]
Description=Orbit admin server (fleet observability)
After=network-online.target
Wants=network-online.target

[Service]
User=orbit
Group=orbit
ExecStart=/usr/local/bin/admin-server \
--agent-addr=:9090 \
--ui-addr=127.0.0.1:8080 \
--ui-trusted-cidrs=127.0.0.1/32 \
--metrics-addr=127.0.0.1:9091
# NUCLEUS_ADMIN_AGENT_TOKEN=… NUCLEUS_ADMIN_UI_PROXY_SECRET=…
EnvironmentFile=/etc/orbit/admin-server.env
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Keep the tokens in the EnvironmentFile (mode 0600) rather than on the command line, where the process list would expose them.

Read the two listener choices above as a pair. The UI listener binds loopback, because your SSO reverse proxy runs on the same host and forwards to it. The agent listener binds all interfaces, which is exactly why it needs the token.

A container

FROM golang:1.26 AS build
RUN CGO_ENABLED=0 go install github.com/jcsvwinston/orbit/server/cmd/admin-server@latest

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /go/bin/admin-server /admin-server
EXPOSE 8080 9090
ENTRYPOINT ["/admin-server"]
docker run -d --name orbit-admin \
-p 9090:9090 -p 8080:8080 \
-e NUCLEUS_ADMIN_AGENT_TOKEN="$AGENT_TOKEN" \
-e NUCLEUS_ADMIN_UI_BEARER="$UI_TOKEN" \
orbit-admin-server

The binary is fully self-contained (UI included), so distroless/static is enough. Use /healthz on either published port as the container health check.

Wire the agents

Each application node runs the agent as a framework extension. Add the module and pass the server's agent endpoint plus the shared token:

go get github.com/jcsvwinston/orbit/agent
import (
"os"

"github.com/jcsvwinston/nucleus/pkg/app"
"github.com/jcsvwinston/orbit/agent"
)

a, err := app.New(cfg,
app.WithExtensions(
agent.NewExtension(agent.ExtensionConfig{
Endpoints: []string{"https://admin.internal:9090"},
Token: os.Getenv("ORBIT_ADMIN_TOKEN"),
}, cfg.StateDir, appVersion),
),
)

The agent is fail-open by default, in two senses. With an empty Endpoints list the extension does nothing at all. With endpoints set but the server unreachable, the application still runs unchanged while the agent retries with exponential backoff, capped at 30 seconds.

Set RequireConnection: true to invert that: the application then refuses to boot unless an admin endpoint accepts it within RequireConnectionTimeout (default 10 seconds). What that gate actually verifies is worth reading before you rely on it — see Security.

The full agent configuration surface — heartbeat cadence, ring-buffer sizes, node labels, a standalone metrics listener — is documented in orbit/agent and the agent.ExtensionConfig godoc.

What you do NOT need to deploy

  • No database. The server keeps its state (node registry, replay buffers, the fleet audit ring) in memory, bounded. Restarting it loses replay history; agents reconnect and re-register on their own.
  • No asset server. The operator UI is embedded in the binary.
  • No separate deployment for the in-process panel. orbit.Module runs inside each application; the fleet plane is additive, not a replacement.