Environment variables
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
Adminium validates its environment before it starts. On invalid input it
prints a variable | problem | hint table to stderr and exits — it does not boot
half-configured and fail later.
The complete list
Section titled “The complete list”| Variable | Required | Default | Description |
|---|---|---|---|
ADMINIUM_SECRET |
Yes | — | Derives the key encrypting stored DSNs and API keys. Minimum 16 characters. |
PORT |
No | 4600 |
Listen port. Integer 1–65535. |
HOST |
No | 0.0.0.0 |
Bind address. 127.0.0.1 to bind loopback only. |
ADMINIUM_META_URL |
No | (embedded SQLite) | Meta-store DSN: postgres://, mysql://, or sqlite:<path>. |
ADMINIUM_META_POOL_MAX |
No | 10 |
How many connections a Postgres or MySQL meta store’s pool may open. Integer 1–100. See below. |
ADMINIUM_SOURCE_POOL_MAX |
No | (per pool: 10, schema reads 5) | How many connections each pool opened against a source database may hold. Integer 1–100. See below. |
ADMINIUM_SOURCE_URL |
No | (unset) | Your own database — connected, introspected, and generated on the first boot. See below. |
ADMINIUM_DATA_DIR |
No | ./data, or ~/.adminium |
Writable directory for files, exports, backups, installed apps and add-ons, and the embedded meta store. Put it on a persistent disk: a host that empties it on every deploy loses installed apps and add-ons, whatever else is configured. Docker and the desktop app set it for you. A CLI run that does not: ./data when the working directory is a project (it holds a package.json, a .git, a Dockerfile, a go.mod…) or already holds an Adminium instance, and ~/.adminium otherwise — so npx @adminiumjs/adminium try from a home directory leaves nothing behind in it. A service should always set it, since its working directory decides the default. |
ADMINIUM_STORAGE_URL |
No | (this server’s disk) | Where uploaded and generated files live. Seeded once, on a boot with no storage destination configured. See below. |
AWS_ENDPOINT_URL_S3 |
No | (unset) | S3-compatible endpoint. Read only by the first-boot storage seed, and only when ADMINIUM_STORAGE_URL is unset. See below. |
AWS_REGION |
No | auto |
Region for the AWS storage variables. |
BUCKET_NAME |
No | (unset) | Bucket for the AWS storage variables. |
AWS_ACCESS_KEY_ID |
No | (unset) | Access key for the AWS storage variables. |
AWS_SECRET_ACCESS_KEY |
No | (unset) | Secret key for the AWS storage variables. |
ADMINIUM_BUNDLED_ADD_ONS |
No | ./add-ons-bundle |
Directory of pre-verified add-on tarballs seeded into the store at boot — see Installing add-ons. |
ADMINIUM_BUNDLED_APPS |
No | ./apps-bundle |
Directory of pre-verified app tarballs seeded at boot, so Studio → Hosted apps has apps to install without reaching the network. See below. |
ADMINIUM_LOG_LEVEL |
No | info |
fatal · error · warn · info · debug · trace |
ADMINIUM_STATIC_ROOT |
No | (auto-detected) | Serve the dashboard build from this directory instead of the auto-detected copy. |
ADMINIUM_SURFACES_DIR |
No | (unset) | Directory of built app surfaces, served at /apps/<app>/<side>/. Unset means this instance hosts no apps. See below. |
ADMINIUM_CSP_IMG_HOSTS |
No | (unset) | CSV of extra origins pictures may load from — for app data that links images hosted elsewhere. Named hosts only: no bare *, no scheme on its own. See below. |
ADMINIUM_MAP_SERVER |
No | https://tiles.openfreemap.org |
Where maps’ pictures come from: one https:// origin, or off for no maps on this install. See below. |
ADMINIUM_ROUTING_SERVER |
No | https://router.project-osrm.org |
The OSRM server asked for routes along roads, once that is switched on in Settings → Maps; or off. See below. |
ADMINIUM_TELEMETRY |
No | (unset) | Overrides the consent screen’s answer. Unset = let it stand; telemetry is opt-in either way. |
ADMINIUM_NETWORK_FEATURES |
No | on |
off on air-gapped installs — the UI stops offering webhooks, OAuth, and provider-API AI, and the server asks adminium.dev for no list of add-ons or apps. |
ADMINIUM_DESIGNER |
No | off |
live lets a Super Admin switch Adminium Designer on for this server, in Settings → AI. Anything else, and its routes do not exist. adminium design needs no variable. |
ADMINIUM_AI_ANTHROPIC_API_KEY |
No | — | An Anthropic key: a model connection the server has without anything saved in Settings → AI. See the ADMINIUM_AI_* block. |
ADMINIUM_AI_OPENAI_API_KEY |
No | — | An OpenAI key, the same way. |
ADMINIUM_AI_COMPATIBLE_BASE_URL |
No | — | The address of an OpenAI-compatible server (http://localhost:8000/v1). |
ADMINIUM_AI_COMPATIBLE_API_KEY |
No | — | The key for that server, when it wants one. |
ADMINIUM_AI_OLLAMA_BASE_URL |
No | — | The address of Ollama (http://localhost:11434). |
ADMINIUM_AI_MODEL |
No | — | The model in use, as <provider>/<model>: anthropic/claude-sonnet-5-5, ollama/qwen3. |
ADMINIUM_TRUST_PROXY |
No | off |
on when behind a reverse proxy. |
ADMINIUM_TRUSTED_PROXIES |
No | loopback,uniquelocal |
Which connections count as your proxy while ADMINIUM_TRUST_PROXY is on. See below. |
ADMINIUM_CORS_ORIGINS |
No | (off) | CSV of exact origins for split deployments. No wildcard. |
ADMINIUM_BRIDGE_ORIGINS |
No | (off) | CSV of exact origins allowed to hand this instance a connection string. No wildcard. |
ADMINIUM_PUBLIC_API_ORIGINS |
No | (off) | CSV of exact origins allowed to reach the scoped public API — plus the sentinel self for pages Adminium hosts itself. Unset means those routes are not registered at all, except under adminium dev, where it defaults to self. No wildcard. Must not overlap ADMINIUM_CORS_ORIGINS. |
ADMINIUM_RUNTIME |
No | self-host |
self-host · desktop. Set by the Electron shell only — see below. |
ADMINIUM_BOOT_TOKEN |
No | (unset) | 64 hex characters. Desktop shell only — see below. |
ADMINIUM_DESKTOP_SINGLE_USER |
No | (unset) | Desktop shell only. Mirrors the app’s “skip login on this computer” answer. |
ADMINIUM_DEMO_SEED_SCRIPT |
No | (unset) | Desktop shell only. Absolute path to the demo-database seed script. |
ADMINIUM_DISABLE_UPDATES |
No | (unset) | Desktop app only. 1 forces the update mode to disabled — see below. |
An empty string is treated as unset, so FOO= adminium start behaves like
FOO being absent.
One variable is deliberately not in this list: ADMINIUM_PROJECT_DIR. It
names the project folder a command should use, and it is read
before the project’s .env is loaded — that is, before the table above is
validated at all. It is documented with the commands that read it:
CLI reference.
Anything not on this list is ignored. Adminium validates exactly these — a misspelled variable does not fail the boot, it simply does nothing, which is why the list is worth checking against.
ADMINIUM_SECRET
Section titled “ADMINIUM_SECRET”The only required variable, and the one worth understanding.
export ADMINIUM_SECRET=$(openssl rand -hex 32)It derives — via a KDF, it is not used directly — the AES-256-GCM key that encrypts every stored DSN and provider API key at rest, and it signs the session cookie.
Under 16 characters and Adminium refuses to start:
Adminium failed to start — invalid environment configuration:
| variable | problem | hint ||-----------------|--------------------------------|-------------------------------------------------------------|| ADMINIUM_SECRET | must be at least 16 characters | set a random string of at least 16 characters, e.g. `openssl rand -hex 32` |ADMINIUM_META_URL
Section titled “ADMINIUM_META_URL”Where Adminium’s own adminium_* tables live.
ADMINIUM_META_URL='postgres://adminium:pass@meta-host:5432/adminium_meta'ADMINIUM_META_URL='mysql://adminium:pass@meta-host:3306/adminium_meta'ADMINIUM_META_URL='sqlite:/var/lib/adminium/meta.db'Unset, Adminium falls back to an embedded SQLite store under
ADMINIUM_DATA_DIR and says so at boot. That warning is not noise — it is
there so the fallback can never be your production meta store by accident.
The meta store’s engine is independent of your source database’s.
ADMINIUM_META_POOL_MAX and ADMINIUM_SOURCE_POOL_MAX
Section titled “ADMINIUM_META_POOL_MAX and ADMINIUM_SOURCE_POOL_MAX”Adminium keeps a pool of database connections for its meta store (10 by default, on Postgres or MySQL; an embedded SQLite meta store has none), and for each connected source a long-lived pool for pages and the API (10) plus short-lived ones to read the schema (5) and collect statistics (10) — up to 25 connections to one source at a busy moment.
Lower them when that is more than the database allows you: a source reached through a
connection pooler or an SSH tunnel, a managed database with a small connection limit, or a
login role with a CONNECTION LIMIT. A pool larger than a role’s limit does not wait for a
free connection — its extra connections are refused, and the page asking for one fails. Keep
ADMINIUM_SOURCE_POOL_MAX at or below a third of the data role’s limit, since the three pools
can be open together; ADMINIUM_META_POOL_MAX below the meta role’s.
ADMINIUM_META_POOL_MAX=4ADMINIUM_SOURCE_POOL_MAX=2ADMINIUM_STORAGE_URL
Section titled “ADMINIUM_STORAGE_URL”Where uploaded files, export artifacts, the branding logo and imported schema files are stored.
Leave it unset unless your host has no persistent disk. Unset means this
server’s own disk, under ADMINIUM_DATA_DIR/files — which is correct on a
Droplet, on Fly with a volume, on Render, and on anything running Docker with a
named volume.
It matters in one situation: a runtime with no persistent local disk, where files written today are gone after the next redeploy. DigitalOcean App Platform is the main one. A destination keeps files there, but not installed apps or add-ons — see Installing apps. Set this before the first boot:
ADMINIUM_STORAGE_URL: s3://my-space?endpoint=https://nyc3.digitaloceanspaces.com®ion=nyc3&pathStyle=0&accessKey=DO00EXAMPLE&secretKey=...The three grammars:
| Driver | Value |
|---|---|
| Any S3-compatible bucket | s3://<bucket>[/<prefix>]?endpoint=®ion=&accessKey=&secretKey=&pathStyle=1&publicBaseUrl= |
| A WebDAV server (a NAS, a Storage Box, Nextcloud) | webdavs://<user>:<pass>@<host>[/<path>]?publicBaseUrl= |
| Another directory on this machine | file:///absolute/path — note the three slashes |
endpoint is optional for AWS itself, whose endpoint is derived from the
region — and in exactly that case region defaults to us-east-1. With an
endpoint set it defaults to auto instead, which is what Cloudflare R2 and
Tigris want. pathStyle=1 is the default whenever endpoint is set, because
self-hosted targets (MinIO, Garage) issue no per-bucket hostnames; managed
providers that do issue them want pathStyle=0. publicBaseUrl is a CDN or
share URL under which the objects are readable without Adminium — it is used
only for the reference stored in your own table, never as a read path.
It runs once. On a boot where no storage destination exists at all, it creates one and makes it the default. On every later boot it changes nothing and says so in the log. A destination you configured in Studio is never overridden, and a value that will not parse is reported as a warning — the boot continues, because Studio is where you fix it.
Fly.io needs none of this
Section titled “Fly.io needs none of this”fly storage create provisions a Tigris bucket and writes five variables into
your app’s secrets: AWS_ENDPOINT_URL_S3, AWS_REGION, BUCKET_NAME,
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. The same seed reads those as a
fallback when ADMINIUM_STORAGE_URL is unset, so a Fly deploy is zero-config.
The storage seed is the only code in Adminium that reads an AWS_* variable.
Nothing else picks up ambient AWS credentials.
ADMINIUM_SOURCE_URL
Section titled “ADMINIUM_SOURCE_URL”The database the back office is generated from. Set it and the first boot connects it, introspects the schema, and generates the pages, so a container comes up with a working dashboard instead of an empty one:
ADMINIUM_SOURCE_URL: postgres://app:secret@db:5432/appLeave it unset and nothing changes: you connect your database in the first-run wizard, which is how every non-Docker install starts. Either way you still finish setup in the browser — this seeds the connection, never the super admin.
It runs once. After one successful connection the seed is claimed and never
runs again, so a connection you delete later stays deleted rather than
reappearing on the next docker compose up. An instance that already has
connections ignores the variable entirely and says so in the boot log.
A DSN that does not work does not stop the boot. The server starts, the connection is recorded with the driver’s own error where Studio shows it, and the next boot tries again with whatever the variable says then — so a typo is fixed where you wrote it, not in the database. That retry is the reason the failure is not claimed: a stored DSN cannot be edited through the API, only replaced.
ADMINIUM_STATIC_ROOT
Section titled “ADMINIUM_STATIC_ROOT”The directory the dashboard build is served from — the one holding the build’s
index.html. Unset, Adminium finds it automatically: the copy bundled into the
published package, else apps/dashboard/dist in a monorepo checkout. That is
right for every normal install, so most deployments never set this.
Set it when the automatic pick is wrong — a dashboard built to a custom location, or a development checkout where a stale bundled copy is shadowing a fresh build:
ADMINIUM_STATIC_ROOT=/srv/adminium/dashboard adminium startIf the directory has no index.html, Adminium logs a warning naming the path,
then falls back to the automatic candidates (or serves the API alone) rather
than refusing to boot.
ADMINIUM_BUNDLED_APPS
Section titled “ADMINIUM_BUNDLED_APPS”Apps that ship with the build. Each entry is a tarball plus its hash:
apps-bundle/ clinic-1.0.0.tgz clinic-1.0.0.tgz.integrity # sha512-…At boot they are staged into this instance’s app store, where Studio → Hosted apps offers them for install. Staging is not installing: nothing is created in your database, and nothing is served, until you pick a connection and confirm the schema plan.
It runs once per package. A version already staged is skipped, so a restart is cheap and an app you installed is never disturbed by the seed. One corrupt entry costs its own package and not the boot, and the log names it.
The point of the bundled set is the air-gapped case: an instance that can only ever use what came
with its image is a supported configuration, not a degraded one — the same reason
ADMINIUM_BUNDLED_ADD_ONS exists. Adminium never reaches the network to populate this. It is also
the only way an installed app survives a deploy on a host with no persistent disk: the published
image carries no apps, so build one that does — see
Installing apps.
To build one from an app checkout:
node scripts/release/pack-app-bundle.mjs ../clinic-desk --build --out apps-bundleADMINIUM_SURFACES_DIR
Section titled “ADMINIUM_SURFACES_DIR”Where the built frontends of the apps this instance hosts live. Unset — the default — it hosts none, and nothing else about the boot changes.
A surface is one built frontend of an app: its staff side, its customer
side, or both. Lay them out by app key and side:
surfaces/ clinic/ staff/index.html customer/index.htmlADMINIUM_SURFACES_DIR=/srv/adminium/surfaces adminium startclinic/staff is then served at /apps/clinic/staff/, and every surface found
gets a serving hosted app surface line in the boot log. The directory name
must be the app key the bundle was built with: the build bakes
/apps/<app>/<side>/ into every asset URL, so a folder renamed after the fact
serves an index.html whose scripts all 404.
Discovery runs once, at boot. Adding an app — or rebuilding one — takes a restart. Everything you decide about a surface afterwards is settings and takes effect without one: where a staff surface appears (its own URL, or blended into the dashboard sidebar), which connection it reads, which domains answer for it, and how many instances of it you run.
A directory that is absent, empty, or misspelled is not an error. You get no apps, no warning, and an empty Studio → Hosted apps; the boot log is where you check, and silence there is the symptom.
A hosted customer surface also needs the public API admitted for pages this instance serves itself:
ADMINIUM_PUBLIC_API_ORIGINS='self'→ An app surface on its own domain
ADMINIUM_CSP_IMG_HOSTS
Section titled “ADMINIUM_CSP_IMG_HOSTS”Adminium’s Content-Security-Policy lets pages load pictures from Adminium itself
and nowhere else (plus, while maps are on, the map server). That is right for
the dashboard’s own assets, and it is one header on everything the server sends
— including the app surfaces it hosts. So when your data links images kept
somewhere else, the browser refuses them and the app shows broken pictures: a
menu whose image_url column points at a CDN, a product list, a page of
service photos.
Name the hosts those pictures come from:
ADMINIUM_CSP_IMG_HOSTS='https://images.example.com,https://*.cdn.example.com'They are appended to img-src, and nothing else in the policy moves. Each entry
is a scheme and a host, with an optional port; a leading *. covers that host’s
subdomains. Some of the example apps’ demo data links its photos on
images.unsplash.com, so a demo database needs https://images.unsplash.com.
Under adminium design, Adminium Designer adds a host to this list in the
project’s .env when you say yes to its card, and that host counts from the
next reply. Everywhere else the list is read once, when the server starts.
What it refuses, and why it refuses rather than guesses:
- A bare
*or a scheme alone (https:). Either one admits pictures from any host, and an image request is a way to carry data out: a script that ever lands on your instance could send what it sees anywhere, one image at a time. The list exists to keep that as narrow as your real image hosting. - A path. CSP matches a path as a prefix, which reads like an exact rule and is not one.
- A wildcard over a whole top-level domain (
https://*.com).
A bad entry stops the boot with every refused value named, like any other
invalid variable. An https:// dashboard cannot show http:// pictures however
this is set — the browser blocks mixed content on its own.
ADMINIUM_ADD_ON_DEV_TRUST
Section titled “ADMINIUM_ADD_ON_DEV_TRUST”For somebody writing an add-on that keeps a ledger. A comma-separated list of add-on keys whose deciding code this server runs whatever its bytes:
ADMINIUM_ADD_ON_DEV_TRUST='stock-kit,my-ledger'Without it, such code runs only from a package this server can vouch for — bundled with the
release, or downloaded from the catalogue — and a package uploaded by hand installs with every
save that would ask its code refused. The variable is ignored when NODE_ENV is production:
it is not a way to run unreleased code on a live server.
ADMINIUM_TRUST_PROXY
Section titled “ADMINIUM_TRUST_PROXY”Off by default, which is right for a direct-to-internet or localhost process:
without a proxy in front, X-Forwarded-For is attacker-controlled and trusting
it would let anyone forge their source IP in your audit log and rate limits.
Turn it on when — and only when — a reverse proxy you control is in front.
ADMINIUM_TRUST_PROXY=onWith it on, Adminium reads X-Forwarded-For, X-Forwarded-Proto,
X-Forwarded-Host and X-Request-Id only on connections from your proxy (see
the next variable). From X-Forwarded-For it takes the last address, the one
your proxy added.
ADMINIUM_TRUSTED_PROXIES
Section titled “ADMINIUM_TRUSTED_PROXIES”Which connections count as your proxy. The default is a connection from this machine or from a private network:
ADMINIUM_TRUSTED_PROXIES=loopback,uniquelocalThat covers a Caddy or nginx on the same host (127.0.0.1), and a proxy
container on a Docker network (172.16.0.0/12). A connection from anywhere else
is treated as a client, whatever headers it sends.
The default also trusts every other machine on your private network. If any of them can reach Adminium’s port directly, name your proxy instead. Your list replaces the default:
ADMINIUM_TRUSTED_PROXIES=127.0.0.1 # Caddy on this host, nothing elseADMINIUM_TRUSTED_PROXIES=172.28.0.0/24 # the compose network your proxy is onEach entry is an IP address, a CIDR range, or one of these names:
| Name | Ranges |
|---|---|
loopback |
127.0.0.0/8, ::1 |
uniquelocal |
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7 |
linklocal |
169.254.0.0/16, fe80::/10 |
Host names such as caddy are not accepted. A bad entry stops the boot, and so
does setting this while ADMINIUM_TRUST_PROXY is off.
ADMINIUM_PUBLIC_API_ORIGINS
Section titled “ADMINIUM_PUBLIC_API_ORIGINS”Turns on the public API — a scoped, read-mostly surface at /api/v1/public that your own
customer-facing or staff-facing pages can call from a browser. It is off by default and stays off
until you name the origins allowed to use it.
ADMINIUM_PUBLIC_API_ORIGINS='https://shop.example.com'self is the one value that is not an origin. It admits the pages this instance serves
itself — hosted app surfaces at /apps/…, and any domain you attach to one of them. A same-origin
page cannot appear in a cross-origin allow-list (its requests carry no Origin header at all), so
without the sentinel a customer surface Adminium hosts could never call its own API. An instance
whose only public consumers are its own hosted surfaces needs exactly this and nothing more:
ADMINIUM_PUBLIC_API_ORIGINS='self'Combine it with real origins (self,https://shop.example.com) when standalone pages call in from
elsewhere too.
Three things about it are deliberate, and each one has bitten somebody:
- Unset means the routes do not exist. Not “exist and refuse” — there is nothing there to probe.
- It must not overlap
ADMINIUM_CORS_ORIGINS. That list is for your admin dashboard and its responses carry credentials; this one never does. An origin in both would make Adminium send two conflictingAccess-Control-Allow-Originheaders and the browser would reject the response. The server refuses to start rather than let you find that out from a CORS error naming neither variable. - It needs
ADMINIUM_TRUST_PROXY=1unless you bind to127.0.0.1. Behind a reverse proxy without it, every anonymous visitor looks like the same IP address, so they all share one rate-limit bucket and a single scraper can lock out your real customers while the limiter appears to be working. Adminium refuses to register the public API in that combination and logs why.
Naming an origin here is still not enough on its own: the public API also has a runtime switch (Settings → Public API) that is off until you turn it on, and every key you issue is limited to one scope you define. Turning the switch back off is instant and destroys nothing — keys, scopes and data all survive.
ADMINIUM_CORS_ORIGINS
Section titled “ADMINIUM_CORS_ORIGINS”Off by default. The dashboard is served by the same process as the API, so it is same-origin and needs no CORS at all.
Set it only for a split deployment where the dashboard is served from a different origin:
ADMINIUM_CORS_ORIGINS='https://admin.acme.io,https://ops.acme.io'Exact origins, comma-separated. * is rejected — responses are
credentialed (they carry cookies), and a wildcard origin with credentials is
either a browser error or a vulnerability, depending on the browser.
ADMINIUM_BRIDGE_ORIGINS
Section titled “ADMINIUM_BRIDGE_ORIGINS”Off by default, and unrelated to ADMINIUM_CORS_ORIGINS above.
A web page cannot open a TCP connection to PostgreSQL, so when you paste a
connection string on adminium.dev/generate
that page has nowhere to send it. This setting lets your browser hand the string
to an Adminium running on your own machine instead. It goes browser →
localhost; adminium.dev is a static site with no server to receive it.
adminium try --bridge sets this for you, to https://adminium.dev and
nothing else (before 0.3.0 it was adminium --bridge). Set the variable
directly only if you run your own copy of that page:
ADMINIUM_BRIDGE_ORIGINS='https://adminium.dev'Unset, the bridge routes are not registered at all — there is no endpoint to probe, rather than one that refuses. When it is set, three gates apply:
- Origin. Only the listed origins may call it, and the JSON content type
forces a preflight, so a page that is not on the list cannot deliver a
request at all.
*is rejected. - Pairing code. The instance prints an 8-character code at startup and the hand-off requires it. It is regenerated on every restart and never written to disk or the logs — the terminal is the only place it appears.
- Session. The route that gives the connection string back is
same-origin, cookie-authenticated and
connections:manage-gated. A guessed ticket is worthless without an admin session.
The bridge never connects to anything. It parks a string for a few minutes and the Studio wizard uses it to pre-fill the connect form, which you still review and submit yourself.
ADMINIUM_TELEMETRY
Section titled “ADMINIUM_TELEMETRY”Accepts on, true, 1 (and off, false, 0).
This variable is an override, not the switch. Telemetry’s normal control is
the first-run consent screen (telemetry.enabled), and it is off by default.
- Unset (the default) — the consent answer stands. This is what you want on a normal install.
- Set — the environment wins outright, in both directions.
offis a hard veto that a user clicking “yes” in the wizard cannot lift;onreports without waiting to be asked.
It does not govern the update check, which has its own consent
(updates.checkEnabled, also off by default).
ADMINIUM_MAP_SERVER
Section titled “ADMINIUM_MAP_SERVER”One origin, or off. Default https://tiles.openfreemap.org.
A map is drawn in each viewer’s browser from pictures of the part of the world on screen, and this is the server they come from. It learns the viewer’s IP address and the area they looked at; it is never sent a row or a name from your data.
ADMINIUM_MAP_SERVER=https://tiles.example.com # your ownADMINIUM_MAP_SERVER=off # no maps on this installWhile maps are on, this origin is added to connect-src and img-src of the
Content-Security-Policy, and to nothing else. It is there on every reply, the sign-in page’s
too, because the dashboard is one page loaded before sign-in; so the origin can be read by
somebody who is not signed in, and should be a server you do not mind naming. While maps are
off, the policy names no outside server at all. Maps are off when this is off, in the desktop app, or when Settings → Maps →
Show maps is switched off; off here cannot be overridden from the dashboard.
It must be a scheme and a host, with an optional port: no path, no *, no name and password.
http:// is taken only for localhost, 127.0.0.1 and [::1], for a tile server on your own
machine while developing. Anything else fails the start with the reason.
The address is deliberately not editable in Settings: it goes into the policy every staff
browser obeys, and an address somebody could type there would open that policy to any server.
A project’s .env cannot set it either on a host that opens folders it did not make (the
desktop app).
ADMINIUM_ROUTING_SERVER
Section titled “ADMINIUM_ROUTING_SERVER”One origin, or off. Default https://router.project-osrm.org.
The OSRM server your Adminium server asks for a route along
roads. It is asked only after somebody switches Settings → Maps → Routes along roads on;
until then, and with off, the way between stops is straight lines marked as estimates and
nothing is sent anywhere. What is sent is the ordered positions of a route’s stops, and nothing
else.
ADMINIUM_ROUTING_SERVER=http://osrm.internal:5000 # your own, on a private networkADMINIUM_ROUTING_SERVER=off # straight-line estimates onlyIt is called by the server, never by a browser, so it is not in the Content-Security-Policy and
may be plain http:// on a private network. In production a loopback address (localhost,
127.0.0.1) is refused, as it is for a webhook: name the machine by its network name.
ADMINIUM_NETWORK_FEATURES=off switches it off too.
The default is the OSRM project’s public demo server: fine for trying routes out, with no promise of being there. Adminium asks it at most once a second and keeps each answer for a day.
ADMINIUM_NETWORK_FEATURES
Section titled “ADMINIUM_NETWORK_FEATURES”Accepts on, true, 1 (and off, false, 0). Default on.
This is a policy answer, not a connectivity check. Adminium never probes the internet to find out whether it is reachable — that outbound call is exactly what an air-gapped install is promised it will not make. So the server reports what you told it, and the UI adapts.
Set it off on an install with no outbound network. Features that need the
internet — webhooks, OAuth integrations, provider-API AI enrichment, routes along
roads — then say so up front instead of being discovered one timeout at a time. Nothing is hidden:
each surface explains why it is unavailable.
It is also the one setting that stops a new install asking adminium.dev for what it offers.
From 0.3.16 a new install lists the add-ons and apps of adminium.dev from its first start: one
request for the two lists when it starts and once a day, which tells adminium.dev the server’s IP
address, the time and its Adminium version. With off set before the first start, that request is
never made, and no switch in the dashboard can turn it on. A server upgraded from an earlier
version keeps the setting it had. See Installing add-ons
and Installing apps.
Everything Adminium does locally is unaffected: browsing and editing data, charts, generation, exports, scheduled reports, and the copy/paste AI round-trip all work exactly the same.
ADMINIUM_NETWORK_FEATURES=offADMINIUM_TELEMETRY=offADMINIUM_MAP_SERVER=offTogether with ADMINIUM_TELEMETRY=off and updates.checkEnabled left at its
default, that is a complete no-phone-home configuration for the server.
ADMINIUM_MAP_SERVER=off is the line for the browsers: without it a staff
member who opens a map fetches its pictures from the map server.
The ADMINIUM_AI_* block
Section titled “The ADMINIUM_AI_* block”A model can be given to the server without saving anything in Settings → AI. Each provider has
its own variable, so several can be set at once, and ADMINIUM_AI_MODEL says which model is in use:
ADMINIUM_AI_ANTHROPIC_API_KEY=sk-ant-…ADMINIUM_AI_OLLAMA_BASE_URL=http://localhost:11434ADMINIUM_AI_MODEL=anthropic/claude-sonnet-5-5Provider in ADMINIUM_AI_MODEL |
Needs |
|---|---|
anthropic |
ADMINIUM_AI_ANTHROPIC_API_KEY |
openai |
ADMINIUM_AI_OPENAI_API_KEY |
openai-compatible |
ADMINIUM_AI_COMPATIBLE_BASE_URL, and ADMINIUM_AI_COMPATIBLE_API_KEY when the server wants a key |
ollama |
ADMINIUM_AI_OLLAMA_BASE_URL |
What saved settings do to it. A provider saved in Settings → AI wins: the page assistant and the AI assist use that one. With nothing saved there they use the model the environment selects, and the settings page says a model from the environment is in use. No key set this way is ever shown, not even its last characters.
In a project folder the same six names go in the project’s .env, and that is where
Adminium Designer writes a model you add there. They are the one part of .env
that is NOT copied into the server’s environment: the server reads them from the file where it
calls the model, so a project’s hooks and the commands it starts never see a key. A variable set in
the server’s real environment (a container, a shell) wins over the file; the settings page lists
any name that is set in both with different values.
ADMINIUM_NETWORK_FEATURES=off still applies: only an Ollama model is called then, whatever is set.
The address of an OpenAI-compatible server or of Ollama is checked before it is called: a cloud
metadata address is always refused.
The desktop block
Section titled “The desktop block”ADMINIUM_RUNTIME, ADMINIUM_BOOT_TOKEN, ADMINIUM_DESKTOP_SINGLE_USER and
ADMINIUM_DEMO_SEED_SCRIPT are set by the Electron app when it launches its
own server process. They are listed here because Adminium validates them, not
because a self-host deployment should set them.
ADMINIUM_RUNTIME is the one to understand, because it decides what the server
exposes. Under desktop — and only under desktop — the server registers
POST /api/v1/auth/desktop-session, the route that trades the desktop app’s
per-launch boot token for a session without a password. It exists so the app can
open straight into your data on a machine you already unlocked.
On a self-host or Docker install the runtime is self-host, that route is
never registered, and the password-free door does not exist to be attacked —
which is a stronger guarantee than a check inside a handler. Setting
ADMINIUM_RUNTIME=desktop on a server would create it. Do not.
The other three are inert without it: the boot token is minted fresh per launch and never stored, the single-user flag mirrors a choice made in the desktop app’s own settings, and the seed-script path points at a file that ships inside the app bundle.
ADMINIUM_DISABLE_UPDATES
Section titled “ADMINIUM_DISABLE_UPDATES”Read by the desktop app, not the server: a kill-switch for fleet admins who manage machines running the Electron build.
Set to exactly 1, it forces the desktop update mode to disabled regardless
of what the desktop config says. Disabled means the updater is never
initialized — the update library is not even loaded, and no update-related
network request is made. Any other value (including 0) leaves the configured
mode in effect; only the literal 1 disables, so a typo cannot silently turn
updates on or off the wrong way.
Flags override the environment
Section titled “Flags override the environment”Where a CLI flag exists, it wins:
PORT=4600 adminium start --port 8080 # listens on 8080| Flag | Variable |
|---|---|
--port |
PORT |
--host |
HOST |
--meta-url |
ADMINIUM_META_URL |
--data-dir |
ADMINIUM_DATA_DIR |
--log-level |
ADMINIUM_LOG_LEVEL |
--static-root |
ADMINIUM_STATIC_ROOT |
Not an environment variable: the bootstrap file
Section titled “Not an environment variable: the bootstrap file”When the setup wizard offers to remember your meta DSN, it writes
<data-dir>/adminium.json — the meta DSN cannot live inside the meta store it
points at. The value is AES-256-GCM-encrypted under ADMINIUM_SECRET.
ADMINIUM_META_URL takes precedence over it.