Environment variables
Every environment variable the Feeblo server and dashboard read, grouped by concern with defaults and which ones are required.
The server reads its configuration from the process environment at boot, and the
dashboard reads a smaller subset through its own Astro env schema. This page covers
every variable in .env.example, grouped by concern. Each row states whether the code
treats the variable as required, what default applies, and what breaks when you leave
it out.
With one exception, a required mark means the process fails to start without the value. The Media group resolves its config lazily, so missing values there surface on the first upload instead of at boot.
Core
| Variable | Required | Default | What breaks without it |
|---|---|---|---|
DATABASE_URL |
Yes | None (compose: postgres://feeblo:password@pg:5432/feeblo) |
Postgres DSN for the server and the migration runner. The server config layer fails at boot. See Database. |
REDIS_URL |
No | In-memory rate-limiter store | Redis DSN, optionally with username, password, and db index. Set it when running more than one server so the rate-limiter store is shared; without it every instance limits independently. The compose network defaults to redis://redis:6379/0. |
APP_URL |
Yes | None (compose: http://localhost:3001) |
Public URL of the dashboard. Auth redirects, cookies, and trusted origins derive from it. It must match what browsers hit, or sign-in breaks in confusing ways. |
API_URL |
Yes | None (compose: http://localhost:3000) |
Public URL of the API. The Slack, Discord, SES, and Polar webhook URLs all derive from it. |
APP_ROOT_DOMAIN |
Yes | None (compose: localhost) |
Root domain your public boards live on. Boards run on arbitrary subdomains of it, so a DNS wildcard must cover it. |
APP_RELEASE |
No | dev |
Release label. The server returns it from /health and the dashboard displays it. Point it at a git SHA or tag to identify a running deployment. |
NO_INDEX |
No | Unset | Any non-empty value adds noindex to dashboard and board pages. An empty value disables the behavior. |
Auth
| Variable | Required | Default | Notes |
|---|---|---|---|
AUTH_ENCRYPTION_KEY |
Yes | None | At-rest key for auth tokens, verification codes, and email-subscription tokens. When the integration encryption key is unset it also encrypts integration credentials, and then it must be at least 32 bytes. Missing it, and the server refuses to start with a missing-key error; compose additionally fails fast at docker compose up. Generate one with openssl rand -hex 32. |
AUTH_TRUSTED_ORIGINS |
No | The app and API URLs, plus *.localhost:3001 in development or a {scheme}//*.{root domain} wildcard in production |
Comma-separated browser origins allowed to call the API (better-auth trustedOrigins). Public board subdomains need a wildcard for the root domain, for example *.feeblo.com; scheme-less patterns match both http and https hosts. |
GITHUB_CLIENT_ID |
No | None | GitHub sign-in. Set it together with the client secret below. |
GITHUB_CLIENT_SECRET |
No | None | GitHub OAuth secret. |
GOOGLE_CLIENT_ID |
No | None | Google sign-in, paired with the secret below. |
GOOGLE_CLIENT_SECRET |
No | None | Google OAuth secret. |
GITHUB_EMULATOR_URL |
No | None | Points GitHub sign-in at a local OAuth emulator (vercel-labs/emulate) instead of the real provider, for example http://localhost:4000. Development only. |
GOOGLE_EMULATOR_URL |
No | None | Same hook for Google, for example http://localhost:4001. |
See OAuth sign-in for registering the provider apps.
The mailer builds its SMTP transport from the credentials below. Defaults come from
packages/transactional/src/config.ts; several differ from the sample values in
.env.example.
| Variable | Required | Default | Notes |
|---|---|---|---|
SMTP_TRANSPORT |
No | smtp-auth |
Documented options are smtp-auth (default), smtp-api, and resend. The current mailer does not read this variable yet; it always uses the credentials below. |
SMTP_HOST |
No | 127.0.0.1 |
SMTP server host. |
SMTP_PORT |
No | 2500 |
SMTP port. The sample value in .env.example is 1025. |
SMTP_USERNAME |
No | None | Auth user passed to nodemailer. Omit for an open relay. |
SMTP_PASSWORD |
No | None | Auth secret, paired with the username. |
SMTP_SECURE |
No | false |
Force TLS on the connection. |
SMTP_UNSAFE_IGNORE_TLS |
No | false |
When true and force-TLS is off, TLS is skipped even if the server advertises STARTTLS. Meant only for broken local relays. |
SMTP_FROM_NAME |
No | None | Display name for the from address. .env.example documents it, but the current mailer does not read it yet. |
SMTP_FROM_ADDRESS |
No | hello@feeblo.com |
Sender address. .env.example marks it required, but the code falls back to this value, so set it explicitly to a real address in production. |
SMTP_PERSONAL_FROM_ADDRESS |
No | Falls back to the default from address | Dedicated sender for personal lifecycle mail (user onboarding, user feedback). |
SMTP_SERVICE |
No | None | Nodemailer well-known service name, passed through to the transport. |
EMAIL_PROVIDER_WEBHOOK_TOKEN |
No | None | The token alone activates the Amazon SES feedback webhook at the API path /email-provider/ses/<token>. Unset, and the route stays inert (404). Generate a dedicated one with openssl rand -hex 32; do not reuse the auth at-rest key. |
EMAIL_PROVIDER_SNS_TOPIC_ARN |
No | None | ARN of the SNS topic that delivers SES feedback. Its only role is source filtering: messages signed from any other topic are rejected. Set it whenever the token is set. |
EMAIL_OUTBOX_GLOBAL_DELIVERY_PAUSED |
No | false |
Pauses all outbound email when true. |
EMAIL_OUTBOX_MAX_CONCURRENT_SENDS |
No | 10 |
Concurrency bound for the outbox send workers. |
EMAIL_OUTBOX_MONTHLY_SEND_LIMIT |
No | 100000 |
Once monthly send attempts reach this number, delivery stops. |
EMAIL_OUTBOX_ESTIMATED_SEND_COST_MICROS |
No | 100 |
Estimated cost per send in micros, used for spend tracking. |
EMAIL_OUTBOX_PAUSED_WORKSPACE_IDS |
No | Empty | Comma-separated workspace IDs whose mail stays queued. |
See Email for transports and the SES webhook end to end.
Media
The upload service resolves this config lazily per request, so a missing value fails the first upload rather than server boot.
| Variable | Required | Default | Notes |
|---|---|---|---|
MEDIA_UPLOAD_REGION |
Yes | None (compose: us-east-1) |
S3 region. Use auto for Cloudflare R2. |
MEDIA_UPLOAD_ENDPOINT |
Yes | None (compose: http://127.0.0.1:9002) |
S3-compatible endpoint. The compose default targets a local MinIO-style server; point it at your R2 or S3 endpoint. |
MEDIA_UPLOAD_ACCESS_KEY_ID |
No | None (compose: feeblo) |
S3 access key. |
MEDIA_UPLOAD_SECRET_ACCESS_KEY |
No | None (compose: password) |
S3 secret key. |
MEDIA_PUBLIC_BUCKET_NAME |
Yes | None (compose: feeblo-media-public) |
Public bucket that uploaded media lands in. |
See Media storage for S3, R2, and MinIO setups.
Integrations
Each integration registers only when its full credential set is present; partial sets disable the provider without any error. The settings below also back the outbound webhook subsystem.
| Variable | Required | Default | Notes |
|---|---|---|---|
INTEGRATION_ENCRYPTION_KEY |
No | Falls back to the auth at-rest key | Encrypts webhook endpoint URLs and signing secrets at rest. When it is shorter than 32 bytes, webhook create/update/rotate fails with Webhook credentials could not be encrypted. |
INTEGRATION_ALLOW_PRIVATE_NETWORK |
No | false |
Allows outbound webhook deliveries to private-network endpoints. The code honors it only when NODE_ENV is development; every other environment rejects private egress. Never enable it in production. |
SLACK_CLIENT_ID |
No | None | Slack app credentials. The integration registers only when the client id, client secret, and signing secret are all set. |
SLACK_CLIENT_SECRET |
No | None | |
SLACK_SIGNING_SECRET |
No | None | Request-signing secret from the Slack app’s Basic Information page. |
SLACK_OAUTH_REDIRECT_URL |
No | The API URL plus /slack/oauth/callback |
OAuth redirect for the Slack install flow. |
DISCORD_CLIENT_ID |
No | None | Discord app credentials. The integration registers only when the client id, client secret, bot token, and public key are all set. |
DISCORD_CLIENT_SECRET |
No | None | |
DISCORD_BOT_TOKEN |
No | None | Application-wide bot token; the same bot serves every guild install. |
DISCORD_PUBLIC_KEY |
No | None | 64 hex characters; verifies interaction signatures. |
DISCORD_OAUTH_REDIRECT_URL |
No | The API URL plus /discord/oauth/callback |
OAuth redirect for the Discord install flow. |
GITHUB_INTEGRATION_APP_ID |
No | None | Numeric App ID from the GitHub App’s General page, not the OAuth client ID. The two-way issue integration registers only when all six values below are set. |
GITHUB_INTEGRATION_APP_SLUG |
No | None | URL-friendly app name from the public installation URL. For https://github.com/apps/feeblo the slug is feeblo. |
GITHUB_INTEGRATION_CLIENT_ID |
No | None | The install flow uses it only to verify the installer can reach the selected app installation. |
GITHUB_INTEGRATION_CLIENT_SECRET |
No | None | Paired with the client id above; the temporary user token is never stored. |
GITHUB_INTEGRATION_PRIVATE_KEY |
No | None | PEM private key from the app’s Private keys section. Preserve the complete BEGIN/END lines and line breaks; store it as a multiline secret. |
GITHUB_INTEGRATION_WEBHOOK_SECRET |
No | None | Random secret configured identically on the GitHub App. Register the fixed callback /github/app/installations/callback and webhook /github/app/webhooks under the API base; Feeblo mounts both paths itself. |
The social-auth GitHub variables under Auth are separate: they only sign users into Feeblo. The integration credentials group above powers the issue integration.
Billing
| Variable | Required | Default | Notes |
|---|---|---|---|
POLAR_MODE |
No | sandbox |
Polar environment: sandbox or production. |
POLAR_ACCESS_TOKEN |
No | None | Enables Polar checkout, the customer portal, and subscription synchronization. Without it the Polar client is never created and billing stays off. |
POLAR_WEBHOOK_SECRET |
No | None | Verifies signed Polar webhooks. Point Polar at the webhook path /api/auth/polar/webhooks under the API base, then resend product.created and product.updated events so the local product catalog populates. |
Observability & misc
| Variable | Required | Default | Notes |
|---|---|---|---|
SENTRY_DSN |
No | None | Turns on server error tracking, tracing, logs, and metrics. |
SENTRY_ENVIRONMENT |
No | NODE_ENV |
Sentry environment label. Set it to staging while NODE_ENV stays production. |
SENTRY_TRACES_SAMPLE_RATE |
No | 0.1 |
Fraction of transactions to trace (0.0–1.0). |
POSTHOG_KEY |
No | None | PostHog project API key for the dashboard. When omitted, all analytics is disabled. |
POSTHOG_HOST |
No | https://us.i.posthog.com |
PostHog instance host. |
EMBEDDING_API_KEY |
No | None | Enables AI post embeddings and semantic suggestions; the API must implement the OpenAI embeddings endpoint. An empty value keeps the feature off. |
EMBEDDING_MODEL |
No | text-embedding-3-small |
Embeddings model name. |
EMBEDDING_DIMENSIONS |
No | 1536 |
Vector size. It must equal the post embedding column dimension (1536); reconfigure the column with db:configure-embeddings before changing it, or embedding jobs fail. |
EMBEDDING_API_URL |
No | None | Override for OpenAI-compatible providers such as Hugging Face or Ollama. |
PROFANITY_EXTRA_WORDS |
No | None | Comma-separated words appended to the bundled profanity dictionary. The server trims and lowercases each word. |