Arc

Configuration

Every environment variable Arc reads, and what happens if one is missing.

Arc is configured entirely by environment variables. If a required one is missing it refuses to boot and names the variable, rather than failing later on a nil.

Required

VariablePurpose
DATABASE_URLpostgres://user:pass@host:5432/arc
SECRET_KEY_BASESigns dashboard sessions and cookies (64 bytes)
ARC_ENCRYPTION_KEY32 bytes, base64. Encrypts secrets at rest
PHX_HOSTPublic hostname, used in generated URLs and credential snippets
ARC_OIDC_ISSUERe.g. https://keycloak.example.com/realms/arc. Optional once ARC_ADMIN_PASSWORD is set
ARC_OIDC_CLIENT_IDConfidential client registered with your provider. Required with the issuer
ARC_OIDC_CLIENT_SECRETThat client's secret. Required with the issuer
ARC_ADMIN_EMAILSComma-separated allowlist of dashboard administrators. Optional once ARC_ADMIN_PASSWORD is set

Keep ARC_ENCRYPTION_KEY safe and backed up

It encrypts every app secret, encryption master key and webhook secret in the database. If it is lost, those secrets cannot be recovered and every app must be issued new credentials. It can be rotated safely; see below.

Generate the two keys with:

mix arc.gen.keys

Values are validated at boot, not on first use: SECRET_KEY_BASE must be at least 64 bytes, ARC_ENCRYPTION_KEY exactly 32 bytes of base64, ARC_ADMIN_EMAILS a list of email addresses, PHX_PUBLIC_SCHEME http or https, and every numeric variable an integer. A bad value fails the boot with the variable's name.

Rotating the encryption key

  1. Generate a new key with mix arc.gen.keys.

  2. Set it as ARC_ENCRYPTION_KEY and move the previous one to ARC_ENCRYPTION_KEY_RETIRED. Restart every node; existing rows still decrypt.

  3. Re-encrypt every stored secret under the new key, once, from any node:

    bin/arc eval "Arc.Release.rewrap()"   # or: mix arc.rewrap
  4. Remove ARC_ENCRYPTION_KEY_RETIRED and restart.

ARC_ENCRYPTION_KEY_RETIRED accepts several keys separated by commas, so a rotation that was interrupted can be finished later.

Optional

VariableDefaultPurpose
ARC_ADMIN_PASSWORD—Password sign-in for the dashboard, at least 12 characters. See dashboard
PORT4000Listen port
PHX_PUBLIC_PORT443Port shown in snippets and generated URLs
PHX_PUBLIC_SCHEMEhttpsScheme for the same. With http the dashboard cookie loses its Secure flag
POOL_SIZE10Postgres connection pool
ARC_ENCRYPTION_KEY_RETIRED—Previous encryption keys, comma-separated, during a rotation
ARC_TRUSTED_PROXIESnoneCIDRs whose X-Forwarded-For is believed, e.g. 10.0.0.0/8,fd00::/8. Unset: the header is ignored
ARC_DRAIN_SECONDS5Wait between readiness turning 503 and clients being closed on shutdown
ARC_MAX_CONNECTIONS_PER_NODEunlimitedSafety ceiling per node
ARC_METRICS_AUTH_TOKEN—If set, /metrics requires this bearer token. Unset, /metrics is public
ARC_CLUSTER_STRATEGYnonenone, gossip, or dns
ARC_CLUSTER_DNS_QUERY—Headless service name, required for dns
RELEASE_COOKIE—Shared Erlang distribution cookie, required when clustered

Limits and timing

Every value is per node; see clustering for what that means across a cluster.

VariableDefaultPurpose
ARC_CONNECT_RATE_PER_MINUTE120WebSocket connection attempts per client address per minute (and burst). Over it: 429 with Retry-After, before the upgrade
ARC_SUBSCRIBE_RATE20Subscribe attempts per connection per second. Over it: subscription error RateLimited, status 429
ARC_AUTH_FAILURE_LIMIT10Failed private/presence authorisations per connection per minute. Over it: close 4010, do not reconnect
ARC_API_RATE / ARC_API_BURST1000 / 2000HTTP API requests per app per second, sustained and burst. Over it: 429
ARC_CLIENT_EVENT_RATE10Client events per connection per second
ARC_ACTIVITY_TIMEOUT120Seconds of silence before the server pings, advertised to clients
ARC_PONG_TIMEOUT_MS30000How long to wait for the pong before closing with 4201
ARC_MAX_QUEUE_LEN5000Frames queued for one connection before it is closed as a slow consumer (4102)
ARC_MAX_FRAME_SIZE262144Largest inbound WebSocket frame, bytes
ARC_CACHE_TTL_MS1800000How long a cache channel keeps its last event

Per-app settings

These live in the database and are edited in the dashboard, because they differ between apps rather than between deployments:

SettingDefaultEffect
Client eventsoffWhether clients may publish to each other on private and presence channels
Connection limitunlimitedConnections past it are refused with a do-not-reconnect code
Presence member ceiling100 per channelJoining a full channel returns a subscription error rather than a truncated list
Largest event payload10 KBPublishes above it are refused with 413
subscription_count queriesoffCounting subscribers asks every node, so it is opt-in
Encryption master keynoneRequired before encrypted channels accept events

Identity provider

The dashboard signs in through OpenID Connect with PKCE. Arc validates the ID token signature against the provider's JWKS and checks iss, aud, exp and nonce before trusting any claim. An administrator needs a verified email that appears in ARC_ADMIN_EMAILS; the allowlist is re-checked on every request, so removing an address revokes access at once. Sessions last 12 hours.

Only the subject and email are kept in the session. Arc stores no tokens, because it calls nothing on the administrator's behalf.

If the provider is unreachable, only sign-in is affected: connections and the HTTP API keep working, and Arc retries the provider in the background.

Keycloak

  1. Create a realm.
  2. Create a client with Client authentication on, the redirect URI https://<your Arc host>/auth/callback, and https://<your Arc host>/* as a valid post-logout redirect URI.
  3. Enable Standard flow, disable Direct access grants, and set PKCE to S256.
  4. Copy the client secret into ARC_OIDC_CLIENT_SECRET.

The realm used by docker compose is in docker/keycloak and is a working example of those settings.

On this page