Self-Hosting
Configuration Reference
Configuration Reference
Everything is configured through environment variables. They come from the .env file in your install directory, which docker-compose.yml loads via env_file. Variables marked compose only are consumed by the compose file itself and never reach the application.
Edit .env, then apply:
cd docu.nexus
$EDITOR .env
docker compose up -d
Compose recreates only the containers whose configuration changed.
Core
| Variable | What it does | Default |
|---|---|---|
NODE_ENV | production serves the built SPA from disk and marks session cookies Secure. development enables pretty logs. | development |
PORT | HTTP API port inside the container. | 3000 |
COLLAB_PORT | Hocuspocus WebSocket port inside the container. | 3001 |
APP_URL | Public origin. Used for OAuth callback URLs and as the CORS fallback. No trailing slash. | http://localhost:3000 |
CORS_ORIGIN | Allowed browser origin. Falls back to APP_URL, then to http://localhost:5173. | unset |
LOG_LEVEL | pino level: trace, debug, info, warn, error, fatal. | info |
WEB_DIST_PATH | Where the built SPA lives. Only read when NODE_ENV=production. | ./apps/web/dist |
COOKIE_SECURE | Whether session cookies are marked Secure. A browser refuses to store a Secure cookie delivered over plain http, so an instance without TLS must set this to false or sign-in succeeds and every request after it is a 401. The installer sets it for you when APP_URL is http://. Remove it once TLS is in front. Unrecognised values are rejected rather than quietly treated as false. | follows NODE_ENV (secure in production) |
APP_VERSION | The release this build is. Baked into the image at build time, so you do not set it. It is listed here because it is what Admin → About reports and what a support conversation asks for. | the release tag, or the commit it was built from |
DOCUNEXUS_UPDATE_CHECK_URL | Where Check for updates reads the current release number. Set it to empty to switch update checking off entirely, and the control disappears. Read only when an administrator presses the button; an instance never contacts it on its own. | https://docu.nexus/dl/latest |
APP_IMAGE | Compose only. Image to run. The installer rewrites this to whatever it loaded. | the image the installer loaded |
COMPOSE_PROJECT_NAME | Compose only. Owns your data. Compose scopes every named volume by project, so this value is what makes postgres_data resolve to your database. Changing it on a running install silently points the stack at a new, empty one. Set once by the installer; never change it. | docunexus |
HOST_PORT | Compose only. Host port mapped to container 3000. Your reverse proxy’s main upstream must point at it. | 3000 |
HOST_COLLAB_PORT | Compose only. Host port mapped to container 3001. Your reverse proxy’s /collab route must reach it. | 3001 |
Database
| Variable | What it does | Default |
|---|---|---|
DATABASE_URL | Connection string. The production compose builds it from DB_PASSWORD. | postgres://docunexus:docunexus@localhost:5433/docunexus |
DB_PASSWORD | Compose only. Password for the docunexus Postgres role. Used by both containers. | none, you must set it |
DB_PROVIDER | Forces the dialect when the URL scheme is ambiguous: postgres, mysql, mssql. | derived from the URL scheme |
DB_POOL_MAX | Maximum pooled connections. | 10 |
REDIS_URL | Redis connection for the job queue. | redis://localhost:6380 |
MIGRATE_ENABLED | Run pending migrations at container start. Any value other than true skips them. | true |
SEED_ENABLED | Seed demo data at container start. Pinned to false in docker-compose.yml, which outranks .env, so you cannot switch it on from there and should not try. The account it creates has a published password. | false, pinned |
SEED_EXAMPLE_CONTENT | Seed the example content nexus at container start. Separate from SEED_ENABLED: it creates no accounts, and it stands down once an operator declines example content in the setup screen. Set to false for an empty instance. | true |
MIGRATIONS_PATH | Nothing. The migrations are compiled into the runner, so there is no directory to point it at. Kept in this table because setting it is harmless and the runner says once at start that it ignored it. | unset |
Authentication
| Variable | What it does | Default |
|---|---|---|
JWT_SECRET | Signs session tokens. Changing it logs everyone out. | change-me-in-production |
ALLOW_REGISTRATION | Pins self-signup at deployment level. Setting it at all makes the deployment own the answer, and Admin, then Authentication shows registration read-only. Only the exact string true enables signup; anything else pins it off. Leave it unset to manage registration from the admin panel. | unset, admin panel decides (signup off by default) |
OAUTH_GITLAB_CLIENT_ID | GitLab application ID. GitLab sign-in is set up in Admin, then Authentication, then Single sign-on; this and the next three are read once, at the first start where that screen has no GitLab settings and both the ID and the secret are set, and are imported into it. After that the screen is in force and these are ignored. | unset |
OAUTH_GITLAB_CLIENT_SECRET | GitLab application secret. Imported once, as above. | unset |
OAUTH_GITLAB_URL | GitLab base URL for self-hosted instances. Imported once, as above; until then it also names the GitLab a Kanban board with no connection reaches. | https://gitlab.com |
OAUTH_GITLAB_SYNC_GROUP_ID | Group for member sync. Imported once, as above. | unset |
OAUTH_GITLAB_SYNC_TOKEN | A shared GitLab access token. The member sync uses it when the administrator has no GitLab sign-in token, and a Kanban board with no connection uses it for anybody who has not signed in with GitLab, so GitLab sees one shared account and everybody sees the same issues. Boards move cards and edit issues, so it needs the api scope; with read_api every change on a board is refused. Still read from the environment, and the member sync and Kanban only send it to the GitLab OAUTH_GITLAB_URL names. | unset |
OAUTH_MICROSOFT_CLIENT_ID | Enables “Sign in with Microsoft” when set together with the secret. | unset |
OAUTH_MICROSOFT_CLIENT_SECRET | Azure AD client secret. | unset |
OAUTH_MICROSOFT_TENANT_ID | Directory tenant. Use common for any Microsoft account. | common |
Instance administrators
The instance administrator is the account that answers for the whole deployment: licensing, the directory connections, and the list of who else is an instance administrator. It is not a nexus role. These three variables are the deployment’s own say in who holds it. The last two, while set, outrank what Admin, then Instance Administrators stores.
| Variable | What it does | Default |
|---|---|---|
INSTANCE_ADMIN_EMAILS | Addresses made instance administrator at every start and every sign-in. Separated by new lines, semicolons or commas. The account must already exist, and the grant is kept when the line is removed. This is the lock-out recovery lever; see Nobody can administer. | unset |
INSTANCE_ADMIN_GROUPS | Groups whose members hold instance administrator, checked at every sign-in and withdrawn when the membership goes. A directory group is its full DN. Separated by new lines or semicolons, never commas: a comma is part of a DN. While it is set, the panel shows the list read only. | unset |
INSTANCE_ADMIN_LDAP_CONNECTION | The id of the one directory connection whose groups may grant instance administrator. Groups reported through any other connection are ignored, and with none named no directory grants anybody. It lets nobody sign in; that is each connection’s Use for sign-in switch. | unset |
A list that arrives in INSTANCE_ADMIN_GROUPS as cn=admins, ou=groups, dc=example has been split on its commas and matches nobody. Write each DN whole, and separate them with semicolons:
INSTANCE_ADMIN_GROUPS="CN=Docs Admins,OU=Groups,DC=corp,DC=example;CN=IT,OU=Groups,DC=corp,DC=example"
To find a connection’s id, run this from your install directory:
docker compose exec -T postgres psql -U docunexus -d docunexus -c \
"SELECT id, name, url FROM ldap_configurations;"
LDAP
These variables do not switch LDAP on, and they do not change a saved connection. They fill in the form for a new connection at Admin, then Authentication, then LDAP, and nothing else. Sign-in reads what that form saves, and directory sign-in needs a saved connection with Use for sign-in on and the LDAP / Active Directory switch on the Sign-in methods tab of the same section. Set them when you deploy from configuration management and want the values already typed in for whoever finishes the setup. See LDAP and Active Directory Setup.
| Variable | What it does | Default |
|---|---|---|
LDAP_ENABLED | Nothing. Kept only so that setting it makes the form say it was prefilled. | false |
LDAP_URL | Directory URL, read into the form’s Transport, Host and Port. | ldap://localhost:389 |
LDAP_STARTTLS | Makes the transport StartTLS. Never with an ldaps:// URL: the form then opens with no transport chosen, and will not save until one is. | false |
LDAP_TLS_REJECT_UNAUTHORIZED | Verify the directory’s certificate. Set false only for self-signed test rigs. | enabled |
LDAP_BIND_DN | Service account DN. | unset |
LDAP_SEARCH_BASE | Base DN for user lookup. | dc=example,dc=com |
LDAP_SEARCH_FILTER | User filter template. | built-in default |
LDAP_SEARCH_SCOPE | base, one, or sub. | sub |
LDAP_USER_BIND_DOMAIN | Appended to the username for direct bind (Active Directory style). | unset |
LDAP_USERNAME_ATTRIBUTE | Username attribute. | uid |
LDAP_EMAIL_ATTRIBUTE | Email attribute. | mail |
LDAP_DISPLAY_NAME_ATTRIBUTE | Display name attribute. | cn |
LDAP_FIRST_NAME_ATTRIBUTE | Given name attribute. | givenName |
LDAP_LAST_NAME_ATTRIBUTE | Surname attribute. | sn |
LDAP_GROUP_ATTRIBUTE | Group membership attribute. | memberOf |
LDAP_AUTO_PROVISION | Create accounts on first sign-in. Set false to disable. | enabled |
LDAP_SYNC_ON_LOGIN | Refresh attributes on each sign-in. Set false to disable. | enabled |
There is no variable for the bind password, the CA certificate or the connection name. Type those into the form, where a save checks a new bind password against the directory before storing it.
Retired and deprecated
The app writes a line to its log at every start while one of these is set, so docker compose logs app | grep -i ldap shows whether yours still are.
| Variable | Status |
|---|---|
LDAP_BIND_CREDENTIALS | Retired, does nothing. It filled in the bind password of the next new connection, whatever host that connection named. |
LDAP_ADMIN_GROUPS | Retired, does nothing. A directory no longer makes anybody a nexus admin. For instance administrators, use INSTANCE_ADMIN_GROUPS above. |
LDAP_DEFAULT_ROLE | Retired, does nothing. A directory no longer places anybody into a nexus; an admin of each nexus picks the role when they place a person. |
LDAP_SIGNIN_CONNECTIONS | Deprecated, and removed in a later release. While set it can only narrow sign-in: a connection whose id it does not name signs nobody in, whatever its Use for sign-in switch says. It never switches one on. Ids are separated by spaces, commas or semicolons. Keep it unchanged until the release that removes it: rolling back to the previous version restores the database but not .env, and that version reads this variable first. See Rolling back. |
Collaboration
| Variable | What it does | Default |
|---|---|---|
ENABLE_COLLABORATION | Set to false to skip starting the Hocuspocus server. Any other value starts it. Without it, pages cannot be edited or published from the editor. | enabled |
Confluence
The Confluence surface is off by default. Import, export, the webhook
receiver, sync, and the background sync scheduler are not started, the
/api/confluence/* endpoints answer 404, and the Import and Sync
sections do not appear in the Admin panel. It ships off because the export half
of the surface is not finished: those endpoints check that a caller is signed in
and stop there, without checking whether that account may read the page or the
space. Switching it on is a decision to accept that on an instance where every
signed-in account is already trusted with every page.
One line in .env turns the whole subject back on, then restart:
ENABLE_CONFLUENCE=true
| Variable | What it does | Default |
|---|---|---|
ENABLE_CONFLUENCE | The whole Confluence subject: import, export, webhook, sync, and the sync scheduler. Only true, 1, yes, or on switch it on; anything else, including a misspelt value and an empty one, leaves it off. | off |
ENABLE_CONFLUENCE_SYNC | Narrower switch, and it only matters when ENABLE_CONFLUENCE is on: set it to false to keep the surface but skip the background sync scheduler. It cannot switch anything on by itself. | enabled, but inert while ENABLE_CONFLUENCE is off |
SYNC_CHECK_INTERVAL | Sync poll interval in milliseconds. Read only when the scheduler runs. | 60000 |
Nothing stored is lost while it is off. Saved connections, sync configurations, and past import jobs stay in the database and come back exactly as they were.
Storage
| Variable | What it does | Default |
|---|---|---|
STORAGE_PATH | Root directory for uploads. Images land in uploads/YYYY/MM, attachments in files/YYYY/MM. | ./storage |
The production compose sets STORAGE_PATH=/app/storage and backs it with the app_storage volume. Set it explicitly. A few Confluence import paths fall back to ./uploads when it is unset, which splits your files across two directories.
Build-time front end
These are baked into the SPA bundle at build time, not read at runtime.
| Variable | What it does | Default |
|---|---|---|
VITE_BASE_URL | Absolute API base. Leave empty when the proxy serves the API and the SPA on one origin. | empty (same origin) |
VITE_COLLAB_URL | Explicit WebSocket URL. Overrides the automatic wss://<host>/collab behavior. | unset |
Git Docs
All optional; the plugin works with none of them set. Repo clones are cached under STORAGE_PATH/gitdocs/.
| Variable | What it does | Default |
|---|---|---|
GITDOCS_PUBLIC_URL | Public base for the webhook payload / artifact upload URLs the setup form shows. Only needed when it differs from APP_URL. | falls back to APP_URL |
GITDOCS_ALLOWED_HOST_PATTERN | Regex of additionally allowed clone hosts. Private, loopback, and link-local hosts are refused unless they match (SSRF guard). Set this for an internal git server, e.g. ^git\.internal\.example\.com$. | unset |
GITDOCS_CLONE_TIMEOUT_MS | Timeout for any git subprocess. | 120000 |
GITDOCS_MAX_FILE_MB | Largest repo file the plugin will read and render. | 5 |
GITDOCS_ENABLE_POLL | Poll repos for changes as a fallback where webhooks are not possible. Must be the exact string true. | disabled |
GITDOCS_POLL_INTERVAL_MIN | Poll interval in minutes. | 15 |
GITDOCS_ARTIFACT_MAX_MB | Size cap for one uploaded artifact site. | 200 |
GITDOCS_ARTIFACT_MAX_FILES | File-count cap for one uploaded artifact site. | 10000 |
GITDOCS_ARTIFACT_KEEP | Artifact versions kept per page; older ones are evicted on upload. | 5 |
GITDOCS_ARTIFACT_HOST | Wildcard domain artifact sites are served from, one hostname per version. Set it and sites that reference /css/... work unmodified. Needs a wildcard DNS record and certificate; see Git Docs. | unset |
Licensing
| Variable | What it does | Default |
|---|---|---|
DOCUNEXUS_LICENSE_KEY | Pins the instance license at the deployment level. When set, it takes precedence over any key pasted in Admin, and the Admin UI refuses to overwrite it. | unset (the key comes from the database; with none, the instance evaluates and then becomes read-only) |
Set this if you manage secrets through your orchestrator. Leave it unset if you prefer pasting the key once in Admin, Licensing.