Skip to content
Fluxer API

Configuration

Fluxer reads its settings from environment variables. They live in a file named .env, in the same directory as docker-compose.yml.

For the bundled Compose stack, start with Core identity and public address and Secrets you must generate. The remaining sections cover custom settings and external services.

The installer in Get started writes .env for you and fills in every secret. Upgrading covers moving between releases.

Compose forwards every setting a self-hosted instance can change from .env to each service that reads it. A few names keep a fixed value or are not passed at all, and Keys Compose does not forward lists them with the reason. Setting one of those in .env has no effect, so add it through a local Compose override.

Compose adds a default of its own only where the stack has to differ from the built-in default, and the entry below names it. Every other forwarded name reaches its service empty when .env leaves it out, and the service applies its built-in default.

Single-quote values containing a literal $ so Compose does not expand them as variable references:

POSTGRES_PASSWORD='ab$cd'

A container’s environment is fixed when it is created. Apply changes with docker compose up -d, which recreates affected services. docker compose restart keeps the old environment.

Precedence, highest first:

  1. A value stored by the admin dashboard, for the settings listed under Runtime settings.
  2. The environment variable.
  3. The built-in default.

Use true or false for booleans, decimal integers for integer settings, and the specified object or array for JSON settings. Defaults and accepted values are listed below. Every service built from this version of the stack files or later reads an empty or blank value the same as an unset one, so an empty value restores the default. Older images do not, which Match the images to the stack files covers.

Any setting a Fluxer service reads for its configuration can come from a file instead, such as a Docker secret. Set the variable’s name with _FILE appended to the file’s path, and leave the variable itself empty. The service reads the file at startup and drops one trailing newline. Startup fails, naming both variables, when the variable and its _FILE form are both set. It also fails, naming the path, when the file is missing, unreadable or not valid UTF-8. The name to use is the one the service sees, so POSTGRES_PASSWORD in .env becomes FLUXER_POSTGRES_PASSWORD_FILE.

Add the secrets through a local Compose override. FLUXER_SUDO_MODE_SECRET is read by api and worker only:

secrets:
sudo_mode_secret:
file: ./secrets/sudo_mode_secret
services:
api:
secrets: [sudo_mode_secret]
environment:
FLUXER_SUDO_MODE_SECRET: ''
FLUXER_SUDO_MODE_SECRET_FILE: /run/secrets/sudo_mode_secret
worker:
secrets: [sudo_mode_secret]
environment:
FLUXER_SUDO_MODE_SECRET: ''
FLUXER_SUDO_MODE_SECRET_FILE: /run/secrets/sudo_mode_secret

Compose still checks the required names in .env before it applies the override, so keep a placeholder value there. Every Fluxer service gets the same shared settings, so a secret several services read, such as FLUXER_POSTGRES_PASSWORD, needs the same two entries on each of them: api, worker, gateway, media-proxy, push, admin, and snowflakes, users, gifs, messages and unfurl with their -shard services. Any service left out keeps the placeholder. The secret file must be readable by the user the container runs as.

The bundled backing containers read their own settings. The postgres container takes POSTGRES_PASSWORD: '' and POSTGRES_PASSWORD_FILE in its own environment, and on a fresh volume it creates the database with that password. seaweedfs-init, meilisearch and livekit have no _FILE form, so a file-based S3, Meilisearch or LiveKit secret still needs its real value in .env for them. FLUXER_ENV, LOG_LEVEL, FLUXER_DISABLE_RATE_LIMITS and the FLUXER_ERLANG_* settings other than FLUXER_ERLANG_COOKIE are read directly, so set them as plain values.

FLUXER_DOMAIN is required. Everything else here is optional.

VariableValue in .env.exampleControls
FLUXER_DOMAINchat.example.comThe hostname users type. Reaches services as FLUXER_BASE_DOMAIN. Compose refuses to start when it is unset or empty
FLUXER_PUBLIC_SCHEMEhttpsThe scheme users see. Must be http or https. Anything else fails startup
FLUXER_PUBLIC_PORT443The port users see. Integer. Omitted from derived URLs when it is the default for the scheme

Outside Compose these fall back to an empty base domain, http, and port 8088. Compose supplies https and 443.

The API and worker require hostname-only domain settings: no scheme, port, credentials, path, query, fragment, or whitespace. Bracket IPv6 addresses. Valid hostname spelling is preserved, including case and a terminal root dot.

FLUXER_DOMAIN also feeds the default passkey relying party identifier, the default VAPID contact address, and the edge listener address.

FLUXER_PUBLIC_ORIGIN states the same public address as one string, and The public origin has it.

Every value below ships as CHANGE_ME. Replace them all before the first start. Compose forwards each one from .env, and refuses to start when one is unset or empty, and names the variable. Generate each with openssl rand -hex 32, except the relay secret, the attachment URL secrets, and the VAPID pair, whose own entries name the command. The installer in Get started generates them all, so come here to rotate one value later or to set them up by hand.

The database login, reused as FLUXER_POSTGRES_PASSWORD. Requires changing the stored role password too.

The Meilisearch master key, reused as FLUXER_SEARCH_API_KEY. Recreate meilisearch and every service that searches.

The object-storage secret, reused as FLUXER_S3_SECRET_ACCESS_KEY. seaweedfs-init installs it as the object store’s only S3 identity on every compose up, so a changed value takes effect on the next start.

Sudo mode JWTs, as a raw HS256 key. Changing it ends every active sudo mode session.

Connection initiation tokens and harvest download links. Changing it invalidates connection initiation tokens not yet verified and harvest download links already issued.

Derives the stable placeholder name and tag that other users see while an account’s profile is hidden. Must be byte-identical on api, worker, and users-shard, or the same account shows two different names. Changing it gives every hidden profile a new placeholder. api and worker refuse to start without it when FLUXER_ENV is production. users-shard refuses to start without it unless FLUXER_ENV is development or test.

Internal RPC between the API and the Gateway. Must be byte-identical on api, worker, and gateway.

Media Proxy URLs. Must match across api, worker, media-proxy, gifs, and unfurl.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64”

Generate with openssl rand -base64 32. Upload relay capability tokens. Must be standard base64 decoding to at least 32 bytes. Must match on api, worker, and media-proxy. api and worker refuse to start without it in every mode. media-proxy refuses to start without it when FLUXER_MEDIA_PROXY_MODE is upload or relay.

FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64

Section titled “FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64”

Generate each entry with openssl rand -base64 32. Signs the attachment URLs Fluxer returns. A comma-separated list, where each entry must be standard base64 decoding to at least 32 bytes. Spaces around an entry and empty entries are ignored. The first entry signs and every entry verifies. Must match on api, worker, and media-proxy, and must differ from FLUXER_MEDIA_PROXY_SECRET_KEY. api and worker refuse to start when an entry is invalid, and with no entry they return unsigned URLs. media-proxy reads it only when FLUXER_MEDIA_PROXY_MODE is mp or upload, and then refuses to start with an invalid entry, or with no entry when FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE is report or enforce.

Compose forwards it from .env and passes it empty when unset, so a self-hosted instance returns unsigned URLs until an operator sets one. Setting it alone changes nothing on the read side, because FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE stays off until it is set too.

Rotate it in three steps, and apply each one with docker compose up -d before starting the next:

  1. Add the new secret as the second entry. Every media-proxy then accepts it before any service signs with it.
  2. Move the new secret to the first entry, so it signs.
  3. Remove the old secret no sooner than one day after step 2, because an ordinary URL it signed stays valid for up to a day.

A data package URL inside a harvest export never expires, so removing a secret ends every data package URL it signed at once, including the links inside exports already delivered, and those exports have to be rebuilt. Step 3 is safe for ordinary URLs alone.

Admin sessions, CSRF tokens, and OAuth state. Changing it signs every admin out. The admin service refuses to start when it is empty.

The admin OAuth2 client secret. The API requires a non-empty value to serve the admin application.

The BEAM distribution secret. Only the Gateway reads it. Rotate it on every Gateway node at once when clustering.

LiveKit access tokens. Must be at least 32 characters.

Base64url of the 65-byte uncompressed P-256 point, unpadded, which is 87 characters. Config validation rejects any other shape at boot. install.sh derives it with openssl ecparam and openssl ec, and npx web-push generate-vapid-keys produces the same pair.

The matching half of the pair. Base64url of the 32-byte P-256 scalar, unpadded, which is 43 characters. The API refuses to start when the scalar does not derive the public point. Both values are required even when nobody uses browser notifications.

The values below ship with a usable value. Both are required.

.env.example fluxer. The object-storage access key. Reaches services as FLUXER_S3_ACCESS_KEY_ID, which config validation requires to be non-empty.

.env.example fluxer. The LiveKit API key. Compose passes it to LiveKit as LIVEKIT_KEYS and as the webhook signing key, and to the API as FLUXER_LIVEKIT_API_KEY, so one change in .env moves them all.

Endpoint derivation from FLUXER_BASE_DOMAIN

Section titled “Endpoint derivation from FLUXER_BASE_DOMAIN”

Fluxer builds its public endpoints from the scheme, the base domain, and the port. A port of 443 under https or wss, or 80 under http or ws, is omitted.

EndpointDerived value
api, api_clientscheme, base domain, optional port, /api
appscheme, base domain, optional port
gatewayws or wss, base domain, optional port, /gateway
mediascheme, base domain, optional port, /media
static_cdnscheme, base domain, optional port. With FLUXER_STATIC_CDN_DOMAIN set it becomes https:// and that domain, with no port
adminscheme, base domain, optional port, /admin
marketingscheme, base domain, optional port, /marketing
invitescheme, FLUXER_INVITE_DOMAIN or the base domain, optional port, /invite
giftscheme, FLUXER_GIFT_DOMAIN or the base domain, optional port, /gift

Nothing in the stack reads X-Forwarded-Proto or X-Forwarded-Host. Every absolute URL comes from configuration, so set the scheme, the host, and the port by hand and keep them in sync with whatever terminates TLS.

FLUXER_PUBLIC_ORIGIN states the public address as one string, with no trailing slash: the scheme, the host, and the port when that port is not the default for the scheme. FLUXER_PUBLIC_SCHEME, FLUXER_DOMAIN and FLUXER_PUBLIC_PORT state the same address between them, so the spellings have to agree.

Use an explicit http:// or https:// origin. Surrounding spaces and a single trailing slash are accepted. Credentials, paths, query strings, fragments, control characters, and empty or zero ports are rejected.

Leave it unset unless you need to state the full address explicitly. Compose otherwise derives the public URLs from the scheme, domain and port, including non-default ports.

If you set it, keep these settings consistent with it. A mismatch can break sign-in, setup, passkeys and media access.

Each of these replaces its derived endpoint wholesale. Set one only when part of the instance answers at an address the derivation does not produce. All are optional.

The API and worker validate endpoint URLs at startup. The Gateway endpoint requires ws:// or wss://. The others require http:// or https://. Path prefixes and explicit ports are allowed. Credentials, fragments, whitespace, and backslashes are rejected. HTTP endpoint bases cannot contain a query. The Gateway URL may retain query parameters.

Default empty. A separate host for static assets. When set, the static endpoint of api and worker is forced to https with no port. Only api and worker read it, so set FLUXER_STATIC_CDN_ENDPOINT to the same origin as well for admin, app-proxy and gateway. Compose forwards it from .env.

Default empty. The host used in invite links. A hostname with no scheme and no path. Compose forwards it from .env.

Default empty. The host used in gift links. A hostname with no scheme and no path. Compose forwards it from .env.

Defaults to the derived endpoint. The public API base. Compose forwards it from .env to the services on the shared block, which leaves out app-proxy. The admin service reads the same name as its internal API target, so Compose sets it to http://api:8080 for that container.

Defaults to the derived endpoint. The API base handed to clients. Setting only one of the API endpoints splits what clients see from what the instance advertises. Compose forwards it from .env.

Defaults to the derived endpoint. The web app origin. Also one of the allowed CORS origins. Serving the client from another hostname requires setting this. Compose forwards it from .env, and builds it from the public origin when it is unset.

Default empty. Further web app origins, comma separated. Each entry must be an HTTP or HTTPS origin, or the API refuses to start. The API treats each one like the FLUXER_APP_ENDPOINT origin. It allows it through CORS, reads invite links on its host, skips unfurling client routes on its host, and refuses webhook token calls from it. Set it when one instance serves the web app on more than one hostname. Compose forwards it from .env.

Defaults to the derived endpoint. The public Gateway WebSocket URL. Compose forwards it from .env.

Defaults to the derived endpoint. The public Media Proxy URL. The gifs service accepts it as a fallback for FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT. Compose forwards it from .env, and builds it from the public origin and /media when it is unset.

Defaults to the derived endpoint. The static asset origin. Read by api, worker, admin, and app-proxy. Compose forwards it from .env. When it is unset, admin gets the public origin, and every other service derives it itself.

Defaults to the derived endpoint. The admin origin. The API accepts exactly this value plus /oauth2_callback as the admin OAuth redirect URI. Compose forwards it from .env, and builds it from the public origin and FLUXER_ADMIN_BASE_PATH when it is unset.

The admin dashboard sets its cookie flags from the scheme of this value. Over HTTPS its CSRF cookie is named __Host-csrf_token and has Secure. Over HTTP it is named csrf_token without Secure.

Defaults to an origin built from FLUXER_PUBLIC_SCHEME, FLUXER_BASE_DOMAIN and FLUXER_PUBLIC_PORT. The docs origin. Read by the docs server only, which the stack does not run, so Compose does not forward it.

Defaults to the derived endpoint. The marketing origin. The second allowed CORS origin. Its hostname is extracted and matched. Compose forwards it from .env, and uses the public origin with no path when it is unset.

Defaults to the derived endpoint. The invite base. The API reads links on its hostname as invite links and does not unfurl them. Compose forwards it from .env.

Defaults to the derived endpoint. The gift base. The API does not unfurl links on its hostname. Compose forwards it from .env.

Internal endpoints address one container from another and never appear in a browser. FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT is required by gifs. The rest are optional.

Default http://127.0.0.1:8080 in the Gateway. The API address used by gateway and push. push refuses to start without it. Compose sets http://api:8080.

Default http://127.0.0.1:8082. The internal Media Proxy address for api and worker. FLUXER_MEDIA_PROXY_ENDPOINT is read as an alias when this name is unset or empty. Compose sets http://media-proxy:8080.

No default. The internal Media Proxy address. unfurl-shard reads this name alone and exits without it. On api and worker it is an alias of FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT, read only when that name is unset or empty. Compose sets http://media-proxy:8080 on unfurl-shard only.

No default. The public Media Proxy URL used by gifs, unfurl, and media-proxy. gifs requires this or FLUXER_MEDIA_ENDPOINT to start. Set it on media-proxy so requests for its own assets use local storage instead of an HTTP round trip. For api and worker, use FLUXER_MEDIA_ENDPOINT instead. Compose forwards it from .env to every app service. When it is unset, they get FLUXER_MEDIA_ENDPOINT, or the public media URL when that is unset too.

The edge container is the only HTTP entry point. Neither layout below needs a change to the edge settings.

Bundled TLS is the default. docker compose up -d binds 80/tcp, 443/tcp, and 443/udp and obtains its own certificate for FLUXER_DOMAIN. Point DNS at the host and set nothing else. The 443 publishes stay on 443 whatever FLUXER_PUBLIC_PORT says, and FLUXER_HTTPS_PORT moves them.

Put your own reverse proxy in front by adding docker-compose.proxy.yml, a second Compose file that overrides parts of the first. Load it with -f twice, or set COMPOSE_FILE=docker-compose.yml:docker-compose.proxy.yml in .env once. The edge then serves plain HTTP on one port, and the proxy in front terminates TLS. Behind your own reverse proxy has the steps to enable the overlay, the requirements, and the per-proxy configuration.

All are optional.

Defaults to the address Compose builds from FLUXER_PUBLIC_SCHEME and FLUXER_DOMAIN, with no port, so the edge listens on 443 or 80 in the container and FLUXER_HTTPS_PORT or FLUXER_HTTP_PORT maps it on the host. What the edge listens on, and the default keeps the listener on the address the instance advertises. Honoured in the bundled layout only, because docker-compose.proxy.yml sets the literal :8080 and tunnel.compose.yml the literal :80, and either discards any .env value. A site address that lists more than one hostname therefore needs the bundled layout. Write the scheme into any value you set here, because a bare hostname means automatic HTTPS on 443 whatever FLUXER_PUBLIC_SCHEME says. Compose forwards it from .env to edge.

Default private_ranges. The peer addresses whose X-Forwarded-For header the edge trusts. The default is 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, 127.0.0.1/8, fd00::/8 and ::1, which covers every same-host proxy. Narrow it to the proxy’s own address when the proxy reaches the instance from a public address, or when clients reach the proxy from a range the default already covers. Compose forwards it from .env to edge.

Default zstd gzip. The response compression the edge offers, written as the arguments of the Caddy encode directive. gzip alone stops the edge offering zstd. Read by the edge container only, and it takes effect on docker compose up -d edge. Compose forwards it from .env to edge.

Default 127.0.0.1:8080. Where the plain-HTTP port binds. Read only under the overlay. 0.0.0.0:8080 must be firewalled to the proxy host, because the edge takes the client address from the X-Forwarded-For header of any peer in FLUXER_EDGE_TRUSTED_PROXIES.

Default 80. The host side of the edge’s HTTP publish, which serves the redirect to HTTPS and the ACME HTTP challenge under an https scheme and nothing at all under an http one. The container still listens on 80 inside. It takes an optional bind address in front of the port, so 127.0.0.1:80 keeps the publish off every public interface. Do not set it to the same host port as FLUXER_HTTPS_PORT, because that publishes one host port twice and the edge refuses to start. docker-compose.proxy.yml ignores it and publishes FLUXER_EDGE_BIND instead. tunnel.compose.yml reads it with a default of 127.0.0.1:80.

Default 443. The host side of the edge’s HTTPS publish, and the only thing it moves is that host side. Set it when the host already has something on the port the instance advertises. It moves the TCP and the UDP publish together, because HTTP/3 needs both on the same port. It takes the same optional bind address in front of the port. The overlay does not read it.

No default. Which Compose files are loaded. Read by Docker Compose itself. Set it to select the overlay for every command.

FLUXER_CADDY_SITE_ADDRESS is the old name for FLUXER_EDGE_SITE_ADDRESS. Compose reads it only when FLUXER_EDGE_SITE_ADDRESS is unset, so an older .env keeps the listener it already had. Rename FLUXER_CADDY_SITE_ADDRESS to FLUXER_EDGE_SITE_ADDRESS at any time.

The edge resolves one client address per request and rewrites X-Forwarded-For to it on every upstream hop, so no service reads what a visitor sent. When the peer is outside FLUXER_EDGE_TRUSTED_PROXIES, the edge uses the peer address and discards the header. When the peer is inside the list, the edge takes the rightmost header entry that is not itself trusted. A proxy that appends to the header therefore still passes on the real client address. A client whose own address is inside the list resolves to the proxy, which is why Trusted proxies tells a LAN or VPN deployment to narrow it.

All are optional.

Default false. Whether the client-IP header is trusted. Compose forwards it from .env with a default of true, because the edge sets the header on every upstream hop. The API has no peer-address fallback, so false on api makes every non-exempt request a 403.

Default x-forwarded-for. Which header has the client IP. Read by api, worker, admin, and gateway. It must match what the edge sets. Compose forwards it from .env.

Default empty. Addresses and CIDR ranges exempt from IP bans. Comma separated. A bare IPv4 address exempts that address, a bare IPv6 address exempts its /64, and a CIDR range such as 2001:db8:1200::/56 exempts every address in it. Every entry must parse as an IP or a CIDR range or the API fails at boot. Compose forwards it from .env.

The API returns 403 for any request whose client-IP header is missing, empty, or not a parsable address, and for every request while FLUXER_TRUST_CLIENT_IP_HEADER is false. The exceptions are /_health, /webhooks/livekit, /test, and the Bluesky client metadata and JWKS routes. The edge sets the header on every upstream hop, so a missing or unparsable header happens only in a layout that puts something other than the edge directly in front of api. A proxy in front of the edge that never sets the header passes the check, and every request then looks as though it came from the proxy.

Default [], which keys every rate limit on the caller. A JSON array of trusted callers, such as a front end that talks to the API for its own visitors. Each entry is an object with a name, a key of at least 32 characters, and buckets, the rate limit buckets the caller may set the client address for. Only donation:request_link, donation:manage, donation:checkout, oauth:token and oauth:revoke accept it. Any other bucket name is ignored. A malformed entry or a short key fails the API at boot.

The caller sends its key in X-Fluxer-Internal-Key and its own client’s address in X-Fluxer-Client-Ip. The API keys the request on that address only when the key matches an entry that lists the route’s bucket. Otherwise it ignores both headers and keys the request on the caller, so a browser cannot set the address. Self-hosters leave this empty. Compose forwards it from .env.

[{"name": "bugs", "key": "<at least 32 characters>", "buckets": ["oauth:token", "oauth:revoke"]}]

Default empty. The older form of a trusted caller limited to the three donation buckets, for the marketing site. The key must be at least 32 characters or the API fails at boot. The API also accepts the address in the older X-Fluxer-Donor-Ip header. Self-hosters leave this empty. Compose forwards it from .env.

These pick which container images Compose pulls. All are optional.

VariableValue in .env.example
FLUXER_REGISTRY_OWNERfluxerapp
FLUXER_REGISTRYghcr.io/${FLUXER_REGISTRY_OWNER}
FLUXER_IMAGE_TAGv1

FLUXER_REGISTRY_OWNER is the owner segment of the image names and falls back to fluxerapp. FLUXER_REGISTRY is the registry images are pulled from and falls back to ghcr.io/ followed by the owner. FLUXER_IMAGE_TAG is the tag on every Fluxer image and falls back to v1.

These affect only the Fluxer images. Each bundled third-party service has an image name of its own, which replaces the whole reference, registry and tag included. Set one to pull from a mirror or to move a service to another tag. All are optional.

  • FLUXER_CADDY_IMAGE: default caddy:2.11-alpine, for edge.
  • FLUXER_POSTGRES_IMAGE: default postgres:16-alpine, for postgres.
  • FLUXER_VALKEY_IMAGE: default valkey/valkey:9.1-alpine, for valkey.
  • FLUXER_NATS_IMAGE: default nats:2.14-alpine, for nats.
  • FLUXER_MEILISEARCH_IMAGE: default getmeili/meilisearch:v1.53, for meilisearch.
  • FLUXER_SEAWEEDFS_IMAGE: default chrislusf/seaweedfs:4.47, for seaweedfs and seaweedfs-init.
  • FLUXER_LIVEKIT_IMAGE: default livekit/livekit-server:v1.12.0, for livekit.

A new Postgres major does not read the data directory an older one wrote. Move FLUXER_POSTGRES_IMAGE to another major only through Move to a new Postgres major version. An upgrade refuses a refresh that would change the major the stack runs.

FLUXER_POSTGRES_HOST, FLUXER_POSTGRES_DATABASE, FLUXER_POSTGRES_USERNAME, and FLUXER_POSTGRES_PASSWORD are required in production. The rest are optional.

Default postgres. Which backend is used. Node accepts only postgres or cassandra. The internal services also accept postgresql, pg, scylla, and scylladb. Every service rejects any other value at startup. Compose sets postgres.

Default empty. A full connection URL. When set, the discrete host, database, user, and password production checks are skipped. Compose forwards it from .env.

Default 127.0.0.1. The database host. In production with postgres and no URL it must not be 127.0.0.1 or localhost. Compose forwards it from .env with a default of postgres, the bundled service.

Default 5432. The database port. Integer 1 to 65535. Compose forwards it from .env.

Default fluxer. The database name. Must be non-empty. Compose forwards it from .env, and passes the same value to the bundled postgres as POSTGRES_DB. The Postgres image creates that database only on its first start with an empty data directory, so set it before the first start. The installer’s backup takes it from the postgres container’s environment, as POSTGRES_DB, and skips the dump when FLUXER_POSTGRES_HOST or FLUXER_POSTGRES_URL points outside the stack.

Default fluxer. The role. Must be non-empty. Compose forwards it from .env, and passes the same value to the bundled postgres as POSTGRES_USER, under the same first-start rule. The installer’s backup takes it from the postgres container’s environment, as POSTGRES_USER, and skips the dump when FLUXER_POSTGRES_HOST or FLUXER_POSTGRES_URL points outside the stack.

Default fluxer. The password. The literal fluxer is rejected in production. CHANGE_ME is not. Compose fills it from POSTGRES_PASSWORD.

Default false. TLS to the database. Must be true or false. When false, an sslmode other than prefer in FLUXER_POSTGRES_URL still applies. In production it must be true unless FLUXER_SELF_HOSTED=true. Compose forwards it from .env.

Default empty. A CA certificate in PEM form. Used only when SSL is on. Compose forwards it from .env.

Default 20. Pool size. Integer 1 to 1000, per process. The total is the sum across every container. Only api, worker, users-shard and messages-shard open a pool, and Compose sets it in each from the per-service name below.

Default 25. The pool size of api. Read by Compose, which passes it to api as FLUXER_POSTGRES_MAX_CONNECTIONS.

Default 25. The pool size of worker, passed the same way.

FLUXER_USERS_SHARD_POSTGRES_MAX_CONNECTIONS

Section titled “FLUXER_USERS_SHARD_POSTGRES_MAX_CONNECTIONS”

Default 20. The pool size of users-shard, passed the same way.

FLUXER_MESSAGES_SHARD_POSTGRES_MAX_CONNECTIONS

Section titled “FLUXER_MESSAGES_SHARD_POSTGRES_MAX_CONNECTIONS”

Default 20. The pool size of messages-shard, passed the same way.

Default fluxer_kv. The key-value table name. Must match a safe Postgres identifier or startup fails. Compose forwards it from .env.

Default true. Named prepared statements. Must be true or false. Compose forwards it in the shared block, so one value governs api, worker and the Rust services at once. Set it to false behind a transaction-pooling pooler such as PgBouncer, where a named statement outlives the session that declared it.

Cassandra or Scylla is an alternative backend, selected with FLUXER_DATABASE_BACKEND=cassandra. Supply a reachable database and configure the settings below when selecting it. The bundled stack uses Postgres, does not include a Cassandra or Scylla service, and forwards none of the names below.

Default 127.0.0.1. Contact points. Comma separated. The Rust services normalise each entry to host and port, with IPv6 bracketed.

Default 9042. The port. Integer.

Default fluxer. The keyspace. Must exist already.

Default datacenter1. The local datacentre. Read by api and worker only. The Rust services ignore it.

Default empty. The login. Empty means no authentication.

Default empty. The password. Empty means no authentication.

The bundled stack requires Valkey for shared cache, live updates and deletion queues. General background jobs use NATS JetStream. Change these settings only when customising the cache or connecting an external Redis-compatible service.

Default redis://localhost:6379/0. The Redis-compatible service address. Read by api and worker. Compose forwards it from .env with a default of redis://valkey:6379/0, the bundled service.

Default standalone. Use standalone for a single Valkey server or cluster for a Redis-compatible cluster. Compose forwards it from .env.

Default 30000. Cache lifetime in the internal services, in milliseconds. The users and messages routers ignore this setting because they keep no configurable response cache. Their shard caches still honour it. Compose forwards it from .env.

Default 100000. Cache entry ceiling. Per process. The users and messages routers ignore this setting because they keep no configurable response cache. Their shard caches still honour it. Compose forwards it from .env.

Default 536870912. GIF cache size. Must be at least 16777216. Set it to 134217728 to keep the cache inside the 256mb ceiling FLUXER_GIFS_SHARD_MEMORY_LIMIT gives gifs-shard. Compose forwards it from .env to gifs-shard.

Default 300000. How often the API refreshes its IP-ban cache. A non-finite value or one at or below zero, such as 0, disables the timer. Compose forwards it from .env.

Keep persistent storage and the bundled noeviction policy to protect deletion queues and other shared state. Back up valkey-data. See Volumes and buckets for recovery implications.

Every uploaded file lands in an S3-compatible object store, which the stack provides with SeaweedFS. FLUXER_S3_ACCESS_KEY_ID and FLUXER_S3_SECRET_ACCESS_KEY are required. The rest are optional. Run a data store outside the stack has the overlay that stops the bundled SeaweedFS once the stack points at another store.

Default http://localhost:3900. The S3 API address. media-proxy defaults to empty instead. Compose forwards it from .env with a default of http://seaweedfs:8333, the bundled store.

No default. The host substituted into presigned URLs, which are the only place it appears. Compose forwards it from .env, and uses FLUXER_S3_ENDPOINT when it is unset, except on app-proxy, which gets only a value .env sets and adds it to its Content Security Policy. admin reads it to find the origin that report downloads are presigned against. See FLUXER_S3_BUCKET_REPORTS.

Default false. Path-style addressing. media-proxy defaults to true instead. Compose forwards it from .env with a default of true, which the bundled store needs.

Default local. The region string. media-proxy defaults to us-east-1. Compose forwards it from .env with a default of us-east-1, so every service signs for the same region.

Default empty. The access key. Config validation requires it non-empty on api and worker. Optional for media-proxy. Compose fills it from FLUXER_S3_ACCESS_KEY.

Default empty. The secret key. Config validation requires it non-empty on api and worker. Optional for media-proxy. Compose fills it from FLUXER_S3_SECRET_KEY.

Default empty. A temporary session token. Read by media-proxy only. Compose forwards it from .env.

Default fluxer. Processed assets. media-proxy defaults to cdn. Compose forwards it from .env with a default of fluxer, so every service and seaweedfs-init name the same bucket.

Default fluxer-uploads. Raw uploads. media-proxy defaults to uploads, app-proxy to fluxer-uploads. Compose forwards it from .env with a default of fluxer-uploads for the same reason.

Default fluxer-reports. Abuse report evidence. Read by api, worker, and admin. Compose forwards it from .env with a default of fluxer-reports, so every service and seaweedfs-init name the same bucket. admin allows one image origin for report attachments and profile snapshots in its Content Security Policy, the origin api presigns report downloads against. It works that origin out from this name, FLUXER_S3_PUBLIC_ENDPOINT or else FLUXER_S3_ENDPOINT, FLUXER_S3_FORCE_PATH_STYLE and FLUXER_S3_BUCKET_UPLOADS, so give admin the same values as api. The images load only when that origin is an address browsers can reach.

Default fluxer-harvests. Data archives. Read by api and worker. Compose forwards it from .env with a default of fluxer-harvests, so every service and seaweedfs-init name the same bucket.

Default static. Static assets. Read by media-proxy only in static mode, which the stack does not use. api and worker never read it, and seaweedfs-init does not create this bucket. Compose does not forward it.

The Media Proxy read path has overrides of its own. All are optional.

Falls back to FLUXER_S3_ENDPOINT. The read-side S3 address. Must be http or https with a host, and have no credentials, query string, or fragment. Compose forwards it from .env to media-proxy.

Falls back to FLUXER_S3_BUCKET_CDN. The read-side bucket. Read by media-proxy only. Compose forwards it from .env.

Defaults to path when path style is on, else virtual. How the bucket appears in the URL. path, virtual, or root. Anything else is a startup error. Compose forwards it from .env to media-proxy.

Default false. Whether read requests are signed. Read by media-proxy only. Compose forwards it from .env with a default of true, because seaweedfs-init installs an S3 identity and the store then refuses anonymous reads. Set false only for a public-read bucket.

The bundled stack provides Meilisearch for message search. These settings select and connect the search service.

Default elasticsearch. Which engine is used. elasticsearch or meilisearch. Compose forwards it from .env with a default of meilisearch, the bundled engine.

Default http://127.0.0.1:9200. The engine address. Compose forwards it from .env with a default of http://meilisearch:7700.

Default empty. The API key. Meilisearch uses it as the key. Elasticsearch uses it as an API key that takes precedence over the username and password. Compose fills it from MEILI_MASTER_KEY.

Default empty. Basic auth user. Elasticsearch only. Ignored under Meilisearch. Compose forwards it from .env.

Default empty. Basic auth password. Elasticsearch only. Compose forwards it from .env.

Default true. Certificate verification. Elasticsearch only. Never passed to the Meilisearch client. Compose forwards it from .env.

The bundled stack requires NATS for communication between services and JetStream for background jobs. Compose forwards the connection settings from .env, with the bundled service as the default. Change them when using an external NATS service, which must have JetStream enabled for the worker connection.

Default nats://127.0.0.1:4222. The core NATS address for api, worker, and gateway. The Gateway defaults to nats://nats:4222 instead. Compose forwards it from .env with a default of nats://nats:4222.

Alias of FLUXER_NATS_URL on api and worker, read only when that name is unset or empty. The core NATS address. Not read by the Gateway. Compose does not forward it, because it forwards FLUXER_NATS_URL.

Default nats://127.0.0.1:4222. The JetStream address for api and worker. Compose forwards it from .env, and uses FLUXER_NATS_URL when it is unset.

Default empty. NATS authentication. Read by api, worker, gateway, and the internal services. The shipped NATS runs without authentication, and Compose forwards this name to every container that connects to it.

Default nats://127.0.0.1:4222. The NATS address for the internal services and push. A separate variable from FLUXER_NATS_URL. Compose forwards it from .env, and uses FLUXER_NATS_URL when it is unset.

No default. Where the Gateway calls the API. Read by the Gateway, which derives it from FLUXER_INTERNAL_API_ENDPOINT when it is unset. Compose does not forward it.

Compose configures the internal services. Change the following settings only when customising their deployment.

Default default. The metrics prefix. It also selects the built-in default of FLUXER_SVC_MAX_CONCURRENT_REQUESTS. Changing it does not rename NATS subjects. Compose sets it per service.

Default router. Must be router or shard. Any other value prevents startup. Compose sets it per service.

Default 1. How many shards exist. Compose fixes it at 1.

Derived from the numeric suffix of POD_NAME, else 0. Which shard this process is. A shard ID at or above the shard count is a startup error. Compose sets 0 on every shard.

Default 0.0.0.0. The bind address. Serves health and metrics only. Compose does not forward it.

Default 8090. The health and metrics port. Not published. Compose does not forward it, because every internal service health check probes 8090.

Defaults to 192 for messages, 320 for snowflakes, 64 otherwise. In-flight request ceiling. The built-in defaults key off FLUXER_SVC_NAME. Compose forwards it from .env to every internal service router and shard. Left unset, each keeps its built-in default. Once set, the one value replaces the default on all of them, so a value below 320 also lowers the snowflakes ceiling. A router holds a slot for the whole round trip to its shard. A request over the ceiling is rejected at once. The API request behind it fails with a 503, and the API logs shard rejected the request because it is at its concurrency limit.

No default. The shard ordinal source and node identity. Also read by the Gateway, and by the API as the pod_name in its RPC timing records. Compose does not set it.

The API reaches the internal services over NATS. Compose forwards the client names and the tuning values below from .env. It does not forward FLUXER_SNOWFLAKE_SERVICE_SUBJECT, FLUXER_USERS_SERVICE_SUBJECT or FLUXER_GIF_SERVICE_SUBJECT, because each has to match the subject its internal service listens on.

Numeric settings require decimal safe integers. Surrounding whitespace is trimmed, and omitted or blank settings use the defaults. Explicit malformed or out-of-range values are rejected.

  • FLUXER_SNOWFLAKE_SERVICE_NATS_CLIENT_NAME, FLUXER_USERS_SERVICE_NATS_CLIENT_NAME and FLUXER_GIF_SERVICE_NATS_CLIENT_NAME: the name the API gives each NATS connection, as NATS monitoring shows it. Each defaults to a name for its service.
  • FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE: defaults to 128. Accepts 1 to 512.
  • FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK: defaults to 32, capped below the batch size. Accepts 0 through batch size minus one.
  • FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS: defaults to 5000. Accepts 1 to 60000 milliseconds.
  • FLUXER_SNOWFLAKE_SERVICE_REQUEST_TIMEOUT_MS: defaults to 6000. Accepts 1 to 60000 milliseconds.
  • FLUXER_USERS_SERVICE_TIMEOUT_MS: defaults to 6000. Accepts 1 to 2147483647 milliseconds.
  • FLUXER_USERS_SERVICE_INFLIGHT_MAX_ENTRIES: defaults to 10000. Accepts 0 through the largest safe integer.
  • FLUXER_GIF_SERVICE_TIMEOUT_MS: defaults to 12000. Accepts 1 to 2147483647 milliseconds.
  • FLUXER_GIF_SERVICE_REGISTER_SHARE_TIMEOUT_MS: defaults to 3000. Accepts 1 to 2147483647 milliseconds.

A Snowflake low watermark of 0 disables background refill before the buffer is empty. A Users in-flight entry limit of 0 disables request coalescing. It is not a limit on concurrent requests.

LiveKit is the media server for voice and video. FLUXER_LIVEKIT_API_KEY and FLUXER_LIVEKIT_API_SECRET are required for voice. The rest are optional.

Default false. The master switch for all voice and video. Compose forwards it from .env with a default of true, because the stack ships LiveKit.

Default empty. The LiveKit API key. Compose fills it from LIVEKIT_API_KEY.

Default empty. The LiveKit secret. Compose fills it from LIVEKIT_API_SECRET.

Default empty. The client-facing LiveKit URL. Set it only when LiveKit is served from another host. docker-compose.yml never passes it empty: it builds the value from FLUXER_PUBLIC_ORIGIN, or from FLUXER_PUBLIC_SCHEME, FLUXER_DOMAIN and FLUXER_PUBLIC_PORT when the origin is unset, followed by /livekit, and forwards a value .env sets. An empty value outside Compose has the API derive wss://host/livekit from the public API origin, or ws:// under http, with a non-default port kept.

Default true. Whether LiveKit discovers the address browsers dial by asking a STUN server. A host that cannot reach one over UDP fails to start with could not resolve external IP: context deadline exceeded. Set it to false and give FLUXER_LIVEKIT_NODE_IP the address instead. Read only by the livekit service.

Default empty. The address LiveKit publishes in its ICE candidates. Empty leaves the choice to the discovery above, and with that discovery off LiveKit falls back to the container’s own address, which no browser can reach. Set both together. docker compose logs livekit names the address in use as nodeIP on the starting line.

Default stun.l.google.com:19302. The first STUN server the discovery asks. Point it at another server to keep the discovery without reaching Google.

Default stun1.l.google.com:19302. The second STUN server, used the same way.

Default empty. The server-side LiveKit control API. Compose forwards it from .env with a default of http://livekit:7880, the bundled service.

No default. The default voice region. JSON with id, name, emoji, latitude, and longitude. Compose forwards it from .env, with a default region named Default at latitude and longitude 0.

Default info. How much LiveKit logs. Read by Compose only, which writes it into the LiveKit configuration as log_level.

Default 7881. The TCP media port. Read by Compose only. Compose publishes it on the host and passes it to LiveKit as rtc.tcp_port, so the port LiveKit advertises in ICE candidates is the one the host forwards.

Default 7882. The UDP media port, published and passed to LiveKit as rtc.udp_port the same way.

LiveKit media does not go through the edge. Compose publishes both media ports directly, so both must stay open in the host firewall and be forwarded to the host when it sits behind NAT. Compose points LiveKit at the webhook target http://api:8080/webhooks/livekit and configures no TURN server. The API accepts requests on that route without user authentication and without a client-IP header. A client that cannot use UDP falls back to ICE-TCP on the TCP port.

Moving a media port is one line in .env followed by docker compose up -d livekit. Compose puts the same value on the host side of the mapping, on the container side, and on the rtc port LiveKit advertises in the ICE candidates it hands to clients.

To rotate the key pair, set both names in .env, then run docker compose up -d livekit api worker to apply the change everywhere.

A peer-to-peer call sends media directly between its participants and not through LiveKit or any relay. It runs only while the voice_p2p experiment is enabled in the instance configuration. The value below names the STUN servers Fluxer hands to each participant. It is optional and Compose forwards it from .env. The stack ships no STUN server.

Default empty. A comma-separated list of stun: URLs. Participants use them to discover their public addresses.

Unset, participants receive no ICE servers and connect only when they can already reach each other, as on one local network. Two participants that cannot reach each other directly fail to connect, and either one can switch the call to LiveKit. Participants in a peer-to-peer call can learn each other’s IP addresses.

The Caddyfile is a bind mount, so the edge reads the copy that sits on disk beside docker-compose.yml. Editing it takes docker compose restart edge, because docker compose up -d leaves a container alone when only a mounted file changed. An upgrade does that restart itself, which What the script does covers. A change to any LiveKit value in .env needs docker compose up -d, because restart reuses the existing container with its old environment.

Only an instance where members sign in with email sends mail. A username instance sends none whatever the values below hold, and needs none of them. Sign-in methods covers the difference.

Email is off by default, and the conditions below turn it on. The email switch must be on. It is the value saved in the admin dashboard, or FLUXER_EMAIL_ENABLED when the dashboard has no saved value. Also, the provider must be smtp with a complete SMTP configuration, meaning FLUXER_EMAIL_FROM_EMAIL, FLUXER_EMAIL_SMTP_HOST, FLUXER_EMAIL_SMTP_PORT, FLUXER_EMAIL_SMTP_USERNAME, and FLUXER_EMAIL_SMTP_PASSWORD are all non-empty. All are optional.

The same conditions turn on a DNS check at registration. The check runs when email is on by the rule above. A switch or SMTP value saved in the admin dashboard wins over the matching variable. The address domain has to publish an MX record, or an A or AAAA record as a fallback, and an address at a domain that publishes neither is answered That email domain cannot receive mail. however well formed it is. The first admin account is no exception, so an owner address at a .lan, .internal or home.arpa name needs email left off.

.env.example false. The delivery switch. The admin dashboard value wins over this. Compose forwards it from .env.

.env.example none. The transport. smtp or none. Anything else fails startup. Compose forwards it from .env.

.env.example [email protected]. The sender address. Default empty. Compose forwards it from .env, and uses noreply@localhost when it is unset.

.env.example empty. The sender name. Default empty. An empty value sends mail under the instance product name, which is the name saved in the admin dashboard or else FLUXER_APP_PRODUCT_NAME, and a rename in the dashboard applies to the next email. A set value always wins. Compose forwards it from .env.

Default empty. The address replies go to, sent as the Reply-To header on every email. An empty value sends no Reply-To header. Must be a single bare address such as [email protected], or the API fails at boot. The admin dashboard has no field for it. Compose forwards it from .env, and .env.example lists it commented out.

.env.example empty. The base URL used in links. Must be http or https with no username, password, query, or fragment, or the API fails at boot. Falls back to the app endpoint. Compose forwards it from .env.

.env.example empty. The SMTP host. Setting any SMTP variable creates the whole SMTP block, which is absent by default. Compose forwards it from .env.

.env.example 587. The SMTP port. Falls back to 587. Compose forwards it from .env.

.env.example empty. The SMTP login. Part of the completeness check. Compose forwards it from .env.

.env.example empty. The SMTP password. Part of the completeness check. Compose forwards it from .env.

.env.example empty. How the SMTP connection is encrypted. Anything other than the four values below fails startup. Compose forwards it from .env.

ValueConnection
implicitTLS from the first byte. Most providers serve this on port 465
starttlsA plain connection that must upgrade with STARTTLS before the login. Most providers serve this on port 587
opportunisticA plain connection that upgrades with STARTTLS when the server offers it, and stays plain when it does not
noneNo TLS, even when the server offers STARTTLS. Only for a mail server on a network you trust

An empty value picks the mode from the port: implicit on port 465 and starttls on every other port. A mode saved in the admin dashboard wins over this variable, and choosing Automatic there hands the choice back to it. The dashboard shows the mode outgoing email ends up with. Automatic in the setup wizard saves the mode the port calls for, so it never follows this variable or FLUXER_EMAIL_SMTP_SECURE.

.env.example unset. The older switch, read only while FLUXER_EMAIL_SMTP_TLS_MODE is empty. true means implicit and false means opportunistic. Compose forwards it from .env, and .env.example lists it commented out. An older .env can hold FLUXER_EMAIL_SMTP_SECURE=true next to port 587, and that pair fails against a STARTTLS server with wrong version number. Remove the line or set FLUXER_EMAIL_SMTP_TLS_MODE.

The Save and test button in the admin dashboard, and Create SMTP test with an empty body, connect with the saved settings, the same ones outgoing email uses, and fail with the reason when outgoing email is turned off or incomplete. A send that fails is logged by api or worker as SMTP send failed with the host, port and TLS mode.

The API reads FLUXER_EMAIL_WEBHOOK_SECRET for inbound delivery webhooks. Compose forwards it from .env, and .env.example lists it commented out.

The API registers POST /webhooks/sweego on every deployment. The route authenticates the Standard Webhooks header triple webhook-id, webhook-timestamp, and webhook-signature against that secret, and answers 404 Email not enabled while email is off. The route sits outside the client-IP exempt list that covers /webhooks/livekit, so give the provider a delivery URL that goes through the edge, https://chat.example.com/api/webhooks/sweego.

Only api and worker build the mail service, so recreating those two is enough after a change.

ALTCHA proof-of-work is on by default. Turn it off or tune it under Instance Config, Bot protection in the admin panel. No environment variables configure it.

All are optional.

Default false. Whether the SSO provider URL may resolve to a private address. Off by default as SSRF protection. Turn it on only for split-horizon DNS or a LAN identity provider. Compose forwards it from .env.

Defaults to the host of FLUXER_PUBLIC_ORIGIN when that is set, and to FLUXER_BASE_DOMAIN otherwise. The WebAuthn relying party identifier. Changing it invalidates every passkey already registered. Compose forwards it from .env with a default of FLUXER_DOMAIN, so a public origin on another host leaves existing passkeys valid.

Default Fluxer. The relying party name browsers display. Does not follow FLUXER_DOMAIN. Compose forwards it from .env.

The complete accepted origin set, comma separated. Despite the name, an explicit value replaces the defaults and must list every origin clients use. Compose forwards it from .env with a default of the public web app origin, so a self-hosted instance accepts its own origin alone. When the variable is omitted or empty outside Compose, the API accepts the configured app origin alongside the built-in Fluxer web and Android origins. To accept only the app origin, list it alone.

Each web entry must be an HTTP(S) origin with no credentials, path beyond an optional final slash, query, or fragment. Web origins are normalised to browser form using the configured public port unless the entry supplies one. Android entries must use android:apk-key-hash: followed by the signing certificate’s SHA-256 fingerprint in canonical, unpadded base64url (43 characters). Invalid entries and control characters fail startup.

Bluesky connections are off by default. Enable them in the admin dashboard’s Runtime Integrations panel and supply an ES256 private signing key with a unique key identifier. The public API must serve the client metadata and signing keys.

For environment-based configuration, use FLUXER_AUTH_BLUESKY_ENABLED, FLUXER_AUTH_BLUESKY_KEYS, and the optional FLUXER_AUTH_BLUESKY_CLIENT_NAME, FLUXER_AUTH_BLUESKY_CLIENT_URI, FLUXER_AUTH_BLUESKY_LOGO_URI, FLUXER_AUTH_BLUESKY_TOS_URI, and FLUXER_AUTH_BLUESKY_POLICY_URI settings. Compose forwards every one from .env.

FLUXER_VAPID_PUBLIC_KEY and FLUXER_VAPID_PRIVATE_KEY are required. FLUXER_VAPID_EMAIL is optional.

VariableValue in .env.exampleControls
FLUXER_VAPID_PUBLIC_KEYCHANGE_METhe VAPID public key. Base64url of the 65-byte uncompressed P-256 point
FLUXER_VAPID_PRIVATE_KEYCHANGE_METhe VAPID private key. Base64url of the 32-byte scalar, and the matching half of the pair
FLUXER_VAPID_EMAILunsetThe VAPID contact address. Compose forwards it from .env, with a default of admin@ followed by FLUXER_DOMAIN

FLUXER_GATEWAY_PUSH_ENABLED is read by the Gateway alone and defaults to true. When it is false, the Gateway hands no message or clear notification to push. Compose forwards it from .env.

push reads every name below. api also reads the APNs names, apart from FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT. Compose forwards the APNs names from .env to both, and FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT and the FCM names to push alone. All are optional.

Default false. The APNs switch. Read by api and push. Compose forwards it from .env.

No default. The Apple team. Paired with the key ID. Compose forwards it from .env.

No default. The signing key ID. Paired with the team ID. Compose forwards it from .env.

No default. The signing key in PEM form. Set exactly one of this and the path. Compose forwards it from .env.

No default. A path to the signing key. Must be readable inside the container. Compose forwards it from .env.

Default production. Which APNs environment is used. production or development. Read by push only. Compose forwards it from .env.

Default []. Per-app APNs configuration. JSON array. An entry with no app_id fails startup. Compose forwards it from .env.

Default false. The FCM switch. Read by push only, like every FCM name. Compose forwards it from .env.

No default. The Firebase project. Paired with the client email. Compose forwards it from .env.

No default. The service account address. Paired with the project. Compose forwards it from .env.

No default. The service account key. Set one of this, FLUXER_PUSH_FCM_PRIVATE_KEY_PATH, or FLUXER_PUSH_FCM_SERVICE_ACCOUNT_JSON_PATH. Compose forwards it from .env.

No default. A path to the key. Must be readable inside the container. Compose forwards it from .env.

No default. A path to the whole service account JSON. Must be readable inside the container. Compose forwards it from .env.

Default https://oauth2.googleapis.com/token. The OAuth token endpoint. Change only for a proxy or a test double. Compose forwards it from .env.

Default []. Per-app FCM configuration. JSON array, under the same app_id rule as APNs. Compose forwards it from .env.

push is the push notification service in the bundled stack. It reads the VAPID pair from Web push, the APNs and FCM names from Mobile push, and FLUXER_SVC_NATS_URL with FLUXER_NATS_AUTH_TOKEN from Message bus and internal services. It refuses to start without FLUXER_INTERNAL_API_ENDPOINT and FLUXER_GATEWAY_RPC_AUTH_TOKEN. Compose supplies both. The names below belong to push, and Compose forwards every one from .env except the bind address and port. All are optional.

Default 0.0.0.0. The bind address. It must parse as an IP address. Also settable with --bind-host. Compose does not set it, so the default applies.

Default 8126. The listen port for the health and metrics endpoints. Also settable with --port. Compose does not set it, so the default applies.

Default 10000. Notification jobs push holds at once. Accepts 1 to 1000000. Compose forwards it from .env.

Default 256. Provider requests in flight at once, across every job. Accepts 1 to 65536. Compose forwards it from .env.

No default. Replaces the APNs host in both environments. Set it only for a test double. Compose forwards it from .env.

Default https://fcm.googleapis.com. The FCM host. Set it only for a proxy or a test double. Compose forwards it from .env.

Section titled “FLUXER_PUSH_SERVICE_RELAY_CONSENT_ACCEPTED”

Default false. Accepts the push relay supplemental privacy notice for this push process. true, 1, or yes accepts it, and false, 0, or no leaves the decision to the push relay configuration. Any other value fails startup. When it is true, push sends through the Fluxer-run relay even while the stored consent is off. Compose forwards it from .env.

Default push.fluxer.com. Hostnames, comma separated, of the Fluxer-run push relay. A browser push endpoint on one of them needs the relay consent above, and a refusal from it counts against the relay quota. Compose forwards it from .env.

Default empty. Hostnames, comma separated, of a push relay this instance runs itself. push delivers a browser push endpoint on one of them straight through its own APNs or FCM credentials, with no HTTP hop. Compose forwards it from .env.

Default empty. Hostnames, comma separated, that a push endpoint may use even when they resolve to private, loopback, or other non-public addresses, such as a ntfy server on your own network. push refuses every other such endpoint and logs blocked_address. Compose forwards it from .env.

push also runs as a relay under --mode relay, which the stack does not use. Only that mode reads FLUXER_PUSH_RELAY_MAX_CONCURRENT, default 1024, FLUXER_PUSH_RELAY_MAX_BODY_BYTES, default 2816, FLUXER_PUSH_RELAY_TRUSTED_PROXY_HOPS, default 1, and FLUXER_PUSH_RELAY_SOURCE_BUCKET_ENABLED, default false. Compose does not forward them, and Names the stack has no use for lists them.

Stripe billing for a paid premium tier and gift purchases. All are optional. On a self-hosted instance, set billing in the admin dashboard’s Premium & billing group instead. A value stored there wins over the matching variable below.

A self-hosted instance sells premium only while its premium mode is mirror, billing is switched on, a Stripe secret key is set, and at least one currency has both a monthly and a yearly price. Instance discovery reports this as stripe_enabled. Existing subscribers can still manage, cancel and renew while the premium mode is mirror and a Stripe secret key is set, even after sales are switched off or the prices are removed. Instance discovery reports this as stripe_serviceable. Stripe sends events to https://<FLUXER_DOMAIN>/api/stripe/webhook, which the dashboard shows as the webhook URL to register in Stripe.

Default false. The Stripe switch. Compose forwards it from .env.

Default empty. The Stripe secret key. Compose forwards it from .env.

Default empty. The webhook signing secret. Compose forwards it from .env.

Default {}. Every price ID at once. JSON object. Compose forwards it from .env.

Individual price variables also exist, one per product and currency: FLUXER_STRIPE_PRICE_MONTHLY_, FLUXER_STRIPE_PRICE_YEARLY_, FLUXER_STRIPE_PRICE_GIFT_1_MONTH_ and FLUXER_STRIPE_PRICE_GIFT_1_YEAR_ in USD, EUR, BRL, DKK, INR, ISK, NOK, PLN, SEK, and TRY. Each overrides the matching price in this object. Compose does not forward them, so a self-hosted instance sets prices with this variable or in the admin dashboard.

Prices saved in the dashboard replace this catalogue entirely. They take any three-letter currency code. A buyer gets the currency mapped to their country, then the default currency, then the first configured one. Fluxer’s country checks for localised currencies apply only to prices set by these variables.

A self-hosted instance never sets Stripe Adaptive Pricing on its Checkout Sessions, so the setting in the Stripe Dashboard applies. The hosted service turns it off for SEK, NOK, DKK and ISK sessions so buyers pay the native price.

Default {}. Retired price IDs, keyed by the same slot names FLUXER_STRIPE_PRICES uses, each mapped to a list: {"monthly_brl": ["price_..."]}. JSON object. Compose forwards it from .env. Existing subscriptions on these prices keep renewing. The localised checkout catalogue uses FLUXER_STRIPE_PRICES alone. Legacy prices saved in the dashboard replace this variable, and there the currency in a slot name is upper case, such as monthly_GBP.

Repricing a slot has a fixed order. Doing the steps in another order leaves a price ID unknown to the API, and invoices on that price fail. Create the new price in Stripe, move the ID it replaces into this variable, roll the API, and only then point FLUXER_STRIPE_PRICES at the new price. A price ID that neither variable names is unknown to the API: a renewal invoice on it fails the webhook with Unknown product for invoice renewal, a checkout completing on it fails with Unknown price ID for checkout session, and both keep failing until the ID is registered. The API answers Stripe as soon as the signature verifies and hands the event to worker, so any retry comes from worker rerunning the processStripeWebhook job: the Stripe dashboard shows the delivery as succeeded and the error is in the worker logs. Keep a retired ID listed for as long as any subscription still bills on it, which for a yearly price is at least a year after the switch.

With prices saved in the dashboard, add the retired ID to its legacy prices before you replace the price.

All are optional.

Default false. Upload virus scanning. Compose forwards it from .env, and no ClamAV container ships.

Default 127.0.0.1. The scanner host. Needs a reachable scanner. Compose forwards it from .env.

Default 3310. The scanner port. Integer. Compose forwards it from .env.

Default false. Behaviour when the scanner is unreachable. With scanning on and this off, an unreachable scanner rejects every upload. Compose forwards it from .env.

Defaults to the inverse of FLUXER_SELF_HOSTED. Off by default on a self-hosted instance. With feeds on, the worker downloads the URLhaus and PhishTank URL lists every six hours, and MalwareBazaar file hashes every twelve hours. Fluxer checks posted links against the URLs and uploads against the hashes.

With feeds off, Fluxer checks none of this data. The first worker start with feeds off removes the URL feed file and every malware_bazaar file-SHA ban that no Admin added. File-SHA bans added through the Admin API stay. Turning feeds back on downloads the URLs within six hours and the hashes within twelve. Compose forwards it from .env.

Default 365. Read by worker. The daily report retention job deletes each report this many days after it was filed, pending or resolved, with its evidence copies and search entry, unless a legal hold is in place. Integer 1 to 36500, checked at startup. Compose forwards it from .env.

Default unset, which leaves resolved reports to FLUXER_REPORT_RETENTION_DAYS. Read by worker. When set, a resolved report is deleted this many days after it was resolved, if that comes before the end of its retention period. Pending reports are not affected, and a legal hold still keeps a report. Integer 1 to 36500, checked at startup. Compose forwards it from .env.

Default false, so report retention deletes for real. Read by worker. With true, each run only counts what it would delete and deletes nothing. true or false, checked at startup. Use it to check the counts in the Processed report retention log line before the first real run, because deleted evidence cannot be restored. Compose forwards it from .env.

Defaults to the inverse of FLUXER_SELF_HOSTED. Breached password rejection. Sends the first five characters of the password’s SHA-1 hash to api.pwnedpasswords.com. Off by default on a self-hosted instance. Compose forwards it from .env.

Use the admin dashboard or Admin instance API to change saved settings. Missing settings use their documented defaults. Invalid JSON, field types, identifiers or out-of-range values cause an error instead of silently resetting security or registration policy.

Invalid saved configuration prevents the API and worker from starting. If a running process cannot apply an update, it logs the error and keeps its previous valid settings. Check the reported section and field paths, repair the saved configuration, then retry. It is not repaired automatically.

The API and worker read configuration saved by older releases under these rules:

  • Gateway rollout accepts nats_request_timeout_ms only when rpc_request_timeout_ms is absent. Both require an integer from 1000 to 60000. Use the current name in Admin requests.
  • Registration accepts adminRegistrationUrlsEnabled only when admin_registration_urls_enabled is absent. Missing registration settings default to open with admin registration URLs enabled.
  • Missing SSO settings default to disabled. A missing enforcement flag takes the value of the enabled flag, and automatic provisioning defaults to on. Stored flags require true or false.
  • SSO allowed domains accept a JSON string array or a legacy comma-separated list of at most 100 entries. Domains are trimmed, lowercased, IDNA encoded and deduplicated. An empty list leaves domains unrestricted. Invalid lists must be repaired even while SSO is disabled.
  • A self-hosted instance with no stored sign-in method gets one on its next start, by the rules in Sign-in methods. Until that write succeeds the instance behaves as an email instance.
  • Missing registration URL and pending-registration lists mean empty lists. Invalid records are rejected rather than discarded. Timestamps require an explicit UTC marker or offset, and null is accepted only for nullable fields.

Explicit false values and supported null clears remain valid. Branding and integration strings retain their documented trimming and blank-value behaviour. Media size and lifetime adjustments also remain unchanged. These checks do not make a multi-section update atomic. Read back the result after a failed write before retrying.

No environment variable changes an instance limit. Use the admin dashboard’s Limit Config page or the Admin API. The thread and forum limits are fixed and have no limit key. Thread lifecycle, Thread members and Forum channel fields state them. Effective limits combine saved settings, deployment defaults and premium policy. Only the values you change are saved. Every other limit follows the deployment defaults and the premium model, so switching the model moves the untouched limits and keeps your edits. Reset to Defaults on a rule in the dashboard drops the edits saved for that rule. After a change is saved, every running api and worker process reloads the limits with no restart. Clients receive the limit configuration object.

Request concurrency is separate from instance limits, and each process sets its own. All are optional.

Default 512. The API in-flight ceiling. Integer 1 to 100000, checked at startup. Compose forwards it from .env.

Default 512. Gateway HTTP RPC concurrency. Compose forwards it from .env to gateway.

Default 512. Gateway NATS handler count. Compose forwards it from .env to gateway.

Default false. Turns rate limits off. Read by the API and by the Gateway. The Gateway turns rate limits off only for the values 1, true and TRUE. A test setting, which Compose does not forward.

Default false. Loosens registration rate limits. A test setting, which Compose does not forward.

All are optional.

Defaults to debug in development, info otherwise. The Node log level. Read by api and worker. Compose forwards it from .env.

Default info. The Rust log filter. Read by media-proxy, app-proxy, admin, push, and the internal services. An empty or invalid filter also gives info, and off silences them. Compose forwards it from .env.

Default info. The Gateway log level. An unrecognised level also gives info. Compose forwards it from .env to gateway.

Falls back to FLUXER_GATEWAY_LOGGER_LEVEL. The Gateway log level at runtime. Overrides the prefixed name, and an unrecognised level falls back to it. Compose forwards it from .env to gateway.

Default dev. The reported build. Baked into the images, so Compose does not set it. When BUILD_VERSION is unset or empty outside development, the process prints a warning.

Default stable. The reported channel. Only canary is recognised as non-stable. Read by app-proxy only. It describes the shipped build, so Compose does not set it.

Set by Docker. Node identity in metrics and logs.

Default development. The runtime mode. development, production, or test. Compose pins production, which gates Postgres validation and the admin cookie flags.

Default false. The self-host switch. Compose sets true. It relaxes the production Postgres SSL requirement, seeds the limit tier, gates registration, donation and discovery controllers, keeps premium billing off until it is set up as Payments describes, turns blocklist feeds off, and keeps hosted-only artwork from fluxer.app out of the app, webhook avatars and link embeds.

/_metrics on api, media-proxy, gateway, and push, plus the Gateway’s /_health/ready, /_health/drain, and /_health/undrain, are gated to loopback peers, so no proxy reaches them. The probes that work from outside are /api/_health, /gateway/_health, /media/_health, and the edge’s own /_health.

All are optional.

Default true. The public guild discovery surface. With it off, discovery search, discovery join, and the guild discovery application routes return 400 DISCOVERY_DISABLED. Compose forwards it from .env.

Default 1. Minimum members for discovery eligibility. Integer. Compose forwards it from .env.

Default 336. How long a deleted account is recoverable. Forced to 0.01 hours when FLUXER_TEST_MODE_ENABLED is on. Compose forwards it from .env.

FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED

Section titled “FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED”

Default false. Presigned attachment uploads. Only api reads it. Compose forwards it from .env to api with a default of true.

FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED

Section titled “FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED”

Default true. Presigned harvest downloads. Applies to the harvests bucket. A presigned link points at FLUXER_S3_PUBLIC_ENDPOINT, which in the bundled stack is the internal http://seaweedfs:8333 that no browser reaches. Compose therefore forwards it from .env with a default of false. Turn it on once FLUXER_S3_PUBLIC_ENDPOINT names an address browsers can reach.

Default false. Whether api and worker publish a record of every object-store write and delete to a JetStream stream. Compose forwards it from .env.

Default STORAGE_CHANGES. The JetStream stream the feed publishes to. Letters, digits, underscores or hyphens, checked at startup only while the feed is on. Compose forwards it from .env.

FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS

Section titled “FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS”

Defaults to the uploads bucket. Buckets the feed leaves out, comma separated. none leaves out no bucket. Compose forwards it from .env.

Default empty. Hosts never unfurled. Comma separated, unvalidated. Compose forwards it from .env.

Default false. Test mode. Collapses the deletion grace period. A test setting, which Compose does not forward.

No default. The test harness credential. A test setting, which Compose does not forward.

Defaults to on outside production. Response schema validation. Costs latency when forced on. A development setting, which Compose does not forward.

Default empty. The Klipy GIF provider key. GIF search reports itself unavailable while this and the admin dashboard key are both empty. Also read by unfurl, which accepts KLIPY_API_KEY as a fallback. Compose forwards this name from .env, and not the fallback.

Default empty. The YouTube Data API key. Also read by unfurl, which accepts YOUTUBE_API_KEY as a fallback. Compose forwards this name from .env, and not the fallback.

Default none. How cached copies of deleted or replaced media are purged. none or http. Anything else fails startup. With none, nothing is queued or sent. Compose forwards it from .env.

Default empty. Required when the adapter is http. Must be an absolute http or https URL without credentials, or startup fails. While purges are queued, the worker posts JSON {"exact": [...], "prefix": [...]} here every 10 seconds. Redirects are not followed. Any 2xx marks the batch done. Your endpoint then purges those URLs from every proxy that caches media. A 400 or 422 makes the worker resend each URL alone, and a URL refused alone moves to the cache_purge:rejected key. Any other failure is retried on a later run. Compose forwards it from .env.

Default empty. The bearer token sent in the Authorization header of each purge request. Empty sends no header. When the adapter is http, a token with spaces, line breaks or non-ASCII characters fails startup. Compose forwards it from .env.

Default 10000. Purge request timeout. Accepts 1000 to 10000. The range is checked only when the adapter is http. Compose forwards it from .env.

Default empty. The GeoIP database. A filesystem path, or an s3://bucket/key URL whose download_path query parameter is mandatory and must be absolute. Read by api and worker. Compose forwards it from .env.

Compose forwards every name below from .env. The admin dashboard also sets the product name and the brand assets, and a value saved there wins. All are optional.

A brand asset on another origin than the instance needs no CSP setting. app-proxy adds the origin of each of the five image URLs below to img-src, whether the URL is set here or in the dashboard. Content Security Policy has the timing.

app-proxy on a self-hosted instance also writes the product name, the icon, the favicon and the theme colour into the page it serves, so a browser shows them before the web app starts. The page title and the home screen name use the product name. The home screen icon uses the icon, or the favicon when no icon is set. The browser favicon and the /favicon.ico redirect use the favicon, or the icon when no favicon is set. The install manifest at /manifest.json takes the product name, the theme colour and the icon, and leaves out the Fluxer description. /browserconfig.xml, the Fluxer tile for legacy Edge, returns 404. app-proxy takes these values the same way as the image origins, from this environment at start and then from the dashboard.

Default Fluxer. The product name clients display and the API writes in its emails and report flows. Also settable in the admin dashboard, which wins. Compose forwards it from .env.

Default empty. The client icon. Its origin is added to the CSP by app-proxy. Compose forwards it from .env.

Default empty. The symbol mark. Its origin is added to the CSP by app-proxy. Compose forwards it from .env.

Default empty. The logo. Its origin is added to the CSP by app-proxy. Compose forwards it from .env.

Default empty. The wordmark. Its origin is added to the CSP by app-proxy. Compose forwards it from .env.

Default empty. The favicon. Its origin is added to the CSP by app-proxy. Compose forwards it from .env.

Default empty. The theme colour, as a hex value such as #4641d9. The web app uses it as its brand colour and as the browser theme colour. A value that is not #rgb or #rrggbb is ignored, and so is pure black. Compose forwards it from .env.

Default empty. The public status page. The web app links to it when loading stalls, and polls its Instatus summary.json and components.json for incidents and maintenance. Empty removes the link and stops the polling. app-proxy does not add this origin to the CSP, so a status page off fluxerstatus.com also needs it in FLUXER_CSP_EXTRA_CONNECT_SRC. Compose forwards it from .env.

FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL

Section titled “FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL”

Default empty. The incident history page of the status page. Compose forwards it from .env.

Default false. Whether setup is marked complete. The stored app_public_config row wins whenever its setup.configured is a boolean. This variable supplies the default only while no such row exists, and only on a self-hosted instance. Off a self-hosted instance the state is always true whatever this says. Compose forwards it from .env.

Default empty, which means email when SMTP is set up and username otherwise. How members sign in on a new self-hosted instance, username or email. Case is ignored, and any other value fails startup. The API reads it once, on the first start of an instance with no stored sign-in method, and stores the result. An instance that is already in use is stored as email whatever this says, by the rules in Instances that already exist. The setup wizard can change the stored method until setup is complete or the first account exists, and after that nothing changes it. Off a self-hosted instance the method is always email. Compose forwards it from .env. See Sign-in methods.

Default empty, which means none. How usernames are tagged on a new self-hosted email instance. none gives unique usernames with no tag, and random gives every account a random tag. A username instance always uses none and ignores this value. Case is ignored, and any other value fails startup. The API reads it once, together with FLUXER_ACCOUNT_IDENTITY, on the first start of an instance with no stored sign-in method. An instance that is already in use is stored as random whatever this says. The setup wizard can change it until setup is complete or the first account exists. Compose forwards it from .env. See Sign-in methods.

Default empty. An invite every new account joins. Must be a live invite code. Compose forwards it from .env.

Default empty. The guild that grants the visionary role. Snowflake. Compose forwards it from .env.

FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID

Section titled “FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID”

Default empty. The role granted there. Snowflake. Compose forwards it from .env.

Completing setup saves that state independently of the environment default. Later branding or legal updates preserve it. Setting FLUXER_INSTANCE_SETUP_CONFIGURED back to false does not reopen the wizard or restore the unauthenticated setup access and first-registration admin grant described in Get started.

FLUXER_API_WORKER_TASK is required under single_task. The rest are optional.

Default 8080. The API listen port. Compose sets 8080, which the edge proxies to.

Default 30000. How long a client may take to send the request line and the headers. Integer 1000 to 3600000, checked at startup. It is clamped down to FLUXER_API_REQUEST_TIMEOUT_MS, so raising it alone does nothing. Compose forwards it from .env.

Default 120000. How long a client may take over the whole request. Integer 1000 to 3600000, checked at startup. This is the one to raise for large uploads or high latency links. Compose forwards it from .env.

Default all_lanes. Which lanes the worker runs. all_lanes, single_lane, or single_task. Anything else fails startup. Compose sets all_lanes, because the stack runs one worker.

No default. Which lane, under single_lane. realtime, unfurl, lifecycle, batch, or crosspost. Compose does not forward it. A deployment that splits the worker by lane needs a crosspost worker, or published announcements are never delivered.

No default. Which task, under single_task. Must name a known task. Compose does not forward it.

No default. Whether the cron scheduler runs. Compose sets true. Without it no scheduled job runs anywhere.

FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES

Section titled “FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES”

No default. Per-lane concurrency. JSON object keyed by lane, realtime, unfurl, lifecycle, batch, or crosspost. Each value must be an integer of at least 1 or startup fails. Built-in concurrency is realtime 10, unfurl 20, lifecycle 8, batch 12, crosspost 8. Compose forwards it from .env.

Default /etc/ssl/certs/ca-certificates.crt, the system bundle in the image. A PEM file of extra CA certificates that api and worker trust for every outbound TLS connection, such as SMTP, object storage, search and webhooks. Node trusts its own built-in roots plus this one file. A file you set replaces the system bundle, so append your CA to a copy of that bundle, mount it into both containers and point this at it. Compose passes it to api and worker as NODE_EXTRA_CA_CERTS, and keeps the image path when .env leaves it out. It reads the prefixed name so a NODE_EXTRA_CA_CERTS in the host shell does not reach the containers.

The Media Proxy takes uploads, transforms images, and serves media back. All are optional.

Default mp. Which routes are served. mp, static, upload, or relay. relay serves the upload relay and the operator and internal endpoints, and returns 404 for every read. Anything else is a startup error. Also settable with --mode. Compose sets upload, which the upload relay needs.

Default 0.0.0.0. The bind address. Also settable with --bind-host. Compose does not set it, so the default applies.

Default 8080. The listen port. Also settable with --port. Compose sets 8080, which the edge proxies to.

Default false. Refuses writes. --read-only can force it on. Compose forwards it from .env.

Default local. Where objects live. local or s3. Compose sets s3, the bundled object store.

Default ./media_proxy_storage. The local storage directory. Used only by the local backend, so Compose does not forward it.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES”

Default 524288000. The upload size cap on both sides. Rust accepts 1 byte to 5 GiB and refuses to start when the value is above FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES. The effective cap is the smaller of the token’s value and this. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS”

Default 900. Relay token lifetime. Read by the API alone. Compose forwards it from .env.

Default http://localhost:8088/media. The relay URL handed to clients. Node side only. A trailing slash and a trailing /v1/relay are stripped. Compose forwards it from .env, and builds it from the public origin and /media when it is unset.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES”

Default empty. Countries that upload directly to S3. Node side only. Empty means every client uses the relay, which is what keeps the internal S3 address out of browsers. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS”

Default 900000. Relay upload timeout. Accepts 1000 to 3600000. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES”

Default 33554432, 32 MiB. The largest declared upload the relay holds in memory so it can retry the S3 write. Accepts 0 to 268435456. A larger upload is streamed to S3 and gets no retry. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES”

Default 536870912, 512 MiB. The memory all buffered relay uploads may hold at once. Accepts 0 to 8589934592. An upload that would pass it is streamed instead. The default equals the 512mb that FLUXER_MEDIA_PROXY_MEMORY_LIMIT gives media-proxy, so lower this or raise that limit when many uploads arrive at once. Compose forwards it from .env.

Defaults to the system temporary directory. Where relay bodies spool, and where media-proxy copies a stored audio or video object above 500 MiB when it has to read inside the file. That happens when the API asks for the media metadata or a frame of an upload or a stored object, and when an attachment read asks for a frame of a video. Must be writable. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES”

Default 1048576. Spool chunk size. Accepts 64 KiB to 64 MiB. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES

Section titled “FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES”

Default 8589934592. Total spool ceiling, shared by relay bodies and the copies of stored audio and video above 500 MiB. A relay upload that does not fit beside the copies in progress fails. An upload larger than the ceiling is stored as a plain file without media metadata, and a stored video larger than the ceiling has no poster frame. Accepts 0 to 256 GiB, and must be at or above FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES, which puts the usable floor at 500 MiB by default. Compose forwards it from .env.

Sending an audio or video attachment above 500 MiB holds up to three copies of it on local disk at once. media-proxy holds one in the spool directory, and api holds the download and the copy it rewrites without metadata in its temporary directory. Leave free space of three times the attachment size limit for each send you expect at the same moment.

api waits 30 seconds for the metadata of an upload up to 500 MiB. Above that it waits 30 seconds plus one second for each 16 MiB of the upload, about 94 seconds for 1 GiB and 542 seconds for 8 GiB. In that time media-proxy copies the upload out of object storage, reads it, and hashes it. A deployment that cannot sustain 16 MiB per second through those steps stores the upload as a plain file.

Defaults to available parallelism, clamped to 2 to 8. Concurrent image transforms. Accepts 1 to 128. Compose forwards it from .env.

Defaults to eight times the transform limit. Transform queue depth. Accepts 1 to 8192. Compose forwards it from .env.

Default 268435456. Transform cache size. Accepts 0 to 4 GiB. Compose forwards it from .env.

FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES

Section titled “FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES”

Default 67108864. Largest cacheable result. Accepts 0 to 512 MiB. Compose forwards it from .env.

Default 120000. Transform cache lifetime. Accepts 0 to 3600000. Compose forwards it from .env.

Default 30000. Socket read and write timeout. Accepts 0 to 300000. Compose forwards it from .env.

Default 30000. Milliseconds allowed to finish requests and media transforms after SIGTERM or Ctrl-C. Accepts 0 to 300000. Exceeding the deadline exits the process with an error. Compose forwards it from .env.

Default 15000. Per-transform timeout. Accepts 1000 to 120000. Compose forwards it from .env.

Default 20000. Animation frame ceiling. Accepts 1 to 100000. Compose forwards it from .env.

Default 30000. Animation duration ceiling. Accepts 100 to 600000. Compose forwards it from .env.

Default empty. An external NSFW classifier. No such service ships with the stack. Compose forwards it from .env.

Default 0.85. The media-side NSFW cutoff. Accepts 0.0 to 1.0 and finite. Compose forwards it from .env.

Default off. The cross-origin read policy. off, report, or enforce. Anything else is a startup error. report logs each read whose origin would be refused and changes no response. enforce refuses those reads with 403 and sets Access-Control-Allow-Origin to the request origin. When FLUXER_MEDIA_PROXY_MODE is static or relay, the Media Proxy ignores it, so a bad value is no startup error. Compose forwards it from .env.

Default empty. Comma-separated origins that may read media through CORS, such as https://web.fluxer.app. Spaces around an entry and empty entries are ignored. Each entry is parsed as a URL and must be http or https with no credentials, query, fragment, or path beyond /. Its host must be an IPv4 address, a bracketed IPv6 address, or a domain name whose labels are ASCII letters, digits, and hyphens after parsing. So a wildcard such as https://*.fluxer.app, a brace pattern, an underscore, a trailing dot on a domain name, or any non-ASCII character fails. Write an internationalised domain name in its punycode form. The one exception is the desktop app origin fluxer-app://app. It is accepted as an entry and is always allowed without one. Any failing entry makes the process exit. An entry is compared in the form a browser sends. https://Web.Fluxer.App:443/ matches https://web.fluxer.app. Required when FLUXER_MEDIA_PROXY_CORS_MODE is report or enforce. When the policy is report or enforce, the Media Proxy logs the parsed list once at startup. When FLUXER_MEDIA_PROXY_MODE is static or relay, the Media Proxy ignores it.

Compose forwards it from .env, and defaults it to the public origin, built from FLUXER_PUBLIC_ORIGIN, or from FLUXER_PUBLIC_SCHEME, FLUXER_DOMAIN, and FLUXER_PUBLIC_PORT. That origin is the only entry, so the web app the instance serves reads its own media. The hosted web client and the desktop app run on https://web.fluxer.app. To let them read this instance’s media through CORS, set the list in .env to the public origin and https://web.fluxer.app, such as https://chat.example.com,https://web.fluxer.app. A value set in .env replaces the Compose value, so it must name the public origin too.

FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE

Section titled “FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE”

Default off. The signed attachment URL policy. off, report, or enforce. An empty value means off, and anything else is a startup error. report logs each attachment read enforce would refuse and serves it. enforce refuses those reads with 404. Under both, a read whose signature is valid gets a Cache-Control that ends with the signature instead of the usual year. report and enforce require FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64, and the Media Proxy logs the mode and the number of secrets once at startup. When FLUXER_MEDIA_PROXY_MODE is static or relay, the Media Proxy ignores it and the secrets, so a bad value is no startup error. A self-hosted instance leaves it off, and signs nothing, until an operator turns it on. Compose forwards it from .env.

This variable is the whole switch, and moving between the three states needs nothing else changed. The Media Proxy reads it once at start, so apply a change with docker compose up -d media-proxy, which replaces the container. docker compose restart media-proxy reuses the old environment. A cache or proxy in front of the instance has a switch of its own, and Turning the signature policy on gives the order to move the two in and what each pairing serves.

The Gateway is the WebSocket service clients hold open for live events. All are optional.

Default 8771. The listen port. Compose sets 8080, which the edge proxies to.

Default all. Which subsystems this node runs. websocket, sessions, presence, guilds, calls, push, or all. An unrecognised value also becomes all. Compose does not forward it, because the stack runs one Gateway node.

Default http://localhost:8088/media. The public media URL used in payloads. Compose forwards it from .env, and uses FLUXER_MEDIA_ENDPOINT or the public media URL when it is unset.

Default http://localhost:8088. The public static origin used in payloads. Compose forwards it from .env, and uses FLUXER_STATIC_CDN_ENDPOINT or the public origin when it is unset.

FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES

Section titled “FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES”

Default 128. Presence push buffer depth. Compose forwards it from .env.

FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES

Section titled “FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES”

Default 1048576. Presence push buffer size. Compose forwards it from .env.

FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED

Section titled “FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED”

Default true. Whether the Gateway asks push to clear a channel’s notifications once the channel is read. Compose forwards it from .env.

FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS

Section titled “FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS”

Default 100000. How long the Gateway waits on one push outbox request. Milliseconds. Compose forwards it from .env.

Default 6. Failures before the API circuit opens. Integer. Compose forwards it from .env.

Default 15000. How long the circuit stays open. Milliseconds. Compose forwards it from .env.

Default false. BEAM clustering. Single-node by default. Compose forwards none of the clustering names, because the stack runs one Gateway node.

No default. The DNS name peers are discovered through. Needs clustering on.

FLUXER_GATEWAY_CLUSTER_DISCOVERY_NODE_BASENAME

Section titled “FLUXER_GATEWAY_CLUSTER_DISCOVERY_NODE_BASENAME”

No default. The node basename for discovery. Needs clustering on.

FLUXER_GATEWAY_CLUSTER_DISCOVERY_POLL_INTERVAL_MS

Section titled “FLUXER_GATEWAY_CLUSTER_DISCOVERY_POLL_INTERVAL_MS”

Default 5000. Discovery poll interval. Milliseconds.

Default empty. A fixed peer list. Comma separated Erlang node names, capped at 256. An entry that is not a node name fails startup.

Default [email protected]. The BEAM node name. Must be resolvable by peers when clustering. Compose does not forward it.

No default. The BEAM distribution secret. The Gateway refuses to start without it, and Compose refuses to start the stack when .env lacks it. Anyone who reaches the distribution port with the value gets code execution, so keep the port unpublished.

Default 8081. The BEAM distribution port. Not published or forwarded by Compose. Never expose it.

Defaults to the container CPU count, clamped to 2 through 16. Normal scheduler count. Positive integer, passed to the BEAM as +S N:N. A value that is not a positive integer is ignored and the derivation runs instead. Compose forwards it from .env to gateway.

Defaults to two thirds of the scheduler count, rounded up. Dirty CPU scheduler count. Positive integer, passed as +SDcpu N:N. Same fallback rule as FLUXER_ERLANG_SCHEDULERS. Compose forwards it from .env to gateway.

Default 2. The floor of the scheduler clamp. Compose forwards it from .env to gateway.

Default 16. The ceiling of the scheduler clamp. Compose forwards it from .env to gateway, and .env.example ships both names commented out.

The Gateway entrypoint derives the scheduler counts before the BEAM starts. It reads the container CPU quota, clamps the result between FLUXER_ERLANG_SCHEDULERS_MIN and FLUXER_ERLANG_SCHEDULERS_MAX, exports the answer as FLUXER_ERLANG_SCHEDULERS, and derives FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS from it. vm.args.src then substitutes both into +S and +SDcpu.

The clamp bounds reach the Gateway from .env. To pin the count, set FLUXER_ERLANG_SCHEDULERS in .env, which skips the clamp.

The Gateway protocol version is 1. The Gateway answers any other value in ?v= with 101, then a close frame reading Invalid API version. Gateway overview has the close code.

The Gateway reads every FLUXER_GATEWAY_ name straight from the environment. Compose forwards each one a single-node stack can use. The role, the clustering names and FLUXER_GATEWAY_API_RPC_ENDPOINT need a Compose override file.

app-proxy serves the web client. All are optional.

Default 0.0.0.0. The bind address. The image sets the same value, and Compose does not set it.

Default 8080. The listen port. Compose sets 8080, which the edge proxies to.

Default ./static. Where the client’s SPA bundle lives, unrelated to static-proxy. A path inside the image, so Compose does not forward it.

No default. An upstream to fetch index.html from. Leave unset for the shipped image. Compose forwards it from .env.

Default http://localhost:8088/api/.well-known/fluxer. Where the self-hosted image fetches the discovery document, which it reads only for the branding image URLs. Compose sets http://edge:8088/.well-known/fluxer. That internal listener supplies the client-IP header the API requires, so leave it pointed at the edge.

Default 60000. How often discovery is refetched, in milliseconds, and so the longest a branding image saved in the admin dashboard waits for its origin to reach the CSP. A value under 1000 counts as 1000. A failed fetch keeps the previous origins and is retried with a backoff of up to five minutes. Unprefixed name. Compose forwards it from .env.

Default empty. Hostnames, comma separated, without a scheme, port or path. When a request’s Host is in the list, the page bootstrap points the web app and its API at that host, as https://<host> and https://<host>/api. Every other request gets the discovery values unchanged. Set it when the web app is served on more than one hostname and each hostname routes /api to the API. Invalid entries are logged and dropped. Compose forwards it from .env.

FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS

Section titled “FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS”

Default empty. HTTPS origins, comma separated, without a path, query or credentials. app-proxy writes each one into the scope_extensions list of the web app manifest, so an installed web app keeps other origins inside its window. Empty leaves the list empty. Each listed origin must serve /.well-known/web-app-origin-association naming this web app, or browsers ignore the entry. Invalid entries are logged and dropped. Compose forwards it from .env.

admin serves the dashboard at FLUXER_ADMIN_BASE_PATH, /admin by default. All are optional.

Default 0.0.0.0. The bind address. Compose does not set it, so the default applies.

Default 3020. The listen port. The image sets 8080 and Compose sets 8080, which the edge proxies to.

Default empty. The path the dashboard is mounted under. Write it with a leading slash and no trailing slash, such as /panel. The edge and the Compose-built FLUXER_ADMIN_ENDPOINT use the value as written, and only admin itself normalises it. The service serves at root and re-prefixes every emitted URL with this. Read by admin and the edge. Compose forwards it from .env with a default of /admin, hands the same value to the edge, which routes that path to admin, and builds the default FLUXER_ADMIN_ENDPOINT from it. A change moves the admin OAuth redirect with it, and takes docker compose up -d.

Defaults to the admin endpoint plus /oauth2_callback. The OAuth2 redirect. Must equal what the API derives from FLUXER_ADMIN_ENDPOINT, so Compose does not set it. The API does not read this name.

Default 1234567890123456789. The OAuth2 client id of admin. Must equal the admin application id built into the API, so Compose does not set it.

Defaults to the crate version. The reported build of admin and app-proxy. BUILD_VERSION is preferred over it. Compose does not set it.

app-proxy builds the policy for the client HTML and the assets it serves. The client HTML policy allows each inline script by its SHA-256 hash, so every visitor to a host gets the same document and header. Each variable appends sources to one directive on top of the built-in ones. All are empty by default, and Compose forwards every one.

Every name below goes in .env. app-proxy reads its environment at container start, so a change takes effect on docker compose up -d app-proxy. A docker compose restart app-proxy does not apply it.

FLUXER_CSP_EXTRA_DEFAULT_SRC, FLUXER_CSP_EXTRA_CONNECT_SRC, FLUXER_CSP_EXTRA_IMG_SRC, FLUXER_CSP_EXTRA_MEDIA_SRC, FLUXER_CSP_EXTRA_FONT_SRC, FLUXER_CSP_EXTRA_SCRIPT_SRC, FLUXER_CSP_EXTRA_STYLE_SRC, FLUXER_CSP_EXTRA_FRAME_SRC, FLUXER_CSP_EXTRA_WORKER_SRC, and FLUXER_CSP_EXTRA_MANIFEST_SRC take one or more sources separated by commas, spaces, tabs, or newlines. Blank entries and sources the directive already lists are dropped. FLUXER_CSP_REPORT_URI sets a single report-uri value.

object-src, base-uri, and frame-ancestors are fixed and have no override. app-proxy adds the static CDN endpoint and the media endpoint from its environment. The self-hosted image also adds the origin of each branding image to img-src. At start it takes them from FLUXER_APP_ICON_URL, FLUXER_APP_SYMBOL_URL, FLUXER_APP_LOGO_URL, FLUXER_APP_WORDMARK_URL and FLUXER_APP_FAVICON_URL. It then polls the discovery document every DISCOVERY_REFRESH_INTERVAL_MS, where a value saved in the admin dashboard wins, so a dashboard change reaches the header within a minute by default and needs no restart. The header is the same for every visitor. A stack on one hostname therefore needs no extra sources, and neither does a logo or favicon on another domain. The usual reason to set one is a voice server on another hostname, which needs its WebSocket origin in FLUXER_CSP_EXTRA_CONNECT_SRC. Voice media does not use the proxy has that line in place.

A front proxy must not add a Content-Security-Policy of its own.

Keys in .env.example that no service reads

Section titled “Keys in .env.example that no service reads”

Docker Compose or the edge container consumes most of the names below, and each of those reaches a service under a different name. Setting one outside Compose, in Kubernetes or a systemd unit, does nothing.

Interpolated into FLUXER_BASE_DOMAIN and into the derived URL strings.

Read by Docker Compose to select the proxy overlay.

Read by the edge container only, and only in the bundled layout. Both overlays overwrite it, docker-compose.proxy.yml with :8080 and tunnel.compose.yml with :80.

Read by Docker Compose as the default for FLUXER_EDGE_SITE_ADDRESS when that name is unset.

FLUXER_EDGE_TRUSTED_PROXIES and FLUXER_EDGE_ENCODE

Section titled “FLUXER_EDGE_TRUSTED_PROXIES and FLUXER_EDGE_ENCODE”

Read by the edge container only. They take effect on docker compose up -d edge.

Used as the host side of the overlay’s port mapping.

Used as the host side of the edge’s published ports.

FLUXER_REGISTRY_OWNER, FLUXER_REGISTRY and FLUXER_IMAGE_TAG

Section titled “FLUXER_REGISTRY_OWNER, FLUXER_REGISTRY and FLUXER_IMAGE_TAG”

Image name selection.

Image name selection for the bundled services, listed under Images.

Becomes FLUXER_POSTGRES_PASSWORD and the Postgres image’s own password.

Becomes FLUXER_SEARCH_API_KEY and the Meilisearch image’s own key.

FLUXER_S3_ACCESS_KEY and FLUXER_S3_SECRET_KEY

Section titled “FLUXER_S3_ACCESS_KEY and FLUXER_S3_SECRET_KEY”

Become FLUXER_S3_ACCESS_KEY_ID and FLUXER_S3_SECRET_ACCESS_KEY, and the identity seaweedfs-init installs.

Become FLUXER_LIVEKIT_API_KEY and FLUXER_LIVEKIT_API_SECRET, and LiveKit’s own LIVEKIT_KEYS.

FLUXER_NODE_EXTRA_CA_CERTS becomes NODE_EXTRA_CA_CERTS in api and worker.

FLUXER_LIVEKIT_LOG_LEVEL, FLUXER_LIVEKIT_TCP_PORT, FLUXER_LIVEKIT_UDP_PORT, FLUXER_LIVEKIT_USE_EXTERNAL_IP, FLUXER_LIVEKIT_NODE_IP, FLUXER_LIVEKIT_STUN_PRIMARY and FLUXER_LIVEKIT_STUN_SECONDARY are written into LIVEKIT_CONFIG, and the two ports also into the published port mappings.

FLUXER_API_POSTGRES_MAX_CONNECTIONS, FLUXER_WORKER_POSTGRES_MAX_CONNECTIONS, FLUXER_USERS_SHARD_POSTGRES_MAX_CONNECTIONS and FLUXER_MESSAGES_SHARD_POSTGRES_MAX_CONNECTIONS each become FLUXER_POSTGRES_MAX_CONNECTIONS in their own service.

Memory, tuning, health checks and restarts

Section titled “Memory, tuning, health checks and restarts”

The names under Resources and Health checks and restarts are read by Docker Compose, which hands them to the engine or to the bundled services under their own names.

Every other name on this page takes effect from .env, except the names Compose fills from another name, listed first below. The other names below stay out of it for the reason given. Add one through a Compose override file that puts it in the relevant service’s environment, which Keep a local compose change describes.

Compose writes each of these from the .env name given, so setting the name itself in .env has no effect. FLUXER_POSTGRES_PASSWORD comes from POSTGRES_PASSWORD. FLUXER_POSTGRES_MAX_CONNECTIONS comes from FLUXER_API_POSTGRES_MAX_CONNECTIONS, FLUXER_WORKER_POSTGRES_MAX_CONNECTIONS, FLUXER_USERS_SHARD_POSTGRES_MAX_CONNECTIONS or FLUXER_MESSAGES_SHARD_POSTGRES_MAX_CONNECTIONS, one per service. FLUXER_S3_ACCESS_KEY_ID comes from FLUXER_S3_ACCESS_KEY, and FLUXER_S3_SECRET_ACCESS_KEY from FLUXER_S3_SECRET_KEY. FLUXER_SEARCH_API_KEY comes from MEILI_MASTER_KEY. FLUXER_LIVEKIT_API_KEY comes from LIVEKIT_API_KEY, and FLUXER_LIVEKIT_API_SECRET from LIVEKIT_API_SECRET. FLUXER_BASE_DOMAIN comes from FLUXER_DOMAIN.

FLUXER_ENV, FLUXER_SELF_HOSTED, FLUXER_DATABASE_BACKEND, the listen ports FLUXER_API_PORT, FLUXER_GATEWAY_PORT, FLUXER_MEDIA_PROXY_PORT, FLUXER_APP_PROXY_PORT and FLUXER_ADMIN_PORT, the internal endpoints FLUXER_INTERNAL_API_ENDPOINT, FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT and FLUXER_MEDIA_PROXY_ENDPOINT, the FLUXER_API_ENDPOINT of admin, FLUXER_SVC_NAME, FLUXER_SVC_MODE, FLUXER_SVC_SHARD_ID, FLUXER_SVC_SHARD_COUNT, FLUXER_API_WORKER_MODE, FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER, FLUXER_MEDIA_PROXY_MODE and FLUXER_MEDIA_PROXY_STORAGE_BACKEND. Compose sets each to a fixed value that the edge, the health checks or another service depend on. A different value gives no working stack.

FLUXER_MEDIA_PROXY_HOST, FLUXER_APP_PROXY_HOST, FLUXER_ADMIN_HOST and FLUXER_PUSH_SERVICE_HOST, whose default already listens on every interface. FLUXER_PUSH_SERVICE_PORT, whose default the health check of push reads. FLUXER_SVC_LISTEN_HOST and FLUXER_SVC_PORT, which the health checks address. FLUXER_API_WORKER_LANE and FLUXER_API_WORKER_TASK, which only the single-lane worker modes read. FLUXER_MEDIA_PROXY_STORAGE_ROOT, which only the local backend reads. FLUXER_S3_BUCKET_STATIC, which only the static mode of media-proxy reads. FLUXER_GATEWAY_ROLE, the FLUXER_GATEWAY_CLUSTER_ names, FLUXER_ERLANG_NODE_NAME and FLUXER_ERLANG_DIST_PORT, since the stack runs one Gateway node. FLUXER_GATEWAY_API_RPC_ENDPOINT, which the Gateway derives from the internal API address. FLUXER_SNOWFLAKE_SERVICE_SUBJECT, FLUXER_USERS_SERVICE_SUBJECT and FLUXER_GIF_SERVICE_SUBJECT, which have to match the subjects the internal services listen on. FLUXER_ADMIN_OAUTH_REDIRECT_URI, which has to match what the API derives. FLUXER_ADMIN_OAUTH_CLIENT_ID, which has to match the admin application id built into the API. The relay-mode names of push, FLUXER_PUSH_RELAY_MAX_CONCURRENT, FLUXER_PUSH_RELAY_MAX_BODY_BYTES, FLUXER_PUSH_RELAY_TRUSTED_PROXY_HOPS, FLUXER_PUSH_RELAY_SOURCE_BUCKET_ENABLED and the _ENTRIES, _PER_MINUTE and _BURST names under FLUXER_PUSH_RELAY_TOKEN_BUCKET and FLUXER_PUSH_RELAY_SOURCE_BUCKET, since the stack runs push in delivery mode. FLUXER_STATIC_DIR, a path inside the image. FLUXER_DOCS_ENDPOINT, which only the docs server reads.

LD_PRELOAD and MALLOC_CONF in media-proxy and app-proxy, and VIPS_CONCURRENCY, VIPS_DISC_THRESHOLD and G_MESSAGES_DEBUG in media-proxy. Each image sets them. LD_PRELOAD loads jemalloc and MALLOC_CONF tunes it. VIPS_CONCURRENCY stays at 1 on purpose, and FLUXER_MEDIA_PROXY_MAX_NATIVE_TRANSFORMS is the setting for image work in parallel. VIPS_DISC_THRESHOLD, 1g in the image, is the image size above which libvips decodes to disk instead of memory. Lower it when a small FLUXER_MEDIA_PROXY_MEMORY_LIMIT runs out of memory.

BUILD_VERSION, FLUXER_BUILD_VERSION and RELEASE_CHANNEL describe the shipped build. POD_NAME and HOSTNAME come from the platform.

FLUXER_TEST_MODE_ENABLED, FLUXER_TEST_HARNESS_TOKEN, FLUXER_DISABLE_RATE_LIMITS, FLUXER_VALIDATE_RESPONSES and FLUXER_RELAX_REGISTRATION_RATE_LIMITS. None of them belongs on a production instance.

Every FLUXER_CASSANDRA_ name. The stack runs Postgres.

FLUXER_NATS_CORE_URL, KLIPY_API_KEY and YOUTUBE_API_KEY. A service reads each only when the name it replaces is unset or empty, and Compose forwards that name instead.

The FLUXER_STRIPE_PRICE_MONTHLY_, FLUXER_STRIPE_PRICE_YEARLY_, FLUXER_STRIPE_PRICE_GIFT_1_MONTH_ and FLUXER_STRIPE_PRICE_GIFT_1_YEAR_ names, one per currency. FLUXER_STRIPE_PRICES holds the same prices in one JSON object, and the admin dashboard can replace them.

The admin dashboard at FLUXER_ADMIN_BASE_PATH stores these in the database. They apply without recreating containers, and a value set here wins over the matching environment variable.

Product name, client-visible brand assets, the terms, privacy, and community guidelines links, and the setup state in the instance discovery document. No environment variable sets a legal link. On a self-hosted instance, the enforcement emails list only the terms and guidelines links set here, and the clients and report flows show no guidelines link until one is set. Its emails and its IP block errors name no contact address. They tell the reader to contact the administrators of the instance, and no setting adds an address. The emails, the report flows, the IP block errors, the title of an APNs alert that has none, and the built-in admin application use the product name set here, and a rename applies without a restart.

Registration mode of open, approval, or closed, admin-issued registration URLs, and pending approval requests.

The ALTCHA proof-of-work check on sign-up, login, password recovery, and a few other gated operations. It is on by default. Turn it off here, or change its cost and maximum counter.

Single-community mode, direct messages and friends, the premium model, and optional embed services.

The Klipy GIF key, the YouTube Data API key, email delivery with an SMTP connection test, and Bluesky OAuth. Email delivery and its test show only on an email instance.

The premium tier name and information link, the billing switch, the Stripe secret key and webhook secret, the webhook URL to register in Stripe, and the price catalogue with its default currency, country currencies and legacy prices. See Payments.

Size-based attachment lifetimes. The built-in default is on.

Turning expiry off hides every expiry date at once and stops deletion. The stored dates are kept.

On a self-hosted instance, changes also apply to attachments that were uploaded earlier. Turning expiry on, or changing a size or lifetime rule, starts a background job that works through existing messages in chunks and recalculates the expiry of their attachments. No attachment is deleted while that job runs. An attachment whose date passed while expiry was off, or that the new settings would expire sooner, is kept for at least the renew window first. Until the job reaches a message, its attachments show the state they had before, so one whose date passed while expiry was off shows as expired until then.

The official hosted instance does not run that job. There, a change applies to attachments uploaded from then on, and deletion of expired attachments is never paused.

Instance Config, Gateway Rollout Configuration

Section titled “Instance Config, Gateway Rollout Configuration”

Session and guild rollout percentages, RPC timeouts, and Gateway concurrency.

OIDC-style SSO for the client and admin apps, and whether SSO is enforced.

The instance limits published to clients.

The voice regions offered and the servers behind them.

Credentials for the Admin API.

Changes propagate without a restart. A process that cannot apply an update logs the error and keeps its previous valid settings.

These settings exist only in the dashboard: attachment decay, which defaults to on, and the inactivity deletion threshold, which defaults to 365 days.

The stack runs its containers on one Docker bridge network, which is private to the stack.

ServiceImageWhat it does
edgecaddy:2.11-alpineTLS and path routing, the only HTTP entry point
app-proxyfluxer-app-proxy-self-hostedServes the web client and builds its CSP header
static-proxyfluxer-staticServes the static asset bundle
apifluxer-apiThe HTTP API
workerfluxer-apiBackground lanes and the cron scheduler
gatewayfluxer-gatewayThe Gateway WebSocket
media-proxyfluxer-media-proxyUploads, transforms, and media delivery
pushfluxer-pushPush notification delivery
adminfluxer-adminThe admin dashboard
snowflakes, snowflakes-shardfluxer-snowflakesIdentifier allocation
users, users-shardfluxer-usersUser reads and writes
messages, messages-shardfluxer-messagesMessage reads and writes
gifs, gifs-shardfluxer-gifsGIF provider access
unfurl, unfurl-shardfluxer-unfurlLink unfurling
postgrespostgres:16-alpineThe database
valkeyvalkey/valkey:9.1-alpineThe key-value store and pub/sub bus, and the deletion queues
natsnats:2.14-alpineCore messaging, and the JetStream streams holding queued background jobs
meilisearchgetmeili/meilisearch:v1.53The search index
seaweedfschrislusf/seaweedfs:4.47S3-compatible object storage
seaweedfs-initchrislusf/seaweedfs:4.47Creates the buckets and the S3 identity, then exits
livekitlivekit/livekit-server:v1.12.0Voice and video

The third-party images above are the defaults of the names under Images.

The edge and LiveKit are the only services that publish ports. The edge publishes 80/tcp, 443/tcp, 443/udp, or one plain-HTTP port under the overlay. LiveKit publishes 7881/tcp and 7882/udp. Everything else is reachable only over the bridge network.

Most settings reach api, worker, gateway, media-proxy, push, admin and the internal services through one shared environment block, so one line in .env reaches every one of them that reads it. app-proxy takes its own list, since it reads the static, media and S3 upload origins, the branding image URLs and its own names. The edge takes the FLUXER_EDGE_ variables and FLUXER_ADMIN_BASE_PATH. The third-party services take their image name, their memory limit, and their own tuning: postgres the password, database, role and FLUXER_POSTGRES_ server settings, meilisearch the master key and FLUXER_MEILISEARCH_ values, valkey the FLUXER_VALKEY_ values, seaweedfs its heap ceiling and telemetry switch, and livekit the key pair, the port, STUN and log level variables. static-proxy reads no environment variables.

The internal services each run a router, which takes requests and holds no state, and one shard, which holds the caches and the database connections. The shipped stack fixes FLUXER_SVC_SHARD_COUNT at 1.

Every service has a memory limit and four also have a memory reservation, all under deploy.resources. Compose reads gb as 1024 MiB and mb as 1 MiB, so 5gb is 5368709120 bytes. Plain docker compose up applies both keys on a single host, with no Swarm and no --compatibility flag. The engine rejects any limit below 6mb, and rejects a limit lower than the same service’s reservation with Minimum memory limit can not be less than memory reservation limit.

A limit is a ceiling. The limits below sum to 18.5 GiB and the stack does not need a host that large, because each container uses only the memory it allocates, up to its limit.

deploy.resources.reservations.memory becomes the container’s cgroup v2 memory.low, which biases kernel reclaim towards other containers under host pressure. It reserves nothing on its own.

All are optional.

Default 256mb. The ceiling for edge.

Default 5gb. The ceiling for postgres. Must be at or above FLUXER_POSTGRES_MEMORY_RESERVATION or the container fails to create. Shared memory in use under FLUXER_POSTGRES_SHM_SIZE is charged against it.

Default 256mb. The ceiling for valkey. Must stay above FLUXER_VALKEY_MAXMEMORY, which bounds the stored dataset alone.

Default 256mb. The ceiling for nats. Covers JetStream file store metadata as well as the core server.

Default 1536mb. The ceiling for meilisearch. Indexing a backlog of a few hundred thousand messages with two indexing threads peaks near 1 GB, so keep this above 1 GB.

Default 256mb. Becomes MEILI_MAX_INDEXING_MEMORY. The memory the indexer may use for one batch. Meilisearch uses more than this while indexing, because every indexing thread also has buffers of its own. Lower it whenever you lower FLUXER_MEILISEARCH_MEMORY_LIMIT.

Default 2. Becomes MEILI_MAX_INDEXING_THREADS. Without it Meilisearch indexes on half the host’s CPU threads, and each thread can use up to 150 MB of buffers, so a host with many cores runs out of memory under a container limit that suits a small one. Raise it only together with FLUXER_MEILISEARCH_MEMORY_LIMIT.

Default production. Becomes MEILI_ENV. production requires the master key on every request.

Default true. Becomes MEILI_NO_ANALYTICS, which keeps Meilisearch from sending usage data.

Default 2gb. The ceiling for seaweedfs. One process runs the master, the volume server and the S3 gateway. The peak is the parts of one upload in flight at once, which the client uploads in parallel: a 500 MB attachment is 20 parts of 25 MB.

Default 1536MiB. The heap ceiling the Go runtime collects against. Go cannot see the container limit, so without this value an upload burst grows the heap past FLUXER_SEAWEEDFS_MEMORY_LIMIT and the kernel OOM-kills the container mid-upload with exit 137. Raising FLUXER_SEAWEEDFS_MEMORY_LIMIT on its own does not fix that, because the runtime grows to fill whatever it is given. Keep this near three quarters of the container limit and move the two together.

Default false. Becomes the -master.telemetry flag of seaweedfs, which reports usage to the SeaweedFS project when on.

Default 1. Becomes WEED_MASTER_VOLUME_GROWTH_COPY_1, the number of volumes SeaweedFS creates at once when a bucket runs out of room. Each volume reserves a 1 GB slot of free disk, and the stack writes to four buckets. SeaweedFS’s own default of 7 needs about 28 GB free before every bucket has a volume, so on a smaller disk the first bucket takes every slot and uploads fail with No writable volumes and no free volumes left.

Default 128mb. The ceiling for seaweedfs-init. A one-shot container that exits, so it never overlaps steady state.

Default 512mb. The ceiling for livekit. Grows with the number of concurrent voice publishers.

Default 2560mb. The ceiling for api. Node derives its heap ceiling from this at about half, and the derivation stops falling at or below 512mb.

Default 2560mb. The ceiling for worker. Same Node derivation as api, applied to every background lane at once.

Default 1gb. The ceiling for gateway. The BEAM has no heap ceiling of its own, so this limit is the only bound on the Gateway.

Default 512mb. The ceiling for media-proxy. Image and video transforms decode into this ceiling, so a large upload is what reaches this limit.

Default 256mb. The ceiling for push. Raise it alongside FLUXER_PUSH_SERVICE_QUEUE_CAPACITY or FLUXER_PUSH_SERVICE_SEND_CONCURRENCY.

Default 256mb. The ceiling for static-proxy. The service reads no environment variables and serves files only.

Default 256mb. The ceiling for app-proxy. It keeps the branding image origins from discovery in memory and reads no database.

Default 128mb. The ceiling for snowflakes. A router holds no shard state.

Default 256mb. The ceiling for snowflakes-shard. Holds the identifier buffer sized by FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE.

Default 128mb. The ceiling for users. A router holds no shard state.

Default 256mb. The ceiling for users-shard. Holds the read cache bounded by FLUXER_SVC_CACHE_MAX_ENTRIES, which defaults to 100000 entries.

Default 128mb. The ceiling for gifs. A router holds no shard state.

Default 256mb. The ceiling for gifs-shard. Lower than the 536870912 byte default of FLUXER_GIFS_SHARD_CACHE_MAX_BYTES.

Default 128mb. The ceiling for messages. A router holds no shard state.

Default 256mb. The ceiling for messages-shard. The shard has a Postgres pool of 20 connections alongside the read cache.

Default 128mb. The ceiling for unfurl. A router holds no shard state.

Default 256mb. The ceiling for unfurl-shard. Fetches remote pages, so a slow upstream holds bytes for the length of the fetch.

Default 256mb. The ceiling for admin. Serves the dashboard and proxies no media.

A reservation goes to the services whose loss takes the instance down, so the kernel reclaims from everything else first. All are optional.

Default 3gb. The reclaim floor for postgres. Must be at or below FLUXER_POSTGRES_MEMORY_LIMIT. Lowering the limit alone fails container creation.

Default 1gb. The reclaim floor for api. Must be at or below FLUXER_API_MEMORY_LIMIT.

Default 1gb. The reclaim floor for worker. Must be at or below FLUXER_WORKER_MEMORY_LIMIT.

Default 384mb. The reclaim floor for gateway. Must be at or below FLUXER_GATEWAY_MEMORY_LIMIT.

Node sizes its own old-space heap at roughly half the container limit, with a floor near 259 MB. A container limit of 2560mb gives a 1328 MB heap ceiling, 1280mb gives 664 MB, 768mb gives 396 MB, and every limit at or under 512mb gives the same 259 MB. Pin the value only to move it away from that derivation. All are optional.

No default. The V8 old-space ceiling for api, in MB. Appended to NODE_OPTIONS as --max-old-space-size only when non-empty. Keep it below FLUXER_API_MEMORY_LIMIT.

No default. The V8 old-space ceiling for worker, in MB. Same rule against FLUXER_WORKER_MEMORY_LIMIT.

No default. Extra Node flags for api, such as --dns-result-order=ipv4first. Compose reads it from .env and appends it to NODE_OPTIONS after --enable-source-maps and the heap flag, only when non-empty.

No default. Extra Node flags for worker. Same rule as FLUXER_API_NODE_OPTIONS.

The names on the Postgres command line tune the bundled server. Keep them consistent with FLUXER_POSTGRES_MEMORY_LIMIT. All are optional.

Default 150. The server-wide connection ceiling. The shipped pools total 90, from 25 each for api and worker and 20 each for messages-shard and users-shard. A value under about 95 exhausts the server before the pools fill, and the connections it turns away are refused with too many clients already.

Default 512MB. The shared buffer pool. Allocated at server start, so it is charged to the container whether or not it is used.

Default 2GB. The planner’s assumption about disk cache. Allocates nothing. Lowering it makes the planner prefer sequential scans and frees no memory.

Default 8MB. The per-operation sort and hash budget. Charged per sort or hash node, so one complex query can spend several multiples of it.

Default 256MB. The budget for one VACUUM, CREATE INDEX, or ALTER TABLE. One such operation at a time normally holds it.

Default 128MB. The budget for each autovacuum worker. Postgres runs three workers by default, so budget three times this value.

Default 1gb. The shared memory of the postgres container, used by parallel queries and parallel maintenance. Keep it well above FLUXER_POSTGRES_MAINTENANCE_WORK_MEM. A manual VACUUM sizes its shared memory from that budget, and fails with could not resize shared memory segment when it does not fit.

The rest of the Postgres command line has one variable per setting.

  • FLUXER_POSTGRES_RANDOM_PAGE_COST: default 1.1, sets random_page_cost.
  • FLUXER_POSTGRES_EFFECTIVE_IO_CONCURRENCY: default 200, sets effective_io_concurrency.
  • FLUXER_POSTGRES_DEFAULT_STATISTICS_TARGET: default 200, sets default_statistics_target.
  • FLUXER_POSTGRES_JIT: default off, sets jit.
  • FLUXER_POSTGRES_MIN_WAL_SIZE: default 512MB, sets min_wal_size.
  • FLUXER_POSTGRES_MAX_WAL_SIZE: default 2GB, sets max_wal_size.
  • FLUXER_POSTGRES_CHECKPOINT_COMPLETION_TARGET: default 0.9, sets checkpoint_completion_target.
  • FLUXER_POSTGRES_WAL_BUFFERS: default 16MB, sets wal_buffers.
  • FLUXER_POSTGRES_WAL_COMPRESSION: default zstd, sets wal_compression.
  • FLUXER_POSTGRES_BGWRITER_DELAY: default 50ms, sets bgwriter_delay.
  • FLUXER_POSTGRES_BGWRITER_LRU_MAXPAGES: default 1000, sets bgwriter_lru_maxpages.
  • FLUXER_POSTGRES_AUTOVACUUM_VACUUM_SCALE_FACTOR: default 0.05, sets autovacuum_vacuum_scale_factor.
  • FLUXER_POSTGRES_AUTOVACUUM_ANALYZE_SCALE_FACTOR: default 0.02, sets autovacuum_analyze_scale_factor.
  • FLUXER_POSTGRES_AUTOVACUUM_VACUUM_COST_LIMIT: default 2000, sets autovacuum_vacuum_cost_limit.
  • FLUXER_POSTGRES_TRACK_IO_TIMING: default on, sets track_io_timing.
  • FLUXER_POSTGRES_SHARED_PRELOAD_LIBRARIES: default pg_stat_statements, sets shared_preload_libraries.

shared_preload_libraries is read only when the server starts. Add a library only if the image ships it, or Postgres refuses to start.

The bundled Valkey uses persistent storage. These settings control its memory limit, its behaviour when full, and how often it writes to disk.

Default 192mb. The dataset ceiling. Bounds stored keys only. Client buffers, replication buffers and allocator overhead sit outside it and inside FLUXER_VALKEY_MEMORY_LIMIT.

Default noeviction. An over-limit write returns an OOM error. Keep this policy to protect deletion queues and other shared state. See Volumes and buckets for recovery implications.

Default everysec. How often Valkey flushes its append-only file to disk. always, everysec or no. everysec can lose the last second of writes in a crash.

No service sets a CPU limit, a CPU reservation or cpu_shares, so every container sees the host’s full CPU count. Bound the Gateway’s scheduler count with FLUXER_ERLANG_SCHEDULERS_MIN and FLUXER_ERLANG_SCHEDULERS_MAX, or pin it with FLUXER_ERLANG_SCHEDULERS and FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS from Gateway settings.

On a host smaller than the 16 GB the defaults assume, lower the large ceilings and the Postgres tuning together. The block below is the 8 GB profile.

Terminal window
FLUXER_POSTGRES_MEMORY_LIMIT=2560mb
FLUXER_POSTGRES_MEMORY_RESERVATION=1gb
FLUXER_POSTGRES_SHARED_BUFFERS=384MB
FLUXER_POSTGRES_EFFECTIVE_CACHE_SIZE=1536MB
FLUXER_API_MEMORY_LIMIT=1280mb
FLUXER_API_MEMORY_RESERVATION=768mb
FLUXER_WORKER_MEMORY_LIMIT=1280mb
FLUXER_WORKER_MEMORY_RESERVATION=768mb
FLUXER_GATEWAY_MEMORY_LIMIT=512mb
FLUXER_GATEWAY_MEMORY_RESERVATION=256mb
FLUXER_MEILISEARCH_MEMORY_LIMIT=1gb
FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY=192mb
FLUXER_SEAWEEDFS_MEMORY_LIMIT=1gb
FLUXER_SEAWEEDFS_GOMEMLIMIT=768MiB
FLUXER_GIFS_SHARD_CACHE_MAX_BYTES=134217728

At 8 GB the api and worker heap ceilings fall to 664 MB each and Postgres caches less of the working set, which shows up as slower search and slower history scrolling under load. Nothing is turned off, though the largest attachments need the default 2gb SeaweedFS ceiling to upload every part at once.

The 4 GB profile trades more.

Terminal window
FLUXER_POSTGRES_MEMORY_LIMIT=1280mb
FLUXER_POSTGRES_MEMORY_RESERVATION=512mb
FLUXER_POSTGRES_SHARED_BUFFERS=192MB
FLUXER_POSTGRES_EFFECTIVE_CACHE_SIZE=768MB
FLUXER_POSTGRES_WORK_MEM=4MB
FLUXER_POSTGRES_SERVER_MAX_CONNECTIONS=120
FLUXER_API_MEMORY_LIMIT=768mb
FLUXER_API_MEMORY_RESERVATION=512mb
FLUXER_WORKER_MEMORY_LIMIT=640mb
FLUXER_WORKER_MEMORY_RESERVATION=384mb
FLUXER_GATEWAY_MEMORY_LIMIT=384mb
FLUXER_GATEWAY_MEMORY_RESERVATION=192mb
FLUXER_MEILISEARCH_MEMORY_LIMIT=1gb
FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY=128mb
FLUXER_MEILISEARCH_MAX_INDEXING_THREADS=1
FLUXER_MEDIA_PROXY_MEMORY_LIMIT=320mb
FLUXER_SEAWEEDFS_MEMORY_LIMIT=320mb
FLUXER_SEAWEEDFS_GOMEMLIMIT=240MiB
FLUXER_LIVEKIT_MEMORY_LIMIT=320mb
FLUXER_VALKEY_MEMORY_LIMIT=192mb
FLUXER_VALKEY_MAXMEMORY=128mb
FLUXER_GIFS_SHARD_CACHE_MAX_BYTES=67108864

At 4 GB the api heap ceiling is 396 MB and the worker heap ceiling is 332 MB. That covers chat. A large attachment and a search reindex at the same time exceed those heap ceilings. Media transforms above roughly 20 MB start failing in media-proxy, Meilisearch indexes a backlog more slowly, and a busy voice room is the first thing to drop. Keep FLUXER_POSTGRES_SERVER_MAX_CONNECTIONS at or above 110.

Compose reads these, and no Fluxer service does. The health checks decide when a dependent service may start and when Docker marks a container unhealthy. All are optional.

  • FLUXER_RESTART_POLICY: default unless-stopped, for every long-running service. seaweedfs-init never restarts.
  • FLUXER_HEALTHCHECK_INTERVAL: default 10s, for every check.
  • FLUXER_HEALTHCHECK_TIMEOUT: default 5s, for every check.
  • FLUXER_HEALTHCHECK_RETRIES: default 10, for edge, postgres, valkey, nats, meilisearch, livekit and static-proxy.
  • FLUXER_APP_HEALTHCHECK_RETRIES: default 30, for the internal services, api, gateway, push and admin.
  • FLUXER_APP_HEALTHCHECK_START_PERIOD: default 90s, for api, worker and gateway.
  • FLUXER_SVC_HEALTHCHECK_START_PERIOD: default 60s, for the internal services, push and admin.
  • FLUXER_WORKER_HEALTHCHECK_RETRIES: default 3, for worker. Its check reads a heartbeat file, and a stale one means the worker is stuck.
  • FLUXER_SEAWEEDFS_HEALTHCHECK_RETRIES: default 20, for seaweedfs.
  • FLUXER_SEAWEEDFS_HEALTHCHECK_START_PERIOD: default 60s, for seaweedfs. api, worker and media-proxy wait on seaweedfs-init, which waits on this check.
  • FLUXER_SEAWEEDFS_INIT_ATTEMPTS: default 60. Compose forwards it from .env to seaweedfs-init, which checks and creates the buckets that many times, 2 seconds apart, before it gives up. Raise it when docker compose up fails on seaweedfs-init on a slow disk.

Durations take the Compose form, such as 90s or 2m. Raise the retries or start period on a slow host where a service is still starting when its check gives up.

The edge is the only HTTP entry point, and every route is served from the one public hostname. It rewrites each path before handing it to an upstream. What the single port routes has the path table, and What every proxy must do has the requirements for anything in front of the edge.

FLUXER_ADMIN_BASE_PATH moves the admin path, and no other variable on this page changes that routing. The Caddyfile is a bind mount, so an edit to it takes docker compose restart edge.

CORS origins are exactly the app endpoint, the FLUXER_APP_ORIGIN_ALIASES origins, and the marketing endpoint. Serving the client from a hostname other than FLUXER_DOMAIN requires overriding FLUXER_APP_ENDPOINT.

VolumeHoldsBack up
postgres-dataEvery account, message, and configuration rowYes
seaweedfs-dataEvery uploaded fileYes
valkey-dataPending file deletions, the thread auto archive schedule, other queues and shared cacheYes
nats-dataPending and failed background jobsYes
edge-dataIssued TLS certificatesOptional, a loss only costs a re-issue
edge-configThe edge’s own stateNo
meilisearch-dataThe search index, rebuildable from PostgresOptional

Losing valkey-data can delay scheduled account and bulk-message deletions while their queues are rebuilt. Pending asset deletions and cache purges exist only here and are lost with it, so do not treat this volume as disposable cache. The thread auto archive schedule exists only here too. After a loss, an active thread is archived automatically again only once it is modified or unarchived. Thread lifecycle states the archive rule.

Losing meilisearch-data leaves search empty until it is rebuilt. Nothing rebuilds it automatically, because Postgres still records every channel as indexed. Rebuild each index with POST /admin/search/indexes/{index_name}/refreshes. The channel_messages, guild_members and threads indexes rebuild one community at a time and need a guild_id for each. A backup of the volume avoids those calls on a large instance.

nats-data retains pending jobs in JOBS for up to 7 days and failed jobs in JOBS_DLQ for up to 30 days. A full jobs stream rejects new work. A full dead-letter stream drops its oldest entries, so investigate failures promptly. If dead-letter storage is unavailable, failed jobs remain in JOBS only until they expire. Losing this volume loses queued work, which is not automatically recovered from the database.

Startup refuses an existing JOBS or JOBS_DLQ stream with the wrong name, subjects, retention or storage type, or with sealing, no-ack, mirroring, sources, republishing or a subject transform set. Startup never recreates a stream. When the JOBS limits differ, startup updates them in place, unless tightening them would drop queued jobs. Stop publishers and workers before migrating an incompatible stream.

Volume names are prefixed with the Compose project name, so postgres-data is fluxer_postgres-data on the host.

seaweedfs-init creates these buckets and exits. Each bucket takes its name from its variable in .env, and the table gives the default.

BucketSet byHolds
fluxerFLUXER_S3_BUCKET_CDNAvatars, guild and entity assets, themes, entrance sounds, memes, and processed attachments
fluxer-uploadsFLUXER_S3_BUCKET_UPLOADSRaw attachment uploads, before processing
fluxer-reportsFLUXER_S3_BUCKET_REPORTSAbuse report evidence
fluxer-harvestsFLUXER_S3_BUCKET_HARVESTSUser and guild data archives

FLUXER_S3_BUCKET_STATIC names an optional bucket. The shipped stack does not use or create it.