Semantius Logo
DocsSelf-HostingSettings Reference

Settings Reference

On this page

Your whole installation is configured by one file called .env, sitting next to docker-compose.yml in your variant folder. It is created for you by ./setup-env.sh, it is never committed to version control, and it is the only file in that folder you are meant to edit.

After changing anything here, apply it with ./up.sh, which keeps your data. A plain restart is not enough: several of these values are read when a container is created, so a restarted container still carries the old ones.

Most people never touch more than the first two groups below.

The defaults shown are those of local-semantius-idp. The Entra variant adds required settings and changes some defaults, listed under Entra ID Variant. On Dokploy, the same settings live in the deployment’s Environment tab. Choose or Build Your Variant explains the variants.

Secrets

Three values are unique to your installation. setup-env generates all three when it creates the file, so a stack started the documented way already has good ones. You only need this section if you copied the example file by hand, or you are rotating something.

SettingWhat it protects
IDP_SECRETSigns everyone’s sessions and encrypts the keys used to sign logins
POSTGRES_PASSWORDFull administrative access to the database
SEMANTIUS_AUTHENTICATOR_PASSWORDThe account the API uses to reach the database

Generate replacements with openssl rand -hex 24 for the two passwords and openssl rand -base64 48 for IDP_SECRET.

Use hexadecimal for the two passwords specifically. They are inserted into database connection addresses, where characters like @, :, / and + break parsing. openssl rand -base64 produces some of those, so it is the wrong tool for these two. IDP_SECRET is never used that way, so base64 is fine there.

IDP_SECRET has to be right before the first start. It encrypts the stored signing keys, so changing it afterward logs everyone out and leaves those keys unreadable. Treat a change to it as a key rotation rather than a settings tweak.

The two passwords are written into the database when it is first created, so changing them on a running stack means changing them in the database too, or recreating with ./create.sh.

Your Public Address

These are the settings a real deployment has to change. On a laptop they all default to localhost and can be left alone. See Going Live for the full walkthrough.

SettingDefaultWhat it is
SITE_ADDRESS:80The address the front door serves. Set it to your bare domain for automatic HTTPS, or leave it when something else handles certificates
IDP_BASE_URLhttp://localhost:3000/idpThe sign-in system’s own address, including the /idp part. All email links are built from this
PUBLIC_WEB_ORIGINhttp://localhost:3000Where the app lives, with no path. Used to check that sign-ins return to the right place
PUBLIC_API_URLhttp://localhost:3000/gateway/restThe address the API documentation tells people to call. Has to be absolute
WEB_PORT3000The port the front door is published on locally. Ignored once you publish 80 and 443 for a real domain

IDP_BASE_URL keeps the /idp suffix and PUBLIC_WEB_ORIGIN has none. That is not a mistake in the defaults: one is the sign-in system’s address, the other is the app’s.

Versions

Every piece runs the newest published version by default, and ./up.sh and ./create.sh download it again each time they run. A plain restart or a reboot does not. Convenient while trying things out, wrong for a server you depend on. Pin all three before going live.

SettingDefaultControls
SEMANTIUS_DB_VERSIONlatestThe database image. Released tags end in -pg18
SEMANTIUS_APP_VERSIONlatestThe web app
SEMANTIUS_IDP_VERSIONlatestThe built-in sign-in system
APP_PULL_POLICYalwaysSet to missing to run a web app image you built and tagged yourself, instead of downloading it
IDP_PULL_POLICYalwaysThe same, for the sign-in system

You can also pin the database for a single run by passing the tag on the command line, for example ./up.sh 0.4.0-pg18. The published versions are listed on each image’s package page: database, web app, sign-in system.

The other images have no version setting yet. The API server, connection pooler, API docs viewer and key fetcher run latest, and the front door runs Caddy 2-alpine. To hold one back, set its image tag in a docker-compose.override.yml.

Email

SettingDefaultEffect
RESEND_API_KEYunsetWithout it, password resets, address verification and notifications are switched off and hidden. Set it to a Resend key to turn them on

Leaving it unset is reasonable on a laptop, where an administrator creates every account. On anything real, people lock themselves out.

Database and Sample Data

SettingDefaultWhat it does
POSTGRES_DBsemantiusName of the database that is created and served
NWINDTRUELoads the Northwind sample dataset. Remove the line for an empty database
POSTGRES_PORT5434Port for connecting a database client directly
PGBOUNCER_PORT6432Port for the pooled connection your own applications should use

NWIND only takes effect when the database is first created. Changing it later does nothing, because sample data is loaded once at setup time. To change your mind, change the line and run ./create.sh, which rebuilds the database from scratch and deletes what is in it.

The ports default to unusual numbers deliberately, so they do not collide with a PostgreSQL you may already run on 5432.

Both ports are published on purpose: 6432 for your own applications that talk SQL, 5434 for administration and migrations with the pg_semantius CLI. They are published on every network interface, not only on the machine itself. That is harmless on a laptop, but on a public server they should be restricted to the machines that need them. The interface is set in the compose file rather than here, and Going Live shows how.

Identity

You can leave every one of these alone if you use the built-in sign-in system, which is what the defaults describe. They matter when you point the stack at a provider of your own, which is usually better done with the Entra ID variant than by editing these.

SettingDefaultWhat it is
VITE_OAUTH_CONFIGthe built-in systemWhere the app discovers its sign-in addresses. Relative by default, so it follows whatever domain the app is opened on
VITE_OAUTH_CLIENT_IDpublic-clientWhich registered application the web app signs in as
VITE_OAUTH_AUDIENCEsemantius://apiWhat the app asks its logins to be valid for
IDP_JWT_AUDIENCEsemantius://apiWhat the built-in system issues logins for. One setting moves both sides
JWKS_URLthe built-in systemWhere the API fetches the keys it checks logins against. Set it empty to derive them from your own provider instead
SEMANTIUS_IDP_TYPEsemantiusWhich provider is behind this deployment. Informational only, used in logs and support. Set it to custom if you repoint the stack
CLI_OAUTH_CLIENT_IDsemantius-cliWhich registered application the semantius CLI signs in as. Not the web app’s
VITE_OAUTH_SCOPEemptyThe scopes the app asks for. Empty takes what the provider advertises. With Entra, it has to name the API, or Entra issues a token for Microsoft Graph instead
VITE_OAUTH_USERINFO_ENDPOINTemptyWhere the app reads the signed-in person’s name and email. Empty takes it from the provider’s discovery document
PGRST_JWT_AUDemptyThe audience the API requires in every token. Set it for a provider that shares signing keys across customers, as Entra does
VITE_BACKEND_TYPEself_hostedWhich account menu the app shows. self_hosted links to the built-in system’s account pages; custom uses the menu in VITE_UI_CUSTOMIZER
VITE_UI_CUSTOMIZERemptyThe account menu for custom, as JSON. Typically one entry pointing at your provider’s own profile page

Two of these are easy to misread. JWKS_URL set to empty is meaningful and different from removing the line: empty means “work it out from the provider’s discovery document,” which is what an external provider wants. And SEMANTIUS_IDP_TYPE is not derived from anything, so a stack repointed at another provider keeps announcing the built-in one until you change it by hand.

Multiple Domains

SettingDefaultWhat it does
IDP_DYNAMIC_ISSUERfalseMakes sign-in follow whatever domain a request arrives on, instead of one fixed address
TRUSTED_PROXIESunsetWhich networks are allowed to tell the stack who a visitor really is

IDP_DYNAMIC_ISSUER is what lets a domain attached after deployment work immediately, and it is the reason the Dokploy blueprint handles domains so gracefully. Turning it on is a statement about your network, not just a convenience: it is only safe when something in front of the stack forwards a closed set of known domains and nothing can bypass it. Dokploy’s router satisfies that. A plain server running the front door directly does not, because it answers to any name pointed at it, so leave it off there.

Email links deliberately do not follow the request, even with this on. They are always built from IDP_BASE_URL, which is what keeps a faked request from producing a password reset link pointing somewhere else. After attaching a real domain, update IDP_BASE_URL once.

TRUSTED_PROXIES unset means nothing is trusted, which is correct when the front door is the edge. Behind another router it has to list that router’s network, or every visitor appears to come from the same place and rate limits and audit records become useless.

Rarely Changed

SettingDefaultWhat it does
DOCS_API_ROUTE/gateway/restThe API address the documentation page uses. /gateway/rest is semantius-idp’s API gateway; the Entra variant uses /rest, the API itself
DOCS_SPEC_URL/gateway/restWhere the documentation page loads the API description from, if it should differ from DOCS_API_ROUTE
PGRST_DB_POOL_MAX_IDLETIME1800Seconds an unused database connection is kept open
PGRST_JWT_ROLE_CLAIM_KEY.roleWhich part of a login token names the permission level. Only changes with an external provider that cannot use the standard name

Set in the Compose File, Not Here

A few values are deliberately fixed in docker-compose.yml rather than exposed in .env, so that a stray setting cannot redirect the stack somewhere unexpected. You will see them if you read the compose file and wonder why they are not configurable:

  • The database administrator username, always postgres.
  • Where the sign-in system reads its configuration from, and which part of the database it uses.
  • The setting that puts the web app in self-hosted mode, so it never contacts the managed service. It is pinned because this deployment is the self-hosted backend, and there is no case where one of its own containers should be asking a hosted service about itself.

Entra ID Variant

In local-entra-idp, Entra signs people in, so the semantius-idp settings do not appear in its .env. Six settings that connect the stack to your tenant are required instead. create and up stop and name any that are empty. The Entra setup script fills them in for you.

Required settingWhat it holds
VITE_OAUTH_CONFIGYour tenant’s OpenID Connect discovery address
VITE_OAUTH_CLIENT_IDThe web app registration’s id
VITE_OAUTH_AUDIENCEThe API registration, which tokens are issued for
VITE_OAUTH_SCOPEThe scopes, including the API’s own scope
PGRST_JWT_AUDThe same API registration, checked by the API on every token
CLI_OAUTH_CLIENT_IDThe semantius CLI registration’s id

It also changes these defaults, so they already fit Entra:

SettingEntra default
SEMANTIUS_IDP_TYPEentra
JWKS_URLempty, so the keys come from Entra’s discovery document
PGRST_JWT_ROLE_CLAIM_KEY.roles[0], because Entra puts app roles in a list
DOCS_API_ROUTE/rest
VITE_BACKEND_TYPEcustom
VITE_UI_CUSTOMIZEROne menu entry linking to the Microsoft account page

Next Steps