Configuration reference
August 29, 2026 · View on GitHub
- How the proxy is configured
- Environment variables
- JSON config file
- Dual listener
- Agent-manager forwarding
- Stack resource protection
- User onboarding
- Docker Compose examples
How the proxy is configured
The proxy reads configuration in this order, each layer overriding the previous:
- Built-in defaults.
- JSON file at
PROXY_CONFIG(optional). - Environment variables.
PROXY_CONFIG=/etc/swarm-rbac-proxy/config.json ./swarm-rbac-proxy
If you're setting up the proxy for the first time, follow the walkthrough first — this document is a reference, not a tutorial.
Environment variables
Proxy TLS and listeners
These control how the proxy listens for incoming requests and which certificates it presents to — and demands from — clients.
| Variable | Default | Description |
|---|---|---|
PROXY_LISTEN | :2375 (:2376 with TLS) | Address for the external TCP listener. |
PROXY_TLS_CERT | (none) | Path to the proxy's TLS certificate (presented to clients so they can authenticate the proxy). |
PROXY_TLS_KEY | (none) | Path to the private key for PROXY_TLS_CERT. |
PROXY_TLS_CLIENT_CA | (none) | CA certificate used to verify incoming client certificates. When set together with PROXY_TLS_CERT/PROXY_TLS_KEY, mTLS is enabled: every external request must present a certificate signed by this CA. The proxy extracts the username from the certificate (SAN email if present, otherwise Subject CN) and looks it up in the user store. |
PROXY_TLS_CLIENT_CA_KEY | (none) | Private key for the client CA. When set, the proxy auto-issues a client certificate (ECDSA P-256, 1-year validity) for each new user created via POST /api/v1/users or swcproxy user add, and returns it in the response. Requires PROXY_TLS_CLIENT_CA. |
PROXY_INTERNAL_LISTEN | (none) | Address for a second, plain-TCP listener that bypasses mTLS and role checks. Intended for docker exec-style admin access from inside the container (e.g. 127.0.0.1:2375). See Dual listener. |
PROXY_EXTERNAL_URL | (none) | Base URL for onboarding curl instructions printed by swcproxy user add (e.g. https://proxy.example.com:2376). |
PROXY_ALLOW_INSECURE | (none) | When set to the exact string true, bypasses the startup safety check that requires mTLS (both PROXY_TLS_CERT and PROXY_TLS_CLIENT_CA) for the external listener. Without mTLS, every caller can drive the full Docker API through the proxy passthrough — PROXY_ADMIN_TOKEN alone only protects the management routes, not the Docker API. Intended only for tests and deployments where network-level isolation (e.g. an internal-only Swarm overlay) provides the security boundary. Any value other than true — empty, 1, yes, TRUE — leaves the guard active. |
Docker backend
Where the proxy forwards Docker API requests. Use either a URL (TCP or Unix socket) or the legacy socket path.
| Variable | Default | Description |
|---|---|---|
PROXY_DOCKER_URL | (none) | Docker endpoint URL (unix:///path or tcp://host:port). Mutually exclusive with PROXY_DOCKER_SOCKET. |
PROXY_DOCKER_SOCKET | /var/run/docker.sock | Path to the Docker Unix socket (legacy; prefer PROXY_DOCKER_URL). |
PROXY_DOCKER_TLS_CA | (none) | CA certificate used to verify a remote Docker daemon over TLS. |
PROXY_DOCKER_TLS_CERT | (none) | Client certificate for backend mTLS to the Docker daemon. |
PROXY_DOCKER_TLS_KEY | (none) | Private key for PROXY_DOCKER_TLS_CERT. |
User identity and management API
These control the seed user created at startup and the bearer token that protects the management API.
| Variable | Default | Description |
|---|---|---|
PROXY_ADMIN_TOKEN | (none) | Bearer token required on /api/v1/* requests. When unset and TLS is enabled, the proxy refuses to start. When unset without TLS, a warning is logged and the API is open. Additionally, if any admin-role user exists in the store and this variable is unset, the proxy refuses to start regardless of TLS. |
PROXY_ADMIN_TOKEN_FILE | (none) | Path to a file containing the admin token. Read once at startup; trailing whitespace/newlines are trimmed. Use this in Docker Swarm deployments to deliver the token via a Docker secret mounted at e.g. /run/secrets/admin-token, instead of inlining the plaintext into stack YAML where docker service inspect exposes it. PROXY_ADMIN_TOKEN (the plain env var) takes precedence when both are set. |
PROXY_ONBOARDING_TOKEN_TTL | 24h | Duration string (time.ParseDuration) for how long newly-issued onboarding tokens remain valid. Expired tokens return 410 Gone with "token expired". Must be strictly positive; the proxy refuses to start on zero, negative, or unparseable values. Expiry cannot be turned off. Any onboarding token issued before this release (rows with token_issued_at IS NULL) is treated as expired on the first attempt — regenerate with swcproxy user regenerate-token <username> before upgrading or immediately after. |
PROXY_SEED_USERNAME | (none) | Username to create at startup if it does not exist. Used to bootstrap the first user so that the very first mTLS client has a matching identity in the store. |
PROXY_SEED_ROLE | user | Role assigned to the seed user: user or admin. See the walkthrough for when to seed an admin versus a regular user. |
Data store
The proxy persists users, onboarding tokens, and the audit log in one of three backends.
| Variable | Default | Description |
|---|---|---|
PROXY_STORE | sqlite | Backend: sqlite, postgres, or memory (dev only; data lost on restart). |
PROXY_DATABASE_PATH | proxy.db | SQLite file path (used when PROXY_STORE=sqlite). |
PROXY_DATABASE_URL | (none) | PostgreSQL connection string (required when PROXY_STORE=postgres), e.g. postgres://user:pass@host:5432/db?sslmode=disable. |
PROXY_BACKUP_DIR | (derived) | Directory for default-location backups (swcproxy backup without -o, and /startbackup). Defaults to <db-dir>/backup (e.g. /data/backup). With PROXY_STORE=postgres there is no database file, so the default resolves to ./backup in the proxy's working directory — set this to a persistent volume path (e.g. /data/backup) for Postgres deployments. |
To back up and restore this data — and to understand why a database restore alone does not keep existing user contexts working — see backup-restore.md.
Integrations and observability
| Variable | Default | Description |
|---|---|---|
PROXY_CONFIG | (none) | Path to a JSON config file. Values loaded from the file are overridden by any environment variables that are set. |
PROXY_AGENT_MANAGER_URL | (none) | Backend URL for /v1/* agent-manager forwarding (e.g. wss://swarmctl_agent-manager:8080). HTTP and WebSocket upgrade are supported. A wss:///https:// URL enables mutual TLS on this hop (see the three vars below). Use the stack-qualified form (<stack>_agent-manager) to avoid overlay DNS name-collision MITM; the proxy logs a warning if the host is a bare single-label name. See Agent-manager forwarding and docs/security.md § "Stack-qualified agent-manager URL". |
PROXY_AGENT_MANAGER_TLS_BUNDLE | (none) | Single PEM file holding the internal-client cert, its key, and the internal CA cert (the consolidated internal-client Docker secret). When set it supersedes the three PROXY_AGENT_MANAGER_TLS_* vars below. Either this or all three of them are required when PROXY_AGENT_MANAGER_URL is wss://. |
PROXY_AGENT_MANAGER_TLS_CERT | (none) | Client certificate the proxy presents to the agent-manager (mutual TLS). Fallback for older bootstraps when PROXY_AGENT_MANAGER_TLS_BUNDLE is unset. |
PROXY_AGENT_MANAGER_TLS_KEY | (none) | Private key for PROXY_AGENT_MANAGER_TLS_CERT. |
PROXY_AGENT_MANAGER_TLS_CA | (none) | CA used to verify the agent-manager's server certificate. |
PROXY_PROTECTED_STACK | (auto-detected) | Name of the Docker Swarm stack containing the rbac-proxy itself — the stack whose resources should be protected from external mutation. Auto-detected from the container label com.docker.stack.namespace when the proxy runs as part of a Swarm stack. Set explicitly if auto-detection is not available (e.g. when running outside Swarm) and you still want the guard active. See Stack resource protection. |
PROXY_ENV | prod | Logging mode: dev (console encoder) or prod (JSON encoder). |
PROXY_LOG_LEVEL | debug (dev) / info (prod) | Minimum log level: debug, info, warn, error. |
JSON config file
JSON keys use snake_case (matching the Go struct tags). Unknown keys are rejected at startup.
{
"listen": ":2376",
"docker_url": "tcp://remote-host:2376",
"docker_socket": "/var/run/docker.sock",
"tls_cert": "/path/to/server-cert.pem",
"tls_key": "/path/to/server-key.pem",
"tls_client_ca": "/path/to/client-ca.pem",
"tls_client_ca_key": "/path/to/client-ca-key.pem",
"docker_tls_ca": "/path/to/ca.pem",
"docker_tls_cert": "/path/to/client-cert.pem",
"docker_tls_key": "/path/to/client-key.pem",
"store": "sqlite",
"database_path": "proxy.db",
"database_url": "postgres://user:pass@host:5432/db",
"admin_token": "my-secret-token",
"seed_username": "admin",
"seed_role": "admin",
"external_url": "https://proxy.example.com:2376",
"internal_listen": "127.0.0.1:2375",
"protected_stack": "my-stack",
"agent_manager_url": "tcp://agent-manager:9090",
"env": "prod",
"log_level": "info"
}
All fields are optional. Omitted fields fall back to their defaults above.
Dual listener
When PROXY_INTERNAL_LISTEN is set, the proxy runs two listeners:
- Internal (
PROXY_INTERNAL_LISTEN, e.g.127.0.0.1:2375): plain TCP, no mTLS, no role checks. Intended for admin access from inside the container or host (docker exec, localhost tools). - External (
PROXY_LISTEN, e.g.:2376): TLS with optional client certificate verification (VerifyClientCertIfGiven). Proxy and agent routes require a valid client cert whenPROXY_TLS_CLIENT_CAis set; the onboarding endpoint does not.
This is the recommended production setup: the internal listener handles automation and the admin CLI (swcproxy), while the external listener faces users with mTLS.
Agent-manager forwarding
When PROXY_AGENT_MANAGER_URL is set, all requests to /v1/* are forwarded to the specified backend. This feature is designed for use with SwarmCLI (coming soon), which routes agent commands (exec, logs) through the RBAC proxy, applying the same authentication and exec guard rules. It is not intended for standalone use.
PROXY_AGENT_MANAGER_URL=tcp://agent-manager:9090 ./swarm-rbac-proxy
Both standard HTTP requests and WebSocket upgrade (hijack) connections are supported. The exec guard applies to /v1/exec on the external listener: exec targeting a container in the protected stack requires admin role.
If PROXY_AGENT_MANAGER_URL is not set, /v1/* requests are forwarded to the Docker daemon like any other path.
Stack resource protection
When running inside a Docker Swarm stack, the proxy auto-detects its own stack name from container labels (com.docker.stack.namespace). Override with PROXY_PROTECTED_STACK.
Protected resource types: services, secrets, networks, volumes, configs, plus swarm/leave. Container exec and attach on protected-stack containers are restricted to admins.
Note: The exec/attach guard is always active on the external listener. Without PROXY_TLS_CLIENT_CA (no mTLS), all exec/attach requests to protected containers are blocked — no user can prove admin status. Use PROXY_INTERNAL_LISTEN for local exec access without mTLS. See security.md for details.
Permission matrix
"External non-admin" is any authenticated caller whose role is not admin —
operator or viewer. The guard keys on admin specifically, not on a
role ranking.
| Operation | Internal listener | External admin | External non-admin |
|---|---|---|---|
| Read (GET) — any resource | allowed | allowed | allowed |
| Create (POST .../create) — protected stack | allowed | blocked (403) | blocked (403) |
| Create (POST .../create) — other stack | allowed | allowed | allowed |
Create service — TaskTemplate.Networks attaches to protected overlay | allowed | blocked (403) | blocked (403) |
| Update (POST .../update) — protected stack | allowed | allowed | blocked (403) |
| Update (POST .../update) — other stack | allowed | allowed | allowed |
| Update service — attaches a non-protected service to the protected overlay | allowed | blocked (403) | blocked (403) |
| Network connect/disconnect — protected overlay | allowed | blocked (403) | blocked (403) |
| Network connect/disconnect — other network | allowed | allowed | allowed |
| Delete (DELETE .../{id}) — protected stack | allowed | blocked (403) | blocked (403) |
| Delete (DELETE .../{id}) — other stack | allowed | allowed | allowed |
| Exec/attach — protected-stack container | allowed | allowed | blocked (403) |
| Exec/attach — non-protected container | allowed | allowed | allowed |
Port-forward (GET /v1/forward) — protected-stack task | allowed | blocked (403) | blocked (403) |
Port-forward (GET /v1/forward) — non-protected task | allowed | allowed | allowed |
Port-forward with dest_addr query param | allowed | blocked (400) | blocked (400) |
Volume read (GET /v1/volumes...) | allowed | allowed | allowed |
| Volume mutate (create/delete/file delete/rename) — protected-stack volume | allowed | allowed | blocked (403) |
| Volume mutate — non-protected volume | allowed | allowed | allowed |
Volume prune (POST /v1/volumes/prune) — bulk, node-wide | allowed | allowed | blocked (403) |
| Swarm leave (POST /swarm/leave) | allowed | blocked (403) | blocked (403) |
All operations on non-protected resources are allowed for all roles.
Why these restrictions:
- Create blocked for all external callers on the protected stack: prevents namespace pollution — injecting resources into the infrastructure namespace could interfere with stack operations (name collisions, label conflicts). Legitimate deployments use
docker stack deployvia the internal listener. - Update allowed for admins on the protected stack: routine operations (image deploys, scaling, secret rotation) require updating protected services through the proxy.
- Overlay-membership mutations blocked for every external role, admin included:
TaskTemplate.Networksattaching to the protected overlay on create/update, andPOST /networks/{id}/{connect,disconnect}against it, all return403whatever the role. A compromised admin certificate therefore cannot bootstrap a pivot ontoagent-net. The only legitimate ways to change overlay membership are the host Docker socket on a manager node, or the internal loopback listener. In-place updates of protected-stack services whose spec re-affirms an existing attachment still work — the check fires only when the target service is not itself on the protected stack. - Delete blocked for all external callers on the protected stack: removing infrastructure services can make the cluster unmanageable. Recoverable only via direct container access.
- Exec/attach admin-only on the protected stack: shell access to infrastructure containers enables privilege escalation (for example direct database access via the
swcproxyCLI). Non-admins may still exec into non-protected containers. An admin exec into a container already resident on the overlay is unaffected by the rule above — that guard's scope is membership, not traffic. - Port-forward to the protected stack blocked for every role, admin included: a raw-TCP relay to an infrastructure container is an exfiltration channel that outlives the per-request authorization model, since a forwarded socket can be reused indefinitely. Symmetric to the connect/disconnect block. Admins needing this must use the host Docker socket or the internal listener.
- Port-forward
dest_addrrejected with 400: the agent computes the destination fromcontainer_iditself, so honouring a client-supplied address would defeat the protected-stack task check. Bothagent-managerandagentreject it defensively as well. - Swarm leave blocked for all external callers: it tears down the entire cluster. Only via the internal listener.
If auto-detection fails (e.g. running outside Docker) and PROXY_PROTECTED_STACK is not set, the guard is disabled and all operations are allowed.
User onboarding
The proxy supports a one-time onboarding flow to provision new users with Docker CLI access:
- Admin creates user via
swcproxy user add <username>(inside the container) orPOST /api/v1/users. A one-time onboarding token is generated. - Admin shares the curl command with the user (printed by
swcproxy user addor constructed from the token). - User fetches the tar archive:
curl -k https://proxy.example.com:2376/api/v1/onboard/<token> -o myname.tar - User imports the Docker context:
docker context import myname-managed myname.tar docker context use myname-managed docker ps - Token is consumed — it cannot be reused. If lost, the admin runs
swcproxy user regenerate-token <username>to issue a new one.
The tar archive contains meta.json (Docker context metadata) and tls/docker/{ca,cert,key}.pem (client certificate bundle). The private key is generated in memory and never stored on the server.
See api.md § Onboard a user for the endpoint reference, and the walkthrough for a live example.
Docker Compose examples
These examples deploy the proxy on a single host using Docker Compose, without Docker Swarm. For a Swarm deployment with encrypted-at-rest secrets, see README § Production deployment.
SQLite with named volume
services:
proxy:
image: eldaratech/swarmcli-rbac-proxy:latest
ports:
- "2376:2376"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./certs:/certs:ro
- proxy-data:/data
environment:
PROXY_LISTEN: ":2376"
PROXY_TLS_CERT: /certs/server-cert.pem
PROXY_TLS_KEY: /certs/server-key.pem
PROXY_TLS_CLIENT_CA: /certs/client-ca.pem
PROXY_TLS_CLIENT_CA_KEY: /certs/client-ca-key.pem
PROXY_ADMIN_TOKEN: change-me
PROXY_SEED_USERNAME: admin
PROXY_SEED_ROLE: admin
PROXY_DATABASE_PATH: /data/proxy.db
PROXY_EXTERNAL_URL: "https://localhost:2376"
PROXY_INTERNAL_LISTEN: "127.0.0.1:2375"
volumes:
proxy-data:
PROXY_DATABASE_PATH points to /data/proxy.db inside the named volume proxy-data, so user data persists across container restarts. Place your TLS certificates in a ./certs/ directory on the host.
PostgreSQL
services:
proxy:
image: eldaratech/swarmcli-rbac-proxy:latest
ports:
- "2376:2376"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./certs:/certs:ro
environment:
PROXY_LISTEN: ":2376"
PROXY_TLS_CERT: /certs/server-cert.pem
PROXY_TLS_KEY: /certs/server-key.pem
PROXY_TLS_CLIENT_CA: /certs/client-ca.pem
PROXY_TLS_CLIENT_CA_KEY: /certs/client-ca-key.pem
PROXY_ADMIN_TOKEN: change-me
PROXY_SEED_USERNAME: admin
PROXY_SEED_ROLE: admin
PROXY_STORE: postgres
PROXY_DATABASE_URL: "postgres://proxy:secret@db:5432/rbac?sslmode=disable"
PROXY_EXTERNAL_URL: "https://localhost:2376"
PROXY_INTERNAL_LISTEN: "127.0.0.1:2375"
depends_on:
- db
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: proxy
POSTGRES_PASSWORD: secret
POSTGRES_DB: rbac
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
The proxy creates the users and audit_log tables automatically on first startup.