Configuration
Serelane is configured entirely through the environment, so a container needs no configuration file and nothing mounted to hold one.
Every setting is validated at boot, and every problem is reported at once rather than one restart at a time. A container with no environment at all boots on the defaults below.
Core
| Variable | Default | What it does |
|---|---|---|
SERELANE_BASE_URL | derived from the Host header | The absolute URL this instance is reachable at. It goes into emails and link previews, which a relative address cannot serve. Must be an absolute http or https URL, and a trailing slash is trimmed. Behind a proxy that does not set Host, the guess is wrong, so set this. |
SERELANE_DATA_DIR | ./data | Where the database, the uploads and the generated secret key live. The only path Serelane writes to. The container image sets this to /data. |
SERELANE_LOG_LEVEL | info | One of debug, info, warn or error. |
SERELANE_PORT | 8080 | The TCP port to listen on. Between 1 and 65535. |
SERELANE_SECRET_KEY | generated into the data directory | Signs sessions, sign-in links and anonymous cookies. At least 32 characters. Left unset, one is generated into secret.key and the instance says so loudly at boot: back that file up, because losing it signs everyone out and invalidates every pending sign-in link. |
SERELANE_TRUSTED_PROXIES | empty | Comma-separated CIDR blocks, or bare addresses for a single host. Only these networks’ X-Forwarded-For is believed. Unset means the header is ignored entirely, so every visitor appears to come from the proxy, which collapses anonymous vote deduplication and rate limiting onto one address. |
Database
| Variable | Default | What it does |
|---|---|---|
SERELANE_DB_DRIVER | sqlite | The only accepted value today. Postgres is a planned driver, not a shipped one, and any other value fails at boot. |
SERELANE_DB_DSN | serelane.db inside the data directory | Where the database lives. Defaulting inside the data directory is what makes moving that directory move the database with it. |
Mail is optional.
With no SERELANE_SMTP_HOST, sign-in links and notifications are written to the log instead of sent, so magic-link identity does not work and nobody is notified of anything.
| Variable | Default | What it does |
|---|---|---|
SERELANE_SMTP_FROM | none | The envelope sender. Required once SERELANE_SMTP_HOST is set; the instance refuses to boot without it. |
SERELANE_SMTP_HOST | empty | The relay. Setting it is what turns mail on. |
SERELANE_SMTP_PASSWORD | empty | The password for the relay. |
SERELANE_SMTP_PORT | 587 | The relay’s port. |
SERELANE_SMTP_TLS | starttls | One of starttls (port 587, a plaintext session upgraded), implicit (port 465, TLS from the first byte) or none (in the clear, which is only ever right for a relay on localhost). true, 1 and yes are accepted as synonyms for starttls, and false, 0 and no for none, because this setting used to be a boolean. |
SERELANE_SMTP_USERNAME | empty | The username for the relay. |
Spam and abuse
The challenge is instance configuration because the keys are per deployment and because it decides whether the process can verify a solution at all. Which boards actually use it is a per-project setting, in the database.
| Variable | Default | What it does |
|---|---|---|
SERELANE_CAPTCHA_PROVIDER | empty | altcha, turnstile, or empty for none. Altcha is a self-hosted proof of work and involves no third party, which is why it is the one to reach for first on a self-hosted instance. |
SERELANE_CAPTCHA_SECRET | the instance secret key | Signs altcha challenges and authenticates to Turnstile. Required for turnstile. In altcha mode it falls back to SERELANE_SECRET_KEY, so the privacy-preserving option needs no configuration beyond switching it on. |
SERELANE_CAPTCHA_SITE_KEY | empty | The public half a third-party widget renders with. Required for turnstile. |
Sign-in
Both providers reject an explicit email_verified: false assertion.
The console requires the ID token to assert email_verified: true, while a board also accepts an omitted optional claim after retrieving the address from subject-matched UserInfo.
A provider that omits the claim entirely, which Azure AD and SAML-bridged identity providers do, is refused exactly as if it had sent false — omitting an assertion is not making one.
This applies to the admin console provider and the board provider alike.
Admin console
Configures the OIDC provider the operator console signs in through.
This is separate from the board provider below: setting one never configures the other, so an instance can require a second factor of its operators without requiring one of every visitor who votes on a roadmap.
The client secret is optional here too, since every authorisation request carries a PKCE S256 challenge.
Register <base URL>/api/v1/admin/auth/callback as the redirect URI with the provider, using the instance’s own base URL in place of <base URL>.
This is a different path from the board provider’s callback below, so a provider can tell the two sign-ins apart.
| Variable | Default | What it does |
|---|---|---|
SERELANE_ADMIN_OIDC_CLIENT_ID | empty | The application registered with the provider. Required once SERELANE_ADMIN_OIDC_ISSUER is set. |
SERELANE_ADMIN_OIDC_CLIENT_SECRET | empty | Optional. Every authorisation request carries a PKCE S256 challenge, so a public client is a complete configuration on its own. |
SERELANE_ADMIN_OIDC_ISSUER | empty | The provider’s base URL, the one its discovery document hangs off. Required once SERELANE_ADMIN_OIDC_CLIENT_ID is set. |
SERELANE_ADMIN_OIDC_LABEL | empty | What the sign-in button says. |
SERELANE_ADMIN_OIDC_ROLE | empty | A role the provider has to assert before an admin sign-in is allowed. Empty leaves the check off, which is the behaviour of every earlier release. |
SERELANE_ADMIN_OIDC_ROLE_CLAIM | roles | Which claim carries the roles. A dotted path walks into a nested claim, such as realm_access.roles. Only read once SERELANE_ADMIN_OIDC_ROLE is set. |
SERELANE_ADMIN_OIDC_ROLE_ORG | empty | Narrows the grant to one organisation, named by its id or by its primary domain, for providers whose role claim records where each role was granted. |
SERELANE_ADMIN_OIDC_SCOPES | empty | Space-separated. Empty means the default set. |
Requiring a role
Without SERELANE_ADMIN_OIDC_ROLE, any account on the instance whose address the provider has verified can sign in to the console.
That is enough when the provider only knows operators.
It is not enough when one provider serves the boards and the console, because everyone who can sign up can then reach the console door with an address that might already have an account behind it.
Naming a role closes that door.
The role permits an admin sign-in and does nothing else: it never creates an account, never grants is_superadmin, and never changes a project membership.
Both checks have to pass, so an operator still needs the role at the provider and an existing account on this instance whose address the provider has verified.
Nothing in OpenID Connect says where roles live, so the claim is named rather than guessed. Three shapes are understood, which between them cover what providers send:
The third is what providers with a tenant model send, mapping each role to the organisations it was granted in.
SERELANE_ADMIN_OIDC_ROLE_ORG matches either half of that pair, so 178 and acme.example both name the same organisation.
Role names are matched exactly, so Admin does not satisfy a requirement for admin.
The ID token is read first. A token that carries the claim is the provider’s answer, whatever it says. A token that does not carry the claim at all is a provider that keeps roles at its UserInfo endpoint, which is asked next, and whose response is only believed when it is about the same subject. Every question that cannot be answered is answered no: a missing claim, a claim in a shape nothing understands, an unreachable UserInfo endpoint, and an organisation a list-shaped claim cannot express all refuse the sign-in.
A refused sign-in tells the browser the same generic thing every other refusal does. The specific reason goes to the process log and the audit trail, which is where to look when a role that was granted is not being seen.
Some providers only put roles in a token when the scope asks for them.
SERELANE_ADMIN_OIDC_SCOPES is how that scope is added, and it replaces the default set rather than extending it, so list openid and email alongside whatever the provider needs.
Two worked examples:
The ZITADEL example scopes the token to project 271828 so that project’s roles are the ones asserted.
A deployment that would rather pin the project in the claim name can set SERELANE_ADMIN_OIDC_ROLE_CLAIM to urn:zitadel:iam:org:project:271828:roles instead, which refuses a role held in any other project even if the token carries several.
Check the claim your provider actually emits before turning the requirement on, by reading a decoded ID token or the UserInfo response for an operator who already has the role.
Signing in to the console through a provider covers what the requirement means for sessions that already exist, and how to move a deployment onto it.
Board provider
Configures one OIDC provider for every board on the instance. It is optional, and when it is set it replaces whatever a project has stored, because a deployment that names its provider here should not have that quietly overridden from a form.
Register <base URL>/api/v1/public/auth/callback as the redirect URI with the provider, using the instance’s own base URL in place of <base URL>.
Identities are joined on the verified email address, not on the provider.
Pointing an instance at a different provider lets anyone who controls that same address there sign in as the existing identity.
They inherit its votes, comments and subscriptions.
Only repoint SERELANE_OIDC_ISSUER at a provider you trust to guard the same addresses as the one it replaces.
| Variable | Default | What it does |
|---|---|---|
SERELANE_OIDC_CLIENT_ID | empty | The application registered with the provider. Required once SERELANE_OIDC_ISSUER is set. |
SERELANE_OIDC_CLIENT_SECRET | empty | Optional. Serelane always sends a PKCE challenge, so a public client is a complete configuration on its own, and some providers will not issue a secret for one. |
SERELANE_OIDC_ISSUER | empty | The provider’s base URL, the one its discovery document hangs off. Required once SERELANE_OIDC_CLIENT_ID is set. |
SERELANE_OIDC_LABEL | empty | What the sign-in button says. |
SERELANE_OIDC_SCOPES | empty | Space-separated. Empty means the default set. |
First run
These are read on every invocation but only used by serelane bootstrap, which creates the first admin account without a browser.
They live here rather than in flags so that an operator provisioning from a compose file finds them beside every other setting.
| Variable | Default | What it does |
|---|---|---|
SERELANE_BOOTSTRAP_DEMO | on for a dev instance, otherwise off | Fills the example board with content. |
SERELANE_BOOTSTRAP_EMAIL | none | The first admin account’s address. Must contain an @. |
SERELANE_BOOTSTRAP_NAME | none | The display name on that account. |
SERELANE_BOOTSTRAP_PASSWORD | none | That account’s password. Checked for length up front, so a typo fails the command rather than creating an account nobody can sign in to. Not trimmed: leading and trailing spaces are a legitimate part of a password, and silently changing what you set would lock you out. |
Without both an email and a password, serelane bootstrap refuses rather than inventing credentials.
Create the account through the setup form instead, at /setup.
Development
| Variable | Default | What it does |
|---|---|---|
SERELANE_DEV | off | Set to 1 to serve the frontend by proxying to the Vite dev server instead of from the embedded copy, and to allow serelane bootstrap to fall back to built-in credentials. Never set this on an instance anyone else can reach. |