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
| Variable | Purpose |
|---|---|
DATABASE_URL | postgres://user:pass@host:5432/arc |
SECRET_KEY_BASE | Signs dashboard sessions and cookies (64 bytes) |
ARC_ENCRYPTION_KEY | 32 bytes, base64. Encrypts secrets at rest |
PHX_HOST | Public hostname, used in generated URLs and credential snippets |
ARC_OIDC_ISSUER | e.g. https://keycloak.example.com/realms/arc. Optional once ARC_ADMIN_PASSWORD is set |
ARC_OIDC_CLIENT_ID | Confidential client registered with your provider. Required with the issuer |
ARC_OIDC_CLIENT_SECRET | That client's secret. Required with the issuer |
ARC_ADMIN_EMAILS | Comma-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.keysValues 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
-
Generate a new key with
mix arc.gen.keys. -
Set it as
ARC_ENCRYPTION_KEYand move the previous one toARC_ENCRYPTION_KEY_RETIRED. Restart every node; existing rows still decrypt. -
Re-encrypt every stored secret under the new key, once, from any node:
bin/arc eval "Arc.Release.rewrap()" # or: mix arc.rewrap -
Remove
ARC_ENCRYPTION_KEY_RETIREDand restart.
ARC_ENCRYPTION_KEY_RETIRED accepts several keys separated by commas, so a rotation that
was interrupted can be finished later.
Optional
| Variable | Default | Purpose |
|---|---|---|
ARC_ADMIN_PASSWORD | — | Password sign-in for the dashboard, at least 12 characters. See dashboard |
PORT | 4000 | Listen port |
PHX_PUBLIC_PORT | 443 | Port shown in snippets and generated URLs |
PHX_PUBLIC_SCHEME | https | Scheme for the same. With http the dashboard cookie loses its Secure flag |
POOL_SIZE | 10 | Postgres connection pool |
ARC_ENCRYPTION_KEY_RETIRED | — | Previous encryption keys, comma-separated, during a rotation |
ARC_TRUSTED_PROXIES | none | CIDRs whose X-Forwarded-For is believed, e.g. 10.0.0.0/8,fd00::/8. Unset: the header is ignored |
ARC_DRAIN_SECONDS | 5 | Wait between readiness turning 503 and clients being closed on shutdown |
ARC_MAX_CONNECTIONS_PER_NODE | unlimited | Safety ceiling per node |
ARC_METRICS_AUTH_TOKEN | — | If set, /metrics requires this bearer token. Unset, /metrics is public |
ARC_CLUSTER_STRATEGY | none | none, 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.
| Variable | Default | Purpose |
|---|---|---|
ARC_CONNECT_RATE_PER_MINUTE | 120 | WebSocket connection attempts per client address per minute (and burst). Over it: 429 with Retry-After, before the upgrade |
ARC_SUBSCRIBE_RATE | 20 | Subscribe attempts per connection per second. Over it: subscription error RateLimited, status 429 |
ARC_AUTH_FAILURE_LIMIT | 10 | Failed private/presence authorisations per connection per minute. Over it: close 4010, do not reconnect |
ARC_API_RATE / ARC_API_BURST | 1000 / 2000 | HTTP API requests per app per second, sustained and burst. Over it: 429 |
ARC_CLIENT_EVENT_RATE | 10 | Client events per connection per second |
ARC_ACTIVITY_TIMEOUT | 120 | Seconds of silence before the server pings, advertised to clients |
ARC_PONG_TIMEOUT_MS | 30000 | How long to wait for the pong before closing with 4201 |
ARC_MAX_QUEUE_LEN | 5000 | Frames queued for one connection before it is closed as a slow consumer (4102) |
ARC_MAX_FRAME_SIZE | 262144 | Largest inbound WebSocket frame, bytes |
ARC_CACHE_TTL_MS | 1800000 | How 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:
| Setting | Default | Effect |
|---|---|---|
| Client events | off | Whether clients may publish to each other on private and presence channels |
| Connection limit | unlimited | Connections past it are refused with a do-not-reconnect code |
| Presence member ceiling | 100 per channel | Joining a full channel returns a subscription error rather than a truncated list |
| Largest event payload | 10 KB | Publishes above it are refused with 413 |
subscription_count queries | off | Counting subscribers asks every node, so it is opt-in |
| Encryption master key | none | Required 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
- Create a realm.
- Create a client with Client authentication on, the redirect URI
https://<your Arc host>/auth/callback, andhttps://<your Arc host>/*as a valid post-logout redirect URI. - Enable Standard flow, disable Direct access grants, and set PKCE to
S256. - 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.