Deploy
Deploy
Run a Lumiose build in production: what the build writes, the settings a server needs, and a guide for each hosting target.
#What to deploy
lumiose build writes dist/ (Rendering modes):
dist/client: the browser assets (content-hashed, underassets/), the files ofpublic/, and the prerendered pages, with.brand.gzcopies of every text file of 1 kB or more.dist/server/entry.js: the request handler for server-rendered pages, endpoints and form actions. It bundles your server code and the Lumiose runtime; other npm packages stay imports, so production needs the app'sdependenciesinstalled.
A site whose every route is prerendered builds no dist/server: any static host serves
dist/client. Anything else needs a runtime for dist/server/entry.js. On Node,
@lumiose/adapter-node serves both directories, from the server.ts that
create-lumiose --template ssr writes (reference); the guides
below run it on Docker, a VPS, Coolify, Fly.io, Render and Railway. dist/server/entry.js exports
a web-standard { fetch } (the server build), which Bun and Deno
serve as it is.
For Cloudflare, Vercel and Netlify, a deploy adapter in the Vite config writes the platform's
output after the build: lumiose({ adapter: cloudflare() }), vercel() or netlify(). Those
platforms serve dist/client themselves and compress on their own, so the build then writes no
.br/.gz copies.
#Production settings
The Node adapter and the request handler read these environment variables:
| Variable | Set it to |
|---|---|
NODE_ENV |
production. The adapter sets it when it is unset, before the build loads. |
PORT |
The port to listen on. Default: 3000. |
HOST |
The address to listen on. Default: every address, which a container needs. Behind a proxy on the same machine, 127.0.0.1. |
ORIGIN |
The public origin, such as https://example.com, behind a proxy that terminates TLS or rewrites the host. Request URLs (url(), ctx.url) are built from it. |
LUMIOSE_SECRET |
When the app signs cookies (signedCookie()): a random secret of at least 32 bytes (openssl rand -base64 32). Never commit it or bake it into an image. |
#Guides
- Docker: A multi-stage Dockerfile:
node:24-alpine, production dependencies only, a non-root user and a health check. - VPS: The Node adapter under systemd or pm2, behind Caddy or nginx, and what the adapter already does for compression and caching.
- Coolify: Deploy from the Dockerfile with Coolify's Dockerfile build pack.
- Cloudflare Workers:
@lumiose/adapter-cloudflare: the Worker, static assets, bindings asctx.platform.env. - Vercel:
@lumiose/adapter-vercel: the Build Output API, with a Node.js or Edge function. - Netlify:
@lumiose/adapter-netlify: the site and a Netlify Function. - Bun:
bun dist/server/entry.js, static files included. - Deno:
deno serve dist/server/entry.js, static files included. - Fly.io, Render, Railway: The Node adapter on container and Node hosts.
- Static hosts: A fully prerendered site on Cloudflare, Netlify, GitHub Pages, S3 with CloudFront or any file server.
- AWS Lambda: A handler from Lambda's events to the server build's
fetch.
#Verification
Each target's status says how far its guide has been checked:
- Verified on the platform: deployed to the real service by hand, on the date given.
- Tested locally: run on a development machine in the target's own runtime (a container, the platform's emulator), by the script named or by hand; not on the real service.
- Not tested: written from the platform's documentation only.
- Planned: no guide yet.
| Target | Guide | Status | Checked | What was checked |
|---|---|---|---|---|
| Docker | Docker | Tested locally | 2026-10-12 | The Dockerfile on the page, on a [email protected] --template ssr project with packages from npm: builds, healthy, serves pages (brotli) and immutable assets, runs as node, stops in under a second. Docker 29.8, linux/arm64. The npm variant is not tested. |
| Docker, bookshelf sample | Docker | Tested locally | 2026-10-12 | examples/ssr-bookshelf/e2e/docker-smoke.ts (local only, not in CI): health, a streamed page compressed on the fly, database queries, a sign-in form post, prerendered pages, immutable hashed assets served from their .br copies, the node user, no dev dependency in the image, a prompt stop. Docker 29.8, linux/arm64. |
| VPS: systemd | VPS | Tested locally | 2026-10-12 | The unit on the page ran the npm project under systemd 252 (Debian 12, in a privileged container): active, serving, stopped cleanly; systemd-analyze verify passes. Not on a VPS. |
| VPS: pm2 | VPS | Tested locally | 2026-10-12 | The ecosystem file on the page with pm2 6.0 (fork mode) in a container: serves, inherits the shell's variables, survives pm2 save and pm2 resurrect. pm2 startup and cluster mode are not tested. |
| Caddy | VPS | Tested locally | 2026-10-12 | The Caddyfile on the page (Caddy 2.11, localhost with Caddy's local CA instead of a public certificate) in front of the bookshelf container: HTTP to HTTPS redirect, HTTP/2, streamed chunks not held back, brotli and gzip passed through unchanged (with encode too), immutable assets, a form post and a cross-site 403. |
| nginx | VPS | Tested locally | 2026-10-12 | The config on the page (nginx 1.31, a self-signed certificate in place of certbot's) in front of the bookshelf container: nginx -t, the same checks as Caddy (with gzip on too); streaming also with nginx's default proxy_buffering on. |
| Coolify | Coolify | Not tested | Not deployed to a Coolify instance yet. It builds the Dockerfile tested above; the steps follow Coolify v4's documentation. | |
| Cloudflare Workers | Cloudflare | Tested locally | 2026-10-13 | examples/ssr-deploy, pnpm e2e:cloudflare (local only, not in CI) in [email protected] dev (workerd): prerendered page and assets with _headers (defaults, lumiose({ headers }), immutable hashed assets, not for a public/assets/ file), a streamed server page with a CSP nonce, endpoints with ctx.platform (a vars binding, CF-Connecting-IP), an npm package, a form post to a prerendered page and a cross-site 403, the not-found page, waitUntil. process.env holds vars with nodejs_compat. |
| Cloudflare, static site | Static hosts | Tested locally | 2026-10-12 | examples/ssg-travel-journal's dist/client by hand in [email protected] dev (assets only, not_found_handling: "404-page") and wrangler pages dev: pages, _headers, 404.html with status 404. Both serve the .br/.gz copies as plain files, hence the advice to leave them out. |
| Vercel, Node.js runtime | Vercel | Tested locally | 2026-10-12 | pnpm e2e:vercel (local only): config.json and .vc-config.json checked against the Build Output API v3, the function directory copied out of the repository (npm package traced in), then served by a Node.js harness that follows the routes and calls handler(req, res) as Vercel's launcher does: the same checks as Cloudflare. A harness, not Vercel's router or runtime. |
| Vercel, Edge runtime | Vercel | Tested locally | 2026-10-12 | pnpm e2e:vercel --edge: the same validation and checks, with the bundled module (it imports only node:async_hooks) called in Node.js, not in Vercel's Edge runtime. |
| Netlify | Netlify | Tested locally | 2026-10-12 | pnpm e2e:netlify (local only) with [email protected] serve, which runs the build and bundles both functions: the same checks as Cloudflare, except two the local server cannot show: it sends max-age=0 for static files (the immutable rule is checked in _headers), and its invocations share no state, so waitUntil is not observed. |
| Netlify, static site | Static hosts | Tested locally | 2026-10-12 | examples/ssg-travel-journal's dist/client by hand with [email protected] serve: pages with and without the slash, _headers, 404.html with status 404. |
| Bun | Bun | Tested locally | 2026-10-12 | pnpm e2e:runtimes bun (local only): [email protected] dist/server/entry.js, the same checks as Cloudflare (static files from dist/client, ctx.platform.clientAddress from Bun). The Dockerfile on the page is not tested. |
| Deno | Deno | Tested locally | 2026-10-12 | pnpm e2e:runtimes deno (local only): [email protected] serve --allow-net --allow-read --allow-env, the same checks. Deno Deploy is not tested. |
| Fly.io | Fly.io | Not tested | Runs the Docker image tested above; fly.toml follows Fly's documentation. |
|
| Render | Render | Not tested | Settings from Render's documentation; the build and start commands are the Node adapter's, tested on a VPS. | |
| Railway | Railway | Not tested | Settings from Railway's documentation; the build and start commands are the Node adapter's. | |
| GitHub Pages | Static hosts | Not tested | The workflow follows GitHub's Pages actions. | |
| S3 and CloudFront | Static hosts | Not tested | The commands follow AWS's documentation. | |
| AWS Lambda | AWS Lambda | Not tested | Not run in Lambda. The handler on the page was called in Node.js 26 with Function URL (payload 2.0) events against the sample's build: an endpoint, a streamed page (buffered), a form post, the not-found page, a static file. |