Skip to main content
PostQueen reads all of its settings from environment variables. With Docker Compose they go in the .env file next to docker-compose.yaml: see Docker Compose. A change takes effect when the postqueen container is recreated, not restarted:
  • NEXT_PUBLIC_ variables need no rebuild. The web app renders its pages on the server and hands these values to the browser at run time, so recreating the container is enough. The one exception is NEXT_PUBLIC_APP_VERSION, which is fixed when the image is built.
  • The start-up check only warns. After the backend is listening it checks the required values below and logs each problem as a Configuration issue. Nothing stops, so a running container is not proof that the settings are right: docker compose logs postqueen | grep -i "configuration issue".
  • The backend and the orchestrator read the same variables. In the official image both run in one container. If you run them apart, give both the same environment.

Required

The start-up check reports these when they are missing, and URLs when they are not valid or end in /.

Encryption keys

A channel’s OAuth access and refresh tokens, the ones a network hands back after sign-in, are encrypted with the same key. Tokens stored before that are encrypted when the app starts. A token is re-encrypted under a new key only when it is next written, and some never expire, so keep PREVIOUS_ENCRYPTION_KEY set after a rotation. Treat a database dump as sensitive all the same.
  • On a new install, set ENCRYPTION_KEY to its own random value before there is data. JWT_SECRET then only signs sessions, and rotating it only signs people out.
  • On an install that never set it, set ENCRYPTION_KEY to the current value of JWT_SECRET. Nothing needs re-encrypting, because that is the key already in use. After that you can rotate JWT_SECRET freely.
  • To rotate ENCRYPTION_KEY, copy the current value into PREVIOUS_ENCRYPTION_KEY, put the new value in ENCRYPTION_KEY, and recreate the container. Leave both set: a row is re-encrypted only when something rewrites it, so data nobody touches stays on the old key.

How switches are read

For most switches, false means on. Only these compare against the exact string true: DISABLE_REGISTRATION, PASSWORDLESS_LOGIN, WALLET_LOGIN, REQUIRE_EMAIL_ACTIVATION, NOT_SECURED, DISABLE_SSRF_PROTECTION, TEMPORAL_TLS, EMAIL_SECURE, UPLOAD_VIA_SERVER and NEYNAR_SPONSOR_SIGNERS. Every other switch on this page is on when it has any value, so IS_GENERAL=false, RUN_CRON=false or POSTQUEEN_GENERIC_OAUTH=false all turn the setting on. Remove the variable to turn one off. With Docker Compose, IS_GENERAL and RUN_CRON also have a default of true in docker-compose.yaml, so delete their lines there.
PRISMA_MIGRATE has its own rule: see Background work.

Sign-up and sign-in

Sign-in buttons

Set the four Apple values together: the button appears with APPLE_CLIENT_ID alone and fails without the rest.

Background work

Temporal runs the schedule, and the orchestrator runs its workers. See Self-hosting overview.

Storage and media

Setup and trade-offs: Uploads and storage and Cloudflare R2.

Limits and safety

Email

Setup: Email.

Networks

Each network’s developer app variables, redirect URI and setup page are on Channel developer apps. Eight networks read no variable: Bluesky, DEV, Hashnode, Lemmy, Listmonk, Moltbook, Nostr and WordPress. Kick’s secret is KICK_SECRET, and Dribbble’s variables are spelled with three b characters.

Farcaster

The channel connects through a Neynar managed signer, which the person approves by scanning a QR code in the Farcaster app. There is no redirect URI. NEYNAR_APP_FID, NEYNAR_APP_MNEMONIC and NEYNAR_SPONSOR_SIGNERS are not listed in the Compose file’s YAML; the .env file passes them through. Setup: Farcaster setup.

Hide or move a network

The identifier is the last part of the connect address, such as tiktok in /integrations/social/tiktok. The two are usually set together: hide the network you retire, and its channels move on their next reconnect.

MCP and OAuth apps

Details: MCP on your server.

AI

Billing

A private install needs none of this: without both Stripe keys there is no billing and every workspace has every feature.

Interface

Setup: Short links. The first one configured in this order wins: DUB_TOKEN (with DUB_API_ENDPOINT, DUB_SHORT_LINK_DOMAIN), SHORT_IO_SECRET_KEY, KUTT_API_KEY (with KUTT_API_ENDPOINT, KUTT_SHORT_LINK_DOMAIN), LINK_DRIP_API_KEY (with LINK_DRIP_API_ENDPOINT, LINK_DRIP_SHORT_LINK_DOMAIN).

Newsletter for new sign-ups

These add each new account’s email address to a mailing list you run. They have nothing to do with posting.
LISTMONK_* does not configure the Listmonk channel. The channel takes its server address and API user in the connect form: see Channel developer apps.

Analytics and error tracking

All optional.

Listed in example files, but not read

These appear in the Compose file or .env.example and change nothing: FEE_AMOUNT, NX_ADD_PLUGINS, SLACK_SIGNING_SECRET, STRIPE_SIGNING_KEY_CONNECT, POSTQUEEN_OAUTH_URL and BACKEND_URL. MCP_URL is passed to the web app, but no page uses it: the MCP addresses on Connections come from NEXT_PUBLIC_BACKEND_URL.
Last modified on October 9, 2026