Configuration reference

August 29, 2026 · View on GitHub

How the proxy is configured

The proxy reads configuration in this order, each layer overriding the previous:

  1. Built-in defaults.
  2. JSON file at PROXY_CONFIG (optional).
  3. 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.

VariableDefaultDescription
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.

VariableDefaultDescription
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.sockPath 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.

VariableDefaultDescription
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_TTL24hDuration 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_ROLEuserRole 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.

VariableDefaultDescription
PROXY_STOREsqliteBackend: sqlite, postgres, or memory (dev only; data lost on restart).
PROXY_DATABASE_PATHproxy.dbSQLite 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

VariableDefaultDescription
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_ENVprodLogging mode: dev (console encoder) or prod (JSON encoder).
PROXY_LOG_LEVELdebug (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 when PROXY_TLS_CLIENT_CA is 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 adminoperator or viewer. The guard keys on admin specifically, not on a role ranking.

OperationInternal listenerExternal adminExternal non-admin
Read (GET) — any resourceallowedallowedallowed
Create (POST .../create) — protected stackallowedblocked (403)blocked (403)
Create (POST .../create) — other stackallowedallowedallowed
Create service — TaskTemplate.Networks attaches to protected overlayallowedblocked (403)blocked (403)
Update (POST .../update) — protected stackallowedallowedblocked (403)
Update (POST .../update) — other stackallowedallowedallowed
Update service — attaches a non-protected service to the protected overlayallowedblocked (403)blocked (403)
Network connect/disconnect — protected overlayallowedblocked (403)blocked (403)
Network connect/disconnect — other networkallowedallowedallowed
Delete (DELETE .../{id}) — protected stackallowedblocked (403)blocked (403)
Delete (DELETE .../{id}) — other stackallowedallowedallowed
Exec/attach — protected-stack containerallowedallowedblocked (403)
Exec/attach — non-protected containerallowedallowedallowed
Port-forward (GET /v1/forward) — protected-stack taskallowedblocked (403)blocked (403)
Port-forward (GET /v1/forward) — non-protected taskallowedallowedallowed
Port-forward with dest_addr query paramallowedblocked (400)blocked (400)
Volume read (GET /v1/volumes...)allowedallowedallowed
Volume mutate (create/delete/file delete/rename) — protected-stack volumeallowedallowedblocked (403)
Volume mutate — non-protected volumeallowedallowedallowed
Volume prune (POST /v1/volumes/prune) — bulk, node-wideallowedallowedblocked (403)
Swarm leave (POST /swarm/leave)allowedblocked (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 deploy via 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.Networks attaching to the protected overlay on create/update, and POST /networks/{id}/{connect,disconnect} against it, all return 403 whatever the role. A compromised admin certificate therefore cannot bootstrap a pivot onto agent-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 swcproxy CLI). 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_addr rejected with 400: the agent computes the destination from container_id itself, so honouring a client-supplied address would defeat the protected-stack task check. Both agent-manager and agent reject 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:

  1. Admin creates user via swcproxy user add <username> (inside the container) or POST /api/v1/users. A one-time onboarding token is generated.
  2. Admin shares the curl command with the user (printed by swcproxy user add or constructed from the token).
  3. User fetches the tar archive:
    curl -k https://proxy.example.com:2376/api/v1/onboard/<token> -o myname.tar
    
  4. User imports the Docker context:
    docker context import myname-managed myname.tar
    docker context use myname-managed
    docker ps
    
  5. 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.