GioJSdocs
On this page

gio build standalone

Package a GioJS app into one directory - the server binary, a bundled worker.js and the static assets - that runs anywhere Node is installed, with no node_modules.

npx gio build standalone
bash
gio build standalone [--out <dir>] [--target <platform>]
node standalone/run.mjs

Reference

OptionTypeDefaultDescription
--out <dir>path./standaloneWhere the build goes, relative to the current directory. The directory is emptied first, so it must be one this command owns (see The output directory).
--target <platform>stringthis machineBuild for another platform: linux-x64, linux-x64-musl, linux-arm64, win32-x64, darwin-x64 or darwin-arm64. The binary comes from @gio.js/server-<target>, which must be installed (npm i @gio.js/server-<target> --force). linux-arm64 has no published package yet.
-h, --helpboolean-Print the usage and exit with 0.

Environment variables

VariableEffect
GIO_APP_DIRThe app directory (default ./app); the project root is its parent.
GIO_STANDALONE_SERVER_BINThe server binary to copy, overriding --target and the installed package.
GIO_SERVER_BINWithout --target: the binary to copy instead of the installed platform package.
GIO_PUBLIC_*Inlined into the client chunks and worker.js at build time, from the environment and the project's .env.production.local, .env.local, .env.production and .env. Changing one needs a rebuild.
GIO_ENV_FILES0 / false loads no .env files for the build, like [env] files = false in gio.toml; 1 / true loads them even when gio.toml turns them off.

Behavior

  1. Loads the project's production .env files (always the production set).
  2. Discovers the routes exactly as the server does, and fails on the same route conflicts. A project with no page and no route.ts is an error.
  3. Builds the route stylesheets and the client chunks in production mode. The stylesheets follow [css] minify in the project's gio.toml at build time.
  4. Bundles the whole Node side - your pages, layouts, route.ts files, middleware.ts, gio.config.ts, React and every dependency - into one worker.js with esbuild. tsx and esbuild are needed only for the build.
  5. Empties --out and writes the output below.
text
standalone/
  server              the Rust binary (server.exe for win32-x64)
  worker.js           the whole Node side, bundled
  run.mjs             the launcher: node run.mjs
  static/             prebuilt client chunks and route stylesheets
  public/             a copy of public/, when the project has one
  gio.toml            a copy of gio.toml, when the project has one
  package.json        {"name":"giojs-standalone-app","private":true,"type":"module"}
  .gio/manifest.json  routes, handlers, chunk and worker hashes (feeds the deployment id)
  .gio/routes.d.ts    the typed routes and .gio/css-modules.d.ts, when the project has them

run.mjs starts server with the output directory as its working directory, NODE_ENV=production unless the environment sets NODE_ENV, and the paths to worker.js and static/. It passes its own arguments to the server (which takes only --check-config), forwards SIGINT / SIGTERM (except on Windows), and exits with the server's code. Like gio, it holds the server's stdin pipe, so killing the launcher also stops the server.

The output directory

The build deletes --out before writing, so it refuses, without touching anything:

  • the project directory or any directory that contains it (--out ., --out ..);
  • a directory inside app/;
  • a path that exists and is not a directory;
  • a non-empty directory that is not a previous standalone build (one holding run.mjs and .gio/manifest.json).

Examples

Build and run

text
$ npx gio build standalone
gio build standalone
  app:    /home/me/my-app/app
  server: /home/me/my-app/node_modules/@gio.js/server-linux-x64/bin/giojs-server
  out:    /home/me/my-app/standalone
  env:    .env.production (GIO_PUBLIC_* inlined at build time)
  routes: /, /about, /blog, /posts/:id

standalone build complete: /home/me/my-app/standalone
  server, worker.js, run.mjs, static/, .gio/

deploy the directory to a server that has Node, then:
  node standalone/run.mjs

Copy the folder to the server and run node run.mjs inside it, or node standalone/run.mjs from its parent. Server-side variables are read at runtime: set them in the deploy environment, or put .env files in the output directory.

Check the configuration on the target machine

bash
node standalone/run.mjs --check-config

run.mjs passes its arguments on, so this runs --check-config with the deployed gio.toml, .env files and environment.

Cross-build for Linux from a Mac

bash
npm i @gio.js/server-linux-x64 --force
npx gio build standalone --target linux-x64 --out dist-linux

--force lets npm install a package for another OS. Without the package the build stops before writing anything:

text
gio build standalone: platform package @gio.js/server-linux-x64 is not installed.
  Cross-target builds need it present: npm i @gio.js/server-linux-x64 --force

In a Dockerfile

Dockerfile
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npx gio build standalone --out standalone

FROM node:22-slim
WORKDIR /app
ENV GIO_HOST=0.0.0.0 PORT=3000
COPY --from=build /app/standalone ./
# The server writes its page cache and IPC sockets under .gio/
RUN mkdir -p .gio && chown -R node:node .gio
USER node
EXPOSE 3000
CMD ["node", "run.mjs"]

The Docker guide has the complete image, with a health check and the settings for a graceful stop; gio add docker writes it for you.

Good to know

  • The output carries no .env files and no node_modules. GIO_PUBLIC_* values are frozen in; every other variable is read when the server starts.
  • Data your app reads from the project at runtime (a SQLite file, migrations, uploaded files) is not copied: ship it next to run.mjs yourself.
  • Modules evaluate as they do from source: route.ts files at startup, pages and layouts on first use. A module that throws when imported fails only the URLs that import it (500), not the whole worker.
  • A usage error - an unknown argument, a missing value, an unknown --target - exits with code 2, as every gio usage error does. A build that fails exits with 1.
  • Options take their value as the next argument or after =: --out dist/app and --out=dist/app are the same.
  • The deployment id is derived from .gio/manifest.json, which includes a hash of worker.js, so a rebuild of changed code never serves the old build's cached pages.

Version history

VersionChanges
v0.1.0-beta.8Refuses an --out it could not safely empty (before, --out . deleted the project). Usage errors exit with 2 instead of 1, and options also take --option=value. Loads .env files and inlines GIO_PUBLIC_*. Ships CSS imports and CSS Modules, following [css] minify. Modules evaluate lazily, so one failing module no longer stops the worker.
v0.1.0-beta.7Introduced.