Environment Variables
Every environment variable the GioJS server, render worker, gio CLI and create-giojs read: defaults, who reads it, and how it ranks against gio.toml.
# A second instance on another port, with JSON logs, from the project root
GIO_PORT=4000 GIO_LOG_FORMAT=json npx gio startThis page is the reference. For how to give your own app its configuration (the .env files, GIO_PUBLIC_* in the browser, secrets), read the Environment Variables guide.
Reference
"Read by" names the process that looks at the variable: the Rust server (giojs-server, which gio dev and gio start run), the Node worker it spawns (your pages, route handlers and plugins run there and inherit the server's environment), the gio CLI, gio export, gio build standalone, the testing kit (@gio.js/core/testing) and create-giojs. An empty value counts as unset for the listen address, the secrets, GIO_CACHE_DIR, GIO_ENV_FILES, GIO_LOG_FORMAT, GIO_DEPLOYMENT_ID and the editor variables. Do not set the other directory variables to an empty value: leave them unset. The server reads an empty GIO_PUBLIC_DIR, GIO_IMAGE_CACHE_DIR, GIO_FONTS_DIR or GIO_STATIC_DIR as its working directory.
| Variable | Default | Read by | gio.toml key it overrides |
|---|---|---|---|
GIO_HOST | [server] host, else 0.0.0.0 | server, gio CLI | [server] host |
GIO_PORT | unset | server, gio CLI | [server] port (and PORT) |
PORT | unset | server, gio CLI | [server] port |
GIO_APP_DIR | ./app | server, worker, gio CLI, export, standalone, testing kit | - |
GIO_PUBLIC_DIR | public/ next to app/ | server | - |
GIO_CACHE_DIR | .gio/cache/pages | server, gio doctor | [cache] disk_path |
GIO_IMAGE_CACHE_DIR | .gio/cache/images | server | - |
GIO_FONTS_DIR | .gio/fonts | server | - |
GIO_STATIC_DIR | .gio/build/static | server | - |
NODE_ENV | unset (production) | server, worker, every CLI command | - |
GIO_ENV_FILES | unset | server, export, standalone, testing kit, gio CLI | [env] files |
GIO_SESSION_SECRET | unset (dev: ephemeral) | server, worker, gio doctor | - |
GIO_REVALIDATE_TOKEN | unset | server | [revalidate] token |
GIO_SITE_URL | unset | worker, export | - |
GIO_DEPLOYMENT_ID | content-derived | server | - |
GIO_PUBLIC_* | - | client bundles (worker, export, standalone) | - |
GIO_LOG_FORMAT | [logging] format, else text | server | [logging] format |
RUST_LOG | info | server | - |
GIO_LOG_LEVEL | info | worker | - |
GIO_EDITOR, VISUAL, EDITOR | code | server (dev) | - |
GIO_EXIT_ON_STDIN_EOF | unset | server, worker | - |
GIO_SOCKET_PATH, GIO_WS_SOCKET_PATH | per instance | server, worker | - |
GIO_SERVER_BIN | installed platform binary | gio CLI, createTestServer | - |
GIO_STANDALONE_SERVER_BIN | installed platform binary | gio build standalone | - |
GIO_OUT_DIR | ./out | gio export | - |
npm_config_user_agent | set by your package manager | create-giojs, gio migrate, gio add, gio doctor | - |
NO_COLOR | unset | create-giojs migrate | - |
GIO_WORKER_INDEX, GIO_WORKER_COUNT | set by the server | your code | - |
GIO_EXPORT | set by gio export | your code, the worker | - |
Precedence over gio.toml
Where a variable and a gio.toml key set the same thing, the variable wins, so a deploy can change it without editing the file:
GIO_PORT>PORT>[server] port>3000. The startup log lineGioJS listening on ...carriesport_from, the source that won.GIO_HOST>[server] host>0.0.0.0.GIO_CACHE_DIR>[cache] disk_path.GIO_REVALIDATE_TOKEN>[revalidate] token.GIO_LOG_FORMAT>[logging] format.GIO_ENV_FILES>[env] files.
Variables already in the real environment also win over the .env files, which the server loads at startup before it reads gio.toml. A variable set in a .env file counts like one set in the shell for everything on this page, except NODE_ENV, GIO_ENV_FILES and GIO_APP_DIR, which decide which files load and where they are, and so must come from the real environment (the server ignores a NODE_ENV line, with a warning).
Listen address and directories
GIO_HOST
The address the server binds, overriding [server] host. It must be an IP address: 0.0.0.0 (every interface), 127.0.0.1 (this machine only), or IPv6 in brackets ([::], [::1]). A host name or a value with a port stops startup with invalid GIO_HOST="localhost": expected an IP address such as 0.0.0.0 .... gio dev -H and gio start -H set it. The testing kit's createTestServer() sets it to 127.0.0.1.
GIO_PORT
The port the server listens on, overriding PORT and [server] port. A value that is not a port number (0-65535) stops startup; an empty one falls through to PORT. gio dev -p and gio start -p set it, and gio cache explain and gio bench read it (with the .env files) to find the local server.
PORT
The port hosting platforms assign (Heroku, Render, Railway, Fly.io, Cloud Run). The server honors it after GIO_PORT and before gio.toml, so the same app runs unchanged on those platforms. The health check in the Dockerfile that create-giojs add docker writes reads it too.
GIO_APP_DIR
The app/ directory, relative to the working directory or absolute. Default ./app. Its parent is the project root: gio.toml, public/, the .env files and the .gio/ caches are found there. If there is no gio.toml next to app/, the server reads gio.toml from the working directory instead. The worker, the gio commands that read routes (routes, typegen, doctor), gio export, gio build standalone and renderPage() all follow it.
The worker compiles your TypeScript with the project root's tsconfig.json (or jsconfig.json), wherever the server is started - and so do gio export, gio routes and gio typegen - so settings such as "jsx": "react-jsx" apply from any working directory. Without either file it falls back to tsx's own lookup, which starts in the working directory; set TSX_TSCONFIG_PATH to choose another file. The IPC sockets (GIO_SOCKET_PATH) still go in the working directory's .gio/.
GIO_PUBLIC_DIR
The directory served at the site root and under /public/* (see Endpoints). Default: public/ in the project root.
GIO_CACHE_DIR
The page cache's disk directory, overriding [cache] disk_path. Unlike the key, it may point outside the project (an absolute path, or one relative to the server's working directory). It may not be, contain or sit inside app/ or public/: startup refuses that with GIO_CACHE_DIR: .... With CSP nonces on, the nonce placeholder is persisted in its meta/ folder.
GIO_IMAGE_CACHE_DIR
Where /_gio/image stores the images it has resized and encoded. Default .gio/cache/images in the project root; a relative value resolves against the working directory. Not created when [images] enabled = false.
GIO_FONTS_DIR
Where the server writes the [[fonts]] files it downloads or copies, and the generated fonts.css, served under /_gio/fonts/. Default .gio/fonts in the project root.
GIO_STATIC_DIR
The built client assets (route chunks and stylesheets) served under /_next/static/. Default .gio/build/static in the project root, where the worker writes them. A standalone bundle's run.mjs points it at the bundle's own static/ folder.
Mode and .env files
NODE_ENV
Decides the runtime mode. Exactly development means development (file watcher, /_gio/devtools, error details, the .env.development* files); any other value, unset and test included, means production. The server spawns the worker with NODE_ENV set to the mode it decided, replacing what the worker would have inherited, so the two can never disagree. gio dev sets it to development and gio start to production, whatever the shell says; gio export keeps development and turns anything else into production; a standalone run.mjs defaults it to production. A NODE_ENV line in a .env file is ignored with a warning.
GIO_ENV_FILES
0 or false loads no .env files; 1 or true loads them; unset or empty leaves it to [env] files in gio.toml (default true). Any other value stops startup: GIO_ENV_FILES="yes" must be 0 (skip them) or 1 (load them). The server, gio export, gio build standalone, the testing kit and the gio CLI's own fallback reader all follow it. Use it on platforms that inject the whole environment and must ignore a stray file in the image.
Secrets
GIO_SESSION_SECRET
The key material for encrypted cookie sessions (createSessionStorage()) and for require_session guards, which the server verifies in Rust. Comma-separated to rotate: each entry (trimmed, empty entries ignored) must be at least 32 bytes; the first signs and encrypts, all of them verify.
- Production, unset:
createSessionStorage()throws, andrequire_sessionguards deny every request. - An entry shorter than 32 bytes: the server logs
invalid session secret - require_session guards deny every requestwith a command that generates a good one. - Development, unset: the server generates an ephemeral secret for this run and passes it to the worker (with
GIO_SESSION_SECRET_EPHEMERAL=1), so sessions work and reset on restart. - Tests: when neither the environment nor a
.envfile sets it,renderPage()andcallRoute()set a random one for the test process. AcreateTestServer()server does not get it.
gio doctor reports an invalid secret as an error, and a missing one when the app has require_session guards.
GIO_REVALIDATE_TOKEN
The bearer token of POST /_gio/revalidate. Setting it (or [revalidate] token) is what creates the endpoint; without either, the path answers 404. It wins over the gio.toml key, is trimmed, and must be at least 32 bytes, or startup stops with the revalidation token (GIO_REVALIDATE_TOKEN) is N bytes; at least 32 are required.
Rendering
GIO_SITE_URL
The site's absolute origin (https://example.com), read by the worker. It is the metadataBase when no layout or page sets one, so relative Open Graph, canonical and alternate URLs become absolute; it resolves the relative URLs app/sitemap.ts and app/robots.ts return (a relative one with the variable unset is sent as is, with the warning app/sitemap returned a relative URL but GIO_SITE_URL is not set). For an app with no app/sitemap.ts, gio export generates a sitemap.xml only when it is set. GioJS never builds absolute URLs from the request's Host header: a cached page would carry whatever host the first visitor sent.
GIO_DEPLOYMENT_ID
Pins the deployment ID instead of deriving it. Used as given, trimmed and cut to 64 characters. The derived ID is a hash of the client build, the app's server-side sources and the gio.toml settings pages render with, so it already changes with every code change and stays the same across restarts of the same code. Pin it when several instances must agree and their builds may differ byte for byte, and change it with every deploy: it keys the page cache, and it is what clients send in x-deployment-id.
GIO_PUBLIC_*
Every variable whose name starts with GIO_PUBLIC_ is inlined into the client bundles where code reads process.env.GIO_PUBLIC_X, with the value it had when the bundles were built: at server start for gio dev and gio start, at build time for gio export and gio build standalone (frozen into the output). In the browser, process.env holds only these, NODE_ENV and GIO_EXPORT; any other process.env.X is undefined. The prefix is fixed. See Variables in the browser.
Logging
GIO_LOG_FORMAT
json or text (any case): the server's log format, overriding [logging] format. json writes one JSON object per line in the worker's shape (ts, level, msg, target, span fields such as request_id). Any other value is ignored with the warning ignoring GIO_LOG_FORMAT=...: expected "json" or "text". The worker always logs JSON.
RUST_LOG
The server's log filter, in tracing's EnvFilter syntax: a level (warn, debug) or per-module directives (info,giojs_server::ipc=debug). Default info.
GIO_LOG_LEVEL
The worker's minimum log level: debug, info, warn or error (lowercase). Default info; any other value means info. It filters the JSON lines the framework logs in the worker; what your own code writes with console.log is never filtered.
Development and processes
GIO_EDITOR, VISUAL, EDITOR
The editor the dev error overlay's file links open (POST /_gio/devtools/open-in-editor). The first one set to a non-empty value wins, in that order; default code. VS Code-family editors get -g file:line. Read per request, development only.
GIO_EXIT_ON_STDIN_EOF
1: shut down gracefully when standard input reaches end-of-file. Launchers that hold a stdin pipe open (gio, a standalone run.mjs) set it, so a launcher killed outright (SIGKILL, the OOM killer) never leaves the server on the port. The server sets it on every worker for the same reason. Ignored when stdin is a terminal or /dev/null.
GIO_SOCKET_PATH, GIO_WS_SOCKET_PATH
The paths of the two Rust-to-Node IPC endpoints (HTTP and WebSocket traffic). Default: a per-instance .gio/ipc-<pid>-<random>.sock and .gio/ws-<pid>-<random>.sock on Unix, unique named pipes on Windows. A value you set wins; in a worker pool the other workers get it with a -w<N> suffix. The server passes the resolved paths to the worker. You rarely need them: createTestServer() sets its own so a test server never collides with a dev server in the same project.
CLI, build and testing
GIO_SERVER_BIN
An explicit giojs-server binary for the gio CLI and createTestServer(), instead of the platform package @gio.js/server installed. Use it for a binary you built with cargo build -p giojs-server. A path that does not exist is an error, not a fallback. gio --version and gio doctor say when it is in use.
GIO_STANDALONE_SERVER_BIN
The server binary gio build standalone copies into the bundle, overriding --target; for cross-building with a binary you built yourself. A path that does not exist stops the build. See Cross-building.
GIO_OUT_DIR
Where gio export writes the static site. Default ./out.
npm_config_user_agent
Set by npm, pnpm, yarn and bun for the scripts and create packages they run. A new app is installed with the package manager you ran create-giojs with (--pm overrides it); in an existing project, create-giojs add and migrate go by the project's lockfile first. gio add and gio migrate read it before the lockfile to choose how to run a create-giojs the project does not have installed (npx, pnpm dlx or bunx), and gio doctor reports the package manager it names.
NO_COLOR
Any value turns off the colored diff of create-giojs migrate. Color is also off when stdout is not a terminal.
Set by GioJS
The server and the CLI set these for your code to read. A value you set is replaced.
GIO_WORKER_INDEX, GIO_WORKER_COUNT
In each Node worker: its position in the pool ("0" for the first) and the pool size ([server] workers; development always runs one). A plugin's onStartup runs in every worker, so gate one-time jobs on process.env.GIO_WORKER_INDEX === '0'.
GIO_EXPORT
"1" while gio export renders, in the exporter and in the client bundles it builds ("0" in bundles the server builds). cspNonce() returns undefined under it, and <GioImage> renders its plain src.
Internal variables
The server and the launchers pass these between the processes. Do not set them; they are listed so you can recognize them in a process listing.
| Variable | What it carries |
|---|---|
GIO_IPC_TOKEN | The per-start secret the worker proves itself with on the IPC sockets. |
GIO_IMAGE_CONFIG, GIO_CSS_CONFIG, GIO_I18N_CONFIG | [images], [css] and [i18n] as JSON, for <GioImage> srcsets, the stylesheet build and the default locale <LocaleLink> leaves unprefixed. All three are hashed into the derived deployment ID. |
GIO_CSP_NONCE_PLACEHOLDER | The secret placeholder the worker renders where a CSP nonce goes; the server swaps it for a fresh nonce per response. |
GIO_SESSION_SECRET_EPHEMERAL | 1 when GIO_SESSION_SECRET is the dev server's generated one. |
GIO_WORKER_ERROR_FILE | Where a worker writes the error that ends it, so a worker that fails to boot is reported by the server with its own error (see worker boot errors). |
GIO_BUILD_ID, GIO_REUSE_BUILD | Let pool workers load the first worker's client build instead of bundling again. |
GIO_NODE_SCRIPT, GIO_TSX_PKG, GIO_PNPM_ROOT, NODE_PATH | How to start the worker: its entry script (packages/giojs-core/src/index.ts by default, a bundle's worker.js in standalone) and the tsx loader that runs it. |
TSX_TSCONFIG_PATH | The tsconfig tsx compiles app code with. The server (and gio export, gio routes, gio typegen) sets it to the project root's tsconfig.json (else jsconfig.json) unless the environment already sets it. |
GIO_STANDALONE | 1 in a standalone bundle: the worker runs on plain node. |
GIO_UPDATE_SCHEMA | For contributors: GIO_UPDATE_SCHEMA=1 cargo test -p giojs-server json_schema regenerates gio.schema.json. |
VITEST | Set by vitest; the testing kit adjusts module loading under it and removes it from a createTestServer() server's environment. |
Examples
Run on a platform that assigns the port
Nothing to configure: the platform sets PORT, and the server binds 0.0.0.0 by default.
PORT=8080 npx gio start
# ... GioJS listening on 0.0.0.0:8080 ... port_from="PORT"A production environment
GIO_SESSION_SECRET=8Jx2...a-32-byte-or-longer-random-value
GIO_REVALIDATE_TOKEN=Hq9v...another-32-byte-or-longer-value
GIO_SITE_URL=https://example.com
GIO_PUBLIC_API_URL=https://api.example.com
GIO_LOG_FORMAT=jsonGenerate each secret with node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))". In a container, pass them as real environment variables and set GIO_ENV_FILES=0 so no file in the image can change them.
Rotate the session secret
# New secret first: it signs from now on. The old one still verifies
# existing cookies until they expire; then drop it.
GIO_SESSION_SECRET="$NEW_SECRET,$OLD_SECRET" npx gio startKeep the page cache on a volume
# From the project root; the cache directory may be anywhere outside app/ and public/
cd /srv/site
GIO_CACHE_DIR=/var/cache/site npx gio start[cache] disk_path only takes a directory inside the project; the variable takes any path, so the persisted pages can live on a volume that outlives the container.
Run a startup job once per pool
import { defineConfig } from '@gio.js/core';
export default defineConfig({
plugins: [
{
name: 'warm-up',
version: '1.0.0',
async onStartup() {
if (process.env.GIO_WORKER_INDEX !== '0') return;
console.log(`warming caches (pool of ${process.env.GIO_WORKER_COUNT})`);
},
},
],
});Good to know
- The server reads its variables, and the
.envfiles, once at startup, and the worker inherits that environment: restart the server after changing one. - The
GIO_PUBLIC_prefix is fixed and cannot be configured: it is the line between values that may reach every visitor and values that must not. A secret given that prefix is public. - Startup never prints values: the
.envloader logs file names, parse errors name the file and line, andgiojs-server --check-configreports whetherGIO_SESSION_SECRETis set and valid (sessionSecret), never its value. - Relative directory values (
GIO_CACHE_DIR,GIO_IMAGE_CACHE_DIR,GIO_FONTS_DIR,GIO_STATIC_DIR,GIO_PUBLIC_DIR) resolve against the server's working directory, not the project root. giojs-server --check-configandgio doctorload the.envfiles and apply these variables exactly as startup does, so they show the address, cache directory and secrets the real server would use.
Related
- Environment Variables guide -
.envfiles, server-only values andGIO_PUBLIC_* - .env files and Listen address in the
gio.tomlreference - .env files (file convention) and
[env] - Authentication -
GIO_SESSION_SECRETin use - On-demand revalidation -
GIO_REVALIDATE_TOKENin use - Observability -
GIO_LOG_FORMAT,RUST_LOG,GIO_LOG_LEVEL - Endpoints, Headers and TypeScript
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | The server decides the mode from NODE_ENV and sets it on the worker. New: GIO_HOST, GIO_PORT and PORT (precedence GIO_PORT > PORT > gio.toml), GIO_ENV_FILES, GIO_PUBLIC_DIR, GIO_SESSION_SECRET, GIO_REVALIDATE_TOKEN, GIO_LOG_FORMAT, GIO_WORKER_INDEX / GIO_WORKER_COUNT, GIO_EXIT_ON_STDIN_EOF, GIO_SERVER_BIN; GIO_PUBLIC_* inlined into client bundles; .env files loaded at startup. The worker compiles app code with the project's tsconfig.json (it used the working directory's) and gets TSX_TSCONFIG_PATH. |
v0.1.0-beta.6 | GIO_DEPLOYMENT_ID pins the (now content-derived) deployment ID; GIO_EDITOR / VISUAL / EDITOR for open-in-editor. |
v0.1.0-beta.3 | GIO_SITE_URL makes the exported sitemap.xml absolute. |