Přeskočit na obsah

Environment variables

Tento obsah zatím není dostupný ve vašem jazyce.

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.

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.

The only required variable, and the one worth understanding.

Terminal window
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` |

Where Adminium’s own adminium_* tables live.

Terminal window
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.

→ Where to put the meta store

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.

Terminal window
ADMINIUM_META_POOL_MAX=4
ADMINIUM_SOURCE_POOL_MAX=2

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&region=nyc3&pathStyle=0&accessKey=DO00EXAMPLE&secretKey=...

The three grammars:

Driver Value
Any S3-compatible bucket s3://<bucket>[/<prefix>]?endpoint=&region=&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 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.

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/app

Leave 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.

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:

Terminal window
ADMINIUM_STATIC_ROOT=/srv/adminium/dashboard adminium start

If 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.

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:

Terminal window
node scripts/release/pack-app-bundle.mjs ../clinic-desk --build --out apps-bundle

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.html
Terminal window
ADMINIUM_SURFACES_DIR=/srv/adminium/surfaces adminium start

clinic/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:

Terminal window
ADMINIUM_PUBLIC_API_ORIGINS='self'

→ An app surface on its own domain

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:

Terminal window
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.

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:

Terminal window
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.

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.

Terminal window
ADMINIUM_TRUST_PROXY=on

With 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.

→ Behind a reverse proxy

Which connections count as your proxy. The default is a connection from this machine or from a private network:

Terminal window
ADMINIUM_TRUSTED_PROXIES=loopback,uniquelocal

That 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:

Terminal window
ADMINIUM_TRUSTED_PROXIES=127.0.0.1 # Caddy on this host, nothing else
ADMINIUM_TRUSTED_PROXIES=172.28.0.0/24 # the compose network your proxy is on

Each 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.

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.

Terminal window
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:

Terminal window
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 conflicting Access-Control-Allow-Origin headers 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=1 unless you bind to 127.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.

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:

Terminal window
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.

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:

Terminal window
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:

  1. 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.
  2. 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.
  3. 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.

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. off is a hard veto that a user clicking “yes” in the wizard cannot lift; on reports without waiting to be asked.

It does not govern the update check, which has its own consent (updates.checkEnabled, also off by default).

→ Telemetry

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.

Terminal window
ADMINIUM_MAP_SERVER=https://tiles.example.com # your own
ADMINIUM_MAP_SERVER=off # no maps on this install

While 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).

→ Maps, places and routes

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.

Terminal window
ADMINIUM_ROUTING_SERVER=http://osrm.internal:5000 # your own, on a private network
ADMINIUM_ROUTING_SERVER=off # straight-line estimates only

It 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.

→ Maps, places and routes

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.

Terminal window
ADMINIUM_NETWORK_FEATURES=off
ADMINIUM_TELEMETRY=off
ADMINIUM_MAP_SERVER=off

Together 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.

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:

Terminal window
ADMINIUM_AI_ANTHROPIC_API_KEY=sk-ant-…
ADMINIUM_AI_OLLAMA_BASE_URL=http://localhost:11434
ADMINIUM_AI_MODEL=anthropic/claude-sonnet-5-5
Provider 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.

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.

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.

Where a CLI flag exists, it wins:

Terminal window
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

→ CLI reference

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.