Environment Variables
August 3, 2026 ยท View on GitHub
This document lists the lab environment variables that matter for transport
and auth setup. The complete per-service env inventory is generated from
PluginMeta and lives in
generated/env-reference.md and
generated/env-reference.json.
Direct Stdio Proxy
The default bearer secret is separate from the hosted daemon administrator token:
LABBY_PROXY_BEARER_TOKEN=replace-with-a-generated-secret
proxy.bearer_token_env may name another key. labby setup proxy --auth bearer generates and writes the value when it is absent; piping a value to
--bearer-token-stdin replaces it without writing the literal to TOML.
Other proxy-adjacent process controls are:
LABBY_TAILSCALE_BIN=tailscale
LABBY_GW_UPSTREAM_STDERR=debug
LABBY_PROXY_TEST_RENEW_MS exists only under the proxy-testkit feature and
is not production configuration. OAuth proxy runs also use the live-daemon
discovery/auth variables described below. There are no environment aliases for
the non-secret [proxy] exposure, auth, path, port/range, scope, inheritance,
or shutdown preferences. See the
stdio MCP proxy guide
and generated proxy environment inventory.
HTTP Auth
Bearer mode:
LABBY_AUTH_MODE=bearer
LABBY_MCP_HTTP_TOKEN=replace-me
OAuth mode:
LABBY_AUTH_MODE=oauth
LABBY_PUBLIC_URL=https://lab.example.com
LABBY_GOOGLE_CLIENT_ID=google-client-id
LABBY_GOOGLE_CLIENT_SECRET=google-client-secret
LABBY_AUTH_ADMIN_EMAIL=admin@example.com
Optional auth overrides:
LABBY_AUTH_SQLITE_PATH=/var/lib/labby/auth.db
LABBY_AUTH_KEY_PATH=/var/lib/labby/auth-jwt.pem
LABBY_AUTH_ALLOWED_REDIRECT_URIS=https://callback.example.com/callback/*
LABBY_GOOGLE_CALLBACK_PATH=/auth/google/callback
LABBY_GOOGLE_SCOPES=openid,email,profile
LABBY_AUTH_ACCESS_TOKEN_TTL_SECS=3600
LABBY_AUTH_REFRESH_TOKEN_TTL_SECS=2592000
LABBY_AUTH_CODE_TTL_SECS=300
LABBY_AUTH_TOKEN_REQUESTS_PER_MINUTE=120
LABBY_AUTH_CODEX_ISSUER_COMPATIBILITY=false
LABBY_AUTH_MACHINE_CLIENTS_JSON=[{"client_id":"ci-agent","client_secret":"replace-me","resources":["https://lab.example.com/mcp"],"scopes":["lab"]}]
LABBY_AUTH_ENTERPRISE_ISSUERS_JSON=[{"issuer":"https://idp.example.com","jwks_uri":"https://idp.example.com/jwks","allowed_client_ids":["ci-agent"]}]
These non-secret overrides can also live in config.toml under [auth].
Rules:
LABBY_AUTH_MODEdefaults tobearer- bearer mode keeps using
LABBY_MCP_HTTP_TOKEN - oauth mode requires
LABBY_PUBLIC_URL,LABBY_GOOGLE_CLIENT_ID,LABBY_GOOGLE_CLIENT_SECRET, andLABBY_AUTH_ADMIN_EMAIL LABBY_AUTH_ADMIN_EMAILis the bootstrap admin Google email; startup fails closed if unset under oauth mode so no Google account can authenticate without explicit permission. Future SQLite-backed allowlist (web-UI managed) will grant access to additional users.LABBY_GOOGLE_CALLBACK_URLoptionally sends the browser callback to a webapp host that differs from the stable OAuth issuer inLABBY_PUBLIC_URLLABBY_AUTH_CODEX_ISSUER_COMPATIBILITY=trueis an explicit temporary workaround for openai/codex#34684; it disables RFC 9207 response-issuer advertisement and emission and should be removed after affected Codex clients are fixed- the old external issuer variables (
LABBY_OAUTH_ISSUER,LABBY_OAUTH_AUDIENCE,LABBY_OAUTH_CLIENT_ID) are no longer used LABBY_PUBLIC_URLalso feeds RFC 9728 metadata, JWT issuer/audience, and HTTP allowed-host derivation
Remote Gateway CLI Usage
labby gateway <subcommand> (add/update/remove/reload/enable/disable/list/
mcp auth */protected-route */discover/import/code *) prefers the live
labby serve daemon's HTTP API over its own local config.toml mutation --
see docs/services/GATEWAY.md for why that split exists. To reach a daemon
running on a different host (not just the one the CLI happens to run on),
the invoking machine needs exactly two things in its ~/.labby/.env:
LABBY_MCP_HTTP_TOKEN=same-token-as-the-daemon
LABBY_PUBLIC_URL=https://labby.example.com
LABBY_MCP_HTTP_TOKENmust be the same token the daemon itself uses for bearer auth (copy it from the daemon host's~/.labby/.env). Without it, the CLI still finds and reaches the daemon but every dispatch fails withauth_failed.LABBY_PUBLIC_URL(orLABBY_MCP_GATEWAY_URLif the gateway is split onto a separate hostname from the main app) is how the CLI locates the daemon when it isn't on the same host. Detection tries the local bind address first (LABBY_MCP_HTTP_HOST/LABBY_MCP_HTTP_PORT, thenconfig.toml's[mcp]section, then127.0.0.1:8765) and falls through toLABBY_MCP_GATEWAY_URLthenLABBY_PUBLIC_URLin order, using whichever responds first to/health.- If neither URL is reachable (or the token is missing/wrong), the CLI
falls back to mutating its own local
config.tomlinstead of erroring -- which keeps bootstrap flows (labby setup --provision, the very firstgateway addbeforelabby serveexists) working, but means a config change made this way won't show up on a different running daemon until one of the above is fixed.
Verified against a completely bare ~/.labby/ containing nothing but the
two lines above (no config.toml, no local databases): both a read
(gateway list) and a mutation (gateway add) reached and used the live
remote daemon correctly, and the mutation did not write a local
config.toml at all. The local GatewayManager (and its ~/.labby/auth.db)
is now built lazily and only comes into existence if remote detection
genuinely fails -- a successful remote dispatch touches no local files at
all.
Remote MCP Stdio Usage
labby serve --transport stdio (a.k.a. labby mcp) applies the same
principle at the protocol level, not just for gateway-management actions:
before building anything local, it probes for a live daemon exactly like the
CLI does above. If one is reachable, the stdio process runs as a pure
bridge -- every tools/, resources/, and prompts/ request coming in over
stdio is forwarded to the live daemon's own MCP endpoint and the response
piped straight back, with no local GatewayManager, upstream pool, or OAuth
state of its own. This is what keeps a locally spawned stdio MCP client
(e.g. an editor or agent configured to run labby mcp instead of connecting
to the daemon directly over HTTP) from becoming a second, silently-diverging
gateway instance. Same two env vars as above make this reachable from a
different host; same fallback (standalone, full local instance) applies if
no daemon is reachable.
Service Environment Variables
Supported environment variables are generated from current product metadata.
Gateway upstream secrets are referenced indirectly by environment-variable name;
for example, a persisted upstream may point at
LABBY_GW_GITHUB_AUTH_HEADER without storing its value in TOML.
Use ../generated/env-reference.md for the current required/optional environment-variable matrix, secret flags, and examples.
Provisioning Environment
labby setup --provision and scripts/incus-bootstrap.sh also honor:
TS_AUTHKEY=tskey-auth-...
When set, provisioning installs Tailscale and joins the host/container to the
tailnet using tailscale up --auth-key=file:/run/labby-ts-authkey. The key is
written only to a root-owned runtime file for the join, then removed. Leave it
unset to skip Tailscale join.