Deploy to Node and Docker
The standalone Node server, the multi-stage Dockerfile and notes for VPS and container platforms.
The Node target produces a self-contained server in dist/server/entry.mjs that serves static
files and on-demand routes from one process. It is the default target and the one used by the
end-to-end tests.
Build and run#
pnpm build:node
HOST=0.0.0.0 PORT=4321 pnpm start
The server reads configuration from the process environment only; nothing is loaded from .env
at runtime. Provide DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL and SITE_URL (at
build time) through your platform.
Docker#
The multi-stage Dockerfile installs dependencies with pnpm, builds the site, then copies the
build output, the migrations and the production dependencies into a node:24-slim image running
as the unprivileged node user, with a health check against /api/health.
docker build --build-arg SITE_URL=https://example.com -t strata-stack .
docker run -p 4321:4321 --env-file .env strata-stack
docker-compose.yml adds a named volume at /app/.data so a file: database survives
restarts. For production prefer Turso or another hosted libSQL/SQLite service so the database is
not tied to one container.
Run migrations before starting a new version. The image ships scripts/migrate.ts, which
applies the SQL files in drizzle/ with Drizzle’s migrator and needs no dev dependencies:
docker run --rm --env-file .env strata-stack node scripts/migrate.ts
Without a container, the Migrate database GitHub Actions workflow applies the same migrations to a Turso database; see CI and releases.
Reverse proxy#
Put the server behind a TLS-terminating proxy (Caddy, nginx, a platform load balancer). The
middleware adds security headers to server-rendered responses; for static files add them at the
proxy, using public/_headers as the reference list. Make sure the proxy forwards
X-Forwarded-For so rate limiting sees real client addresses.
Platforms#
- Fly.io, Railway, Render: point them at the Dockerfile; set the port to 4321.
- Kubernetes: use the health check path
/api/healthfor liveness and readiness probes. - Bare VPS: run with a process manager such as
systemdorpm2, and keep Node 24 current.
Static hosting only#
If you remove authentication and every prerender = false route, the site becomes fully static.
Set output: 'static' (already the default), delete the adapter and upload dist/ to any static
host.