Docs Reference Environment variables
Environment variables
Grouped as in .env.example. Refused means the worker starts, logs
misconfigured, missing: …, answers /health with 503 naming the variable, and every other route
with 500. Docker then marks the container unhealthy, and docker compose up --wait fails. An
empty value counts as absent.
The image and the port
| Variable | Default | What it does | When it is wrong |
|---|---|---|---|
FRUITBACK_IMAGE |
ghcr.io/sakuga-software/fruitback-worker:edge |
Compose only. The image, as a tag or a digest. | A tag that does not exist fails the pull. A tag already on the machine is not pulled again: run docker compose pull. |
FRUITBACK_PORT |
8080 |
Compose only. The port on the host, or 127.0.0.1:8080 to keep it off the internet. |
An older .env says PORT, which the file ignores: the port falls back to 8080. |
FRUITBACK_CONSOLE_IMAGE |
ghcr.io/sakuga-software/fruitback-console:edge |
Compose only. The image of the console, as a tag or a digest. See The console. | A tag that does not exist fails the pull of the console, and of no other service. |
FRUITBACK_CONSOLE_PORT |
8081 |
Compose only. The port of the console on the host, or 127.0.0.1:8081 to keep it off the internet. |
The same port as FRUITBACK_PORT: the second container does not start. |
PORT |
8080 |
The port the process listens on, inside the container. Compose sets it to 8080 and does not read it from .env. |
A value other than the published port makes the worker unreachable, and Docker still reports it healthy, because the healthcheck probes the same port (measured with 9000). With docker run, a value that is not a positive integer: the process listens on 8080 with no message, but the healthcheck probes the raw value, so Docker marks the container unhealthy (measured with abc). |
HOST |
0.0.0.0 |
The interface the process listens on. Compose does not pass it. | 127.0.0.1 makes the worker unreachable from outside the container, and Docker still reports it healthy (measured). |
NODE_ENV |
production, set by the image |
What refuses the in-memory store. Compose does not pass it. | Any other value lets FRUITBACK_STORE=memory start, and every note dies with the container. |
Who may call the worker
| Variable | Default | What it does | When it is wrong |
|---|---|---|---|
ALLOWED_ORIGINS |
none: required | Origins that may call the worker, comma-separated, or *. The address of each site of the console joins the list by itself (measured). |
Absent: Compose does not start. With docker run, the container runs and is refused: /health answers 503 naming it, and Docker marks it unhealthy (measured). An origin that is not exactly what the browser sends (a trailing slash, http for https, a missing port) is accepted at boot, and each call from the site answers 403 origin-not-allowed, which the browser shows as a CORS error. A request with no Origin, such as curl, is not checked. * accepts every site. An origin of the list that is no site of the console plants nothing: a write names a site, and the page of another origin gets 403 origin-not-allowed-for-client (measured). |
TRUSTED_PROXY_HOPS |
1 in the worker, 0 in docker-compose.yml |
The number of proxies between the internet and the container. | Not a whole number from 0 up: refused. Too high or too low: see Behind a reverse proxy. |
RATE_LIMIT_PER_MINUTE |
20 |
Requests a minute per client address, reads and writes together. Counted in the database, so the replicas share one limit. /health is not counted. |
Not a whole number above 0: refused. Too low for a team behind one office address: they share a bucket and get 429 rate-limited. |
Where the notes live
| Variable | Default | What it does | When it is wrong |
|---|---|---|---|
FRUITBACK_STORE |
postgres |
Where the notes of a site with no tracker of its own go: postgres (the database of FRUITBACK_DATABASE_URL), linear, github or memory. |
Unknown: refused, FRUITBACK_STORE (unknown store "postgre", expected …), with the list. The store of a file, of before 0.3.0: refused, with what to do: see Upgrading. Unset while LINEAR_API_KEY, LINEAR_TEAM_ID or a FRUITBACK_GITHUB_ variable is set: refused, because a worker of before with no store ran on Linear; write the store. memory in the image: refused. |
FRUITBACK_SQLITE_PATH |
none | Gone since 0.3.0. It named the file of the notes of a worker of before. The notes are in the database; migrate-to-postgres --notes copies a file there. |
Set: refused, with what to do. Compose hands it over empty for that reason only. |
LINEAR_API_KEY |
none | A Linear personal API key. A secret: it never reaches a browser. | Absent with linear: refused, with LINEAR_TEAM_ID. Wrong: /health answers 200, and every read and write answers 502 store-unavailable, Linear responded 401 (measured). |
LINEAR_TEAM_ID |
none | The team that receives the issues. A client’s teamId replaces it. |
Absent with linear: refused. Wrong: not checked at boot; Linear refuses the call, and the worker answers 502 store-unavailable with Linear’s message. |
LINEAR_PROJECT_ID |
none | The project for the issues. Optional. A client’s projectId replaces it. |
Wrong: not checked at boot. |
FRUITBACK_GITHUB_APP_ID |
none | The ID of the GitHub App, or its client ID. | Absent with github: refused, with the two other FRUITBACK_GITHUB_ variables. Wrong: not checked at boot; GitHub refuses the App’s token, and every read and write answers 502 store-unavailable. |
FRUITBACK_GITHUB_PRIVATE_KEY |
none | The App’s private key, the .pem file GitHub generates. A secret. On one line, write each line break as \n; inside double quotes, the key can keep its line breaks. Both forms reach the worker as a key it reads (measured with docker compose config). |
Absent with github: refused. Not an RSA private key: refused, and the diagnostic names the variable, never the key. The key of another App: every read and write answers 502 store-unavailable. |
FRUITBACK_GITHUB_REPOSITORY |
none | The repository that receives the issues, as owner/repo. A client’s repository replaces it. |
Absent with github: refused. Not owner/repo: refused. A repository the App is not installed on: /health answers 200, and every read and write answers 502 store-unavailable. |
Several client sites
| Variable | Default | What it does | When it is wrong |
|---|---|---|---|
FRUITBACK_CLIENTS |
none | Gone since 0.3.0. It was the map of the sites of a worker with no database. The console makes each site, in the database. | Set, whatever it holds: refused, with what to do: make each site in the console, then copy its notes with migrate-to-postgres --notes <file> --client <id>=<id of the site>. |
Identity and reads
| Variable | Default | What it does | When it is wrong |
|---|---|---|---|
FRUITBACK_IDENTITY_SECRET |
none: required | The HS256 key that signs the access token of a session of the extension, 32 characters or more. Give it to no site: see Change FRUITBACK_IDENTITY_SECRET. Make one with openssl rand -base64 32. |
Absent: refused. Under 32 characters, or with white space at its start or its end: refused. Changed: every reviewer’s access token answers 401 until its next refresh. |
FRUITBACK_READ |
none | It applies to no site: each site of the console has its visibility, members or everyone, and the boot log says so (measured). It was the setting of a worker of before 0.3.0, public or authenticated. |
A typo: refused, never defaulted to public. |
FRUITBACK_HIDE_COMMENTS |
empty: the team’s replies are shown | 1 keeps the team’s replies out of the pins. |
Any other value, true included, is accepted and hides nothing (measured). |
FRUITBACK_NO_UPDATE_CHECK |
empty: the worker asks GitHub for the last release | 1: the worker never asks. Empty: when an administrator opens « Instance » in the console, the worker asks GitHub for the last release, once a day at most, and the screen says when a newer one is out. The request carries nothing of the instance, but GitHub sees the address of the server. |
Any other value, true included, is accepted and stops nothing. |
FRUITBACK_TEAM_LOCALE |
en |
The language of the prose in an issue description, for the team that triages it: en or fr. It never follows the reporter’s browser, and the note itself is never translated. |
A value that is not a locale tag: refused at boot, named in missing. A valid tag this build has no words for, de for example: the description is in English (measured on the round trip, which holds in every language because only the prose is translated). |
The database, the console and the sessions
| Variable | Default | What it does | When it is wrong |
|---|---|---|---|
FRUITBACK_SESSION_PATH |
none | Gone since 0.3.0. It named the file of the sessions of the extension, on a worker of before. The sessions are in the database. | Set: refused, with what to do. Each reviewer pairs again, once, from the console. |
FRUITBACK_FIRST_REVIEWER |
none | Gone since 0.3.0. It paired the first reviewer of a worker with no workspace. The first administrator is FRUITBACK_FIRST_ADMIN, and a reviewer pairs from the console. |
Set: refused, with what to do. |
FRUITBACK_FIRST_ADMIN |
none: no link is written when the worker starts | The address of the first administrator, for an instance that sends no e-mail and has no provider. A worker whose instance has no owner yet writes a sign-in link for that address in its log when it starts. See The first administrator. | Not one address: refused. Without FRUITBACK_AUTH_SECRET and its database: refused, and the worker does not make the secret. |
FRUITBACK_ACCOUNTS_PATH |
none | Gone since 0.3.0. It named the file of the accounts. The accounts are in PostgreSQL, with FRUITBACK_DATABASE_URL. |
Set: refused at boot, and the log says what to set. See Upgrading a deployment that has an accounts file. |
FRUITBACK_DATABASE_URL |
none: required; in docker-compose.yml, the postgres service of the file |
The address of the PostgreSQL database that holds everything the worker keeps: the notes, the accounts, the workspaces and their sites, the connectors and the sessions, as postgres://user:password@host:5432/database. The worker makes its tables at the first request, so the role of the address has CREATE on the schema of the worker: public, or the one of FRUITBACK_DATABASE_SCHEMA. The worker sets the search path of each of its connections itself: the one of the address, of the role or of the database has no effect. |
Absent: refused, with the four variables below and a word on Upgrading. Not a postgres:// address with a host: refused. With FRUITBACK_SESSION_PATH or FRUITBACK_CLIENTS: refused. It holds a password: no log of the worker and no answer of /health quotes it. A database that does not answer, or a role that cannot create in the schema of the worker: the worker answers 502 to every read and every write, the log says why, and /health stays green. |
FRUITBACK_DATABASE_SCHEMA |
public |
The schema of that database for the tables of the worker and its journal of migrations: see In a database that another application uses. | Not a plain name (a lower-case letter or _, then lower-case letters, digits and _, 63 characters at most, not pg_… or information_schema, and not a key word of SQL such as user, order or table): refused, and the value is not quoted. Set without FRUITBACK_DATABASE_URL: refused. A schema that is not there and a role that cannot create in the database: 502, and the log says what to grant. |
FRUITBACK_POSTGRES_PASSWORD |
none: required by docker-compose.yml |
The password of the PostgreSQL that docker-compose.yml starts beside the worker. The worker does not read it: the compose file puts it in the address it gives the worker. Make one with openssl rand -hex 32: it goes into an address. |
Absent: docker compose starts nothing, and names it. Changed after the first start: the database keeps the password it was made with, and the worker cannot connect. |
FRUITBACK_CONSOLE_URL |
none: required; http://localhost:8081 in .env.example |
Where the console answers. A sign-in link opens it, and the console routes answer this origin only, with credentials. | Absent: refused. Plain http:// on a host other than localhost: refused. |
FRUITBACK_MAIL_FROM |
none: no sign-in by link | The address the sign-in links come from, on a domain the provider verified. | Set without FRUITBACK_SCALEWAY_TEM: refused. A domain the provider did not verify: /console/auth/email answers 502 mail-unavailable. |
FRUITBACK_SCALEWAY_TEM |
none: no sign-in by link | Scaleway Transactional Email, as <project id>:<secret key> of an API key that may send. |
Set without FRUITBACK_MAIL_FROM, or with no colon: refused. A revoked key: /console/auth/email answers 502 mail-unavailable. |
FRUITBACK_PUBLIC_URL |
none: required; http://localhost:8080 in .env.example |
The address this worker answers at, as a browser reaches it, behind its proxy. The console calls it, and GitHub, Google and Linear send a person back to it. | Absent: refused. Plain http:// on a host other than localhost, or with a path: refused. |
FRUITBACK_GITHUB_OAUTH |
none | Gone since 0.3.0. It named the GitHub OAuth App of the sign-in of before. FRUITBACK_AUTH_GITHUB_OAUTH is the one that signs in. |
Set: refused at boot, and the log says what to remove. |
FRUITBACK_GOOGLE_OAUTH |
none: no sign-in with Google | A Google OAuth client of type « Web application », as <client id>:<client secret>. Its redirect URI is <FRUITBACK_PUBLIC_URL>/console/auth/callback/google. |
No colon, or set without FRUITBACK_AUTH_SECRET: refused. A redirect URI that differs from the client’s: Google refuses the sign-in. |
FRUITBACK_LINEAR_OAUTH |
none: a workspace connects Linear with a key | A Linear OAuth application, as <client id>:<client secret>. Its callback is <FRUITBACK_PUBLIC_URL>/auth/linear/callback. |
No colon, or set without FRUITBACK_PUBLIC_URL, FRUITBACK_DATABASE_URL or FRUITBACK_SECRETS_KEY: refused. A callback that differs from the application’s: Linear refuses the consent. |
FRUITBACK_GITHUB_CONNECTOR |
none: a workspace connects no GitHub | The GitHub App that a workspace installs to connect its GitHub, as <app id>:<client id>:<client secret>. Another App than the one of the store github. |
Set without FRUITBACK_GITHUB_CONNECTOR_PRIVATE_KEY, not three parts, an App ID that is not digits, or set without FRUITBACK_DATABASE_URL or FRUITBACK_SECRETS_KEY: refused. |
FRUITBACK_GITHUB_CONNECTOR_PRIVATE_KEY |
none | The private key of that App, the .pem file GitHub generates. A secret: it mints a token for every repository of every organisation that installed the App. On one line, write each line break as \n; with its line breaks, the worker reads it as it is. |
Set without FRUITBACK_GITHUB_CONNECTOR, or not an RSA private key: refused, and the diagnostic names the variable, never the key. |
FRUITBACK_SECRETS_KEY |
none: a workspace connects no tracker | What encrypts, in the database, the key of each connector a workspace adds. At least 32 characters. | With white space at its start or its end: refused. Equal to FRUITBACK_IDENTITY_SECRET, white space aside, or set without FRUITBACK_DATABASE_URL: refused. Changed or lost: every connector answers 502, and each workspace adds its key again. |
FRUITBACK_AUTH_SECRET |
none: required | What better-auth signs the session cookie of the console with. The worker builds better-auth when it starts, says so in its log, and serves its sign-in under /console/auth. At least 32 random characters. Make one with openssl rand -base64 32. |
Absent: refused: an administrator can always sign in, and the worker makes no secret. Under 32 characters, or with white space at its start or its end (a value of spaces, a line that kept one): refused. Equal to FRUITBACK_IDENTITY_SECRET or FRUITBACK_SECRETS_KEY, white space aside: refused. With a BETTER_AUTH_… variable in the environment: refused, because better-auth reads those over what the worker gives it. Changed or lost: every session of the console ends, and everybody signs in again. |
FRUITBACK_AUTH_GITHUB_OAUTH |
none: no sign-in with GitHub | A GitHub OAuth App, as <client id>:<client secret>. Its callback is <FRUITBACK_PUBLIC_URL>/console/auth/callback/github. |
No colon: refused. Without FRUITBACK_AUTH_SECRET: refused. |
FRUITBACK_AUTH_TRACK_IP |
none: no address and no user agent kept | 1: better-auth keeps the address and the user agent of each session of the console, and limits by address too, with the address the worker read behind its trusted proxies. |
Any value but 1: refused. Without FRUITBACK_AUTH_SECRET: refused. |
FRUITBACK_SIGN_UP |
invited: the people a workspace invites, and nobody else |
Who may create an account. invited: a sign-in that would create an account is refused, except for an address that a workspace invited and whose invitation waits, the first account of the instance, and the link of FRUITBACK_FIRST_ADMIN or of admin-link. The console tells a person who is refused to ask for an invitation. anyone: every person who signs in gets an account, and can make a workspace. A person who has an account signs in with either value. |
Another value, Anyone included: refused, with the two values. |
FRUITBACK_FAKE_LINEAR |
none | The old spelling of FRUITBACK_STORE=memory. Deprecated. |
In the image it is ignored, and the boot log says so. Delete the line. |
FRUITBACK_DEV_DATABASE |
none | Development only: pglite gives the worker of pnpm dev a PostgreSQL in its process. Compose does not pass it. |
In the image: refused, /health answers 503 and names it. |
FRUITBACK_DEV_VISIBILITY |
none | Development only: read by the worker of pnpm dev, for the site it makes. Compose does not pass it. |
The image does not read it. |
FRUITBACK_DEV_LINEAR |
none | Development only: fake makes the worker of the bench of the extension send the notes of its site to a fake Linear. Compose does not pass it. |
The image does not read it. |