Gateway Configuration File

View as Markdown

The OpenShell gateway reads its configuration from a TOML file when --config or OPENSHELL_GATEWAY_CONFIG is set. When neither is set, the gateway reads $XDG_CONFIG_HOME/openshell/gateway.toml if that file exists. If no config file exists, the gateway starts from built-in defaults. Gateway process flags and gateway OPENSHELL_* environment variables override the file. Compute driver settings live in the driver TOML tables. See RFC 0003 for the full schema.

Source Precedence

Gateway CLI flag > gateway OPENSHELL_* env var > TOML file > built-in default

database_url is env-only. The loader rejects it when it appears in the file. When OPENSHELL_DB_URL is unset, the gateway stores its SQLite database under $XDG_STATE_HOME/openshell/gateway/openshell.db.

Package-Managed Locations

Package-managed gateways do not require a TOML file. Create one at the package’s optional config location when you need to override built-in defaults. Set OPENSHELL_GATEWAY_CONFIG in the launch environment to use a different file.

PackageOptional Gateway TOML location
Homebrew$XDG_CONFIG_HOME/openshell/gateway.toml when it exists, otherwise an existing Homebrew prefix config such as /opt/homebrew/var/openshell/gateway.toml.
Debian/Ubuntu$XDG_CONFIG_HOME/openshell/gateway.toml, usually ~/.config/openshell/gateway.toml for the systemd user service.
Fedora/RHEL RPM$XDG_CONFIG_HOME/openshell/gateway.toml, usually ~/.config/openshell/gateway.toml for the systemd user service.
Snap$SNAP_COMMON/gateway.toml, usually /var/snap/openshell/common/gateway.toml.

Layout

The file is rooted at [openshell]. Gateway-wide settings live under [openshell.gateway]. Each compute driver owns its own [openshell.drivers.<name>] table. Shared keys set at gateway scope are inherited into driver tables when not overridden.

1[openshell]
2version = 1
3
4[openshell.gateway]
5# ... gateway-wide settings ...
6
7[openshell.gateway.tls]
8# ... gateway listener TLS ...
9
10[openshell.gateway.oidc]
11# ... JWT bearer auth ...
12
13[openshell.drivers.kubernetes]
14# ... driver-specific settings ...

Full Example

A complete gateway configuration covering every section. Trim to the fields you need.

1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2# SPDX-License-Identifier: Apache-2.0
3
4[openshell]
5version = 1
6
7[openshell.gateway]
8bind_address = "0.0.0.0:8080"
9health_bind_address = "0.0.0.0:8081"
10metrics_bind_address = "0.0.0.0:9090"
11
12log_level = "info"
13
14# When empty, the gateway auto-detects Kubernetes, then Podman, then Docker.
15# VM is never auto-detected and requires an explicit entry here.
16compute_drivers = ["kubernetes"]
17
18sandbox_namespace = "openshell"
19ssh_session_ttl_secs = 3600
20
21# Subject Alternative Names baked into the gateway server certificate.
22# Wildcard DNS SANs (e.g. "*.dev.openshell.localhost") also enable sandbox
23# service URLs under that domain.
24server_sans = ["openshell", "*.dev.openshell.localhost"]
25# Allow plaintext HTTP routing for loopback sandbox service URLs.
26enable_loopback_service_http = true
27
28# Set true only for local plaintext gateways or trusted TLS termination.
29disable_tls = false
30
31# Shared driver defaults. These inherit into [openshell.drivers.<name>] tables
32# when the driver-specific table does not override them.
33default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
34supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
35client_tls_secret_name = "openshell-client-tls"
36service_account_name = "openshell-sandbox"
37host_gateway_ip = "10.0.0.1"
38enable_user_namespaces = false
39sa_token_ttl_secs = 3600
40guest_tls_ca = "/etc/openshell/certs/ca.pem"
41guest_tls_cert = "/etc/openshell/certs/client.pem"
42guest_tls_key = "/etc/openshell/certs/client-key.pem"
43
44# Optional gRPC rate limit. Both values must be positive to enable the limit.
45# Set either value to 0, or omit both, to disable rate limiting.
46grpc_rate_limit_requests = 120
47grpc_rate_limit_window_seconds = 60
48
49# Gateway listener TLS (distinct from the per-driver guest_tls_*).
50[openshell.gateway.tls]
51cert_path = "/etc/openshell/certs/gateway.pem"
52key_path = "/etc/openshell/certs/gateway-key.pem"
53client_ca_path = "/etc/openshell/certs/client-ca.pem"
54require_client_auth = false
55
56[openshell.gateway.gateway_jwt]
57signing_key_path = "/etc/openshell/jwt/signing.pem"
58public_key_path = "/etc/openshell/jwt/public.pem"
59kid_path = "/etc/openshell/jwt/kid"
60gateway_id = "openshell"
61# Omit or set to 0 only for local single-player Docker, Podman, or VM gateways.
62ttl_secs = 3600
63
64[openshell.gateway.auth]
65allow_unauthenticated_users = false
66
67[openshell.gateway.mtls_auth]
68enabled = false
69
70[openshell.gateway.oidc]
71issuer = "https://idp.example.com/realms/openshell"
72audience = "openshell-cli"
73jwks_ttl_secs = 3600
74roles_claim = "realm_access.roles"
75admin_role = "openshell-admin"
76user_role = "openshell-user"
77scopes_claim = ""

Local Docker, Podman, and VM gateways can also set [openshell.gateway.mtls_auth] enabled = true to authenticate CLI callers from verified client certificates. Kubernetes deployments must leave this unset and use OIDC or a trusted access proxy; the Helm chart does not render this table.

[openshell.gateway.gateway_jwt] ttl_secs controls gateway-minted sandbox JWT lifetime. When omitted, it defaults to 0: the token exp claim and expires_at_ms response field become 0, and the sandbox JWT does not expire. Use that default only for local single-player Docker, Podman, or VM gateways. Kubernetes and other shared deployments should set a positive TTL; Helm renders 3600 seconds by default, and the gateway logs a warning when a Kubernetes gateway uses 0.

[openshell.gateway.auth] allow_unauthenticated_users = true is an unsafe local-development and trusted-proxy escape hatch. It accepts user-facing CLI/API calls without OIDC or mTLS credentials while sandbox supervisors still authenticate with gateway-minted sandbox JWTs. Leave it false for shared and production gateways.

image_pull_policy is intentionally not a shared gateway key. Kubernetes and Docker use Always, IfNotPresent, or Never. Podman uses always, missing, never, or newer. Set it inside the relevant driver table.

Driver References

Each example is a complete TOML file for one compute driver. The examples repeat [openshell] and [openshell.gateway] so they stay copyable, and the driver tables list the accepted driver-specific keys. Driver-specific values override inherited gateway defaults. The gateway rejects unknown driver fields after inheritance is merged.

Kubernetes

The gateway runs as a Pod and creates sandbox Pods in another namespace. mTLS material for sandboxes is delivered through a Kubernetes Secret rather than host-side file paths.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "0.0.0.0:8080"
6health_bind_address = "0.0.0.0:8081"
7metrics_bind_address = "0.0.0.0:9090"
8log_level = "info"
9compute_drivers = ["kubernetes"]
10
11[openshell.gateway.tls]
12cert_path = "/etc/openshell-tls/server/tls.crt"
13key_path = "/etc/openshell-tls/server/tls.key"
14client_ca_path = "/etc/openshell-tls/client-ca/ca.crt"
15
16[openshell.drivers.kubernetes]
17namespace = "agents"
18service_account_name = "openshell-sandbox"
19default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
20image_pull_policy = "IfNotPresent"
21image_pull_secrets = ["regcred"]
22supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
23supervisor_image_pull_policy = "IfNotPresent"
24# Use the image volume on Kubernetes >= 1.35 (GA in 1.36); switch to "init-container"
25# on older clusters or where the ImageVolume feature gate is off.
26supervisor_sideload_method = "image-volume"
27grpc_endpoint = "https://openshell-gateway.agents.svc:8080"
28ssh_socket_path = "/run/openshell/ssh.sock"
29client_tls_secret_name = "openshell-client-tls"
30host_gateway_ip = "10.0.0.1"
31enable_user_namespaces = false
32app_armor_profile = "Unconfined"
33workspace_default_storage_size = "10Gi"
34# Kubernetes RuntimeClass applied to sandbox pods when the API request does
35# not specify one. Empty (default) = omit the field, using the cluster default.
36# default_runtime_class_name = "kata-containers"
37# Kubelet clamps projected tokens below 600 seconds. The driver caps values at 86400.
38sa_token_ttl_secs = 3600
39# Optional SPIFFE Workload API socket mounted into sandbox pods for dynamic
40# provider token grants. Use an absolute path under a dedicated directory;
41# shared roots such as /run, /var, /tmp, and /etc are rejected.
42# Supervisor-to-gateway auth still uses gateway JWTs.
43provider_spiffe_workload_api_socket_path = "/spiffe-workload-api/spire-agent.sock"

Docker

Sandboxes run as containers on a local bridge network. The supervisor binary is bind-mounted from the host (no in-cluster image pull required); guest mTLS material is supplied as host paths.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7compute_drivers = ["docker"]
8
9[openshell.drivers.docker]
10default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
11# Docker vocabulary: Always | IfNotPresent | Never. Empty behaves like IfNotPresent.
12image_pull_policy = "IfNotPresent"
13sandbox_namespace = "docker-dev"
14# Empty auto-detects https://host.openshell.internal:<gateway-port> when guest TLS is set.
15grpc_endpoint = "https://host.openshell.internal:17670"
16# Skip the image-pull-and-extract step by pointing at a locally built binary.
17supervisor_bin = "/usr/local/libexec/openshell/openshell-sandbox"
18supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
19guest_tls_ca = "/etc/openshell/certs/ca.pem"
20guest_tls_cert = "/etc/openshell/certs/client.pem"
21guest_tls_key = "/etc/openshell/certs/client-key.pem"
22network_name = "openshell-docker"
23host_gateway_ip = "172.17.0.1"
24ssh_socket_path = "/run/openshell/ssh.sock"
25# Unsafe operator override. Host bind mounts, including Docker local-driver
26# bind-backed volumes, expose gateway-host paths inside sandboxes and can
27# negate OpenShell isolation and filesystem controls.
28enable_bind_mounts = false
29# Set to 0 to leave Docker's runtime default unchanged.
30sandbox_pids_limit = 2048

Podman

Sandboxes run as Podman containers on a user-mode bridge network. The supervisor image is mounted read-only via Podman’s type=image mount; guest mTLS material is supplied as host paths.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7compute_drivers = ["podman"]
8
9[openshell.drivers.podman]
10# Rootless socket path. For root Podman use /run/podman/podman.sock.
11socket_path = "/run/user/1000/podman/podman.sock"
12default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
13image_pull_policy = "missing" # always | missing | never | newer
14grpc_endpoint = "https://host.containers.internal:17670"
15# The gateway overwrites gateway_port from bind_address at runtime.
16gateway_port = 17670
17network_name = "openshell"
18# Omit for the platform default: empty on Linux, 192.168.127.254 on macOS Podman machine.
19# Set "" to force Podman's host-gateway resolver.
20# host_gateway_ip = "192.168.127.254"
21sandbox_ssh_socket_path = "/run/openshell/ssh.sock"
22stop_timeout_secs = 10
23supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
24guest_tls_ca = "/etc/openshell/certs/ca.pem"
25guest_tls_cert = "/etc/openshell/certs/client.pem"
26guest_tls_key = "/etc/openshell/certs/client-key.pem"
27# Unsafe operator override. Host bind mounts, including Podman local-driver
28# bind-backed volumes, expose gateway-host paths inside sandboxes and can
29# negate OpenShell isolation and filesystem controls.
30enable_bind_mounts = false
31# Set to 0 to leave Podman's runtime default unchanged.
32sandbox_pids_limit = 2048
33# Health check interval in seconds. Lower values detect readiness faster
34# but increase process churn (each check spawns a conmon subprocess).
35# Set to 0 to disable health checks entirely. Default: 10.
36health_check_interval_secs = 10

MicroVM

Each sandbox runs inside its own libkrun microVM managed by the standalone openshell-driver-vm subprocess. Use this driver when you want stronger isolation than container namespaces alone.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7# VM is never auto-detected; an explicit entry here is required.
8compute_drivers = ["vm"]
9
10[openshell.drivers.vm]
11state_dir = "/var/lib/openshell/vm"
12# Where the gateway looks for the openshell-driver-vm subprocess binary.
13driver_dir = "/usr/local/libexec/openshell"
14default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
15grpc_endpoint = "https://host.containers.internal:17670"
16# Empty falls back to default_image.
17bootstrap_image = "ghcr.io/nvidia/openshell/sandbox:latest"
18krun_log_level = 1
19vcpus = 2
20mem_mib = 2048
21overlay_disk_mib = 4096
22guest_tls_ca = "/var/lib/openshell/guest-tls/ca.pem"
23guest_tls_cert = "/var/lib/openshell/guest-tls/client.pem"
24guest_tls_key = "/var/lib/openshell/guest-tls/client-key.pem"