Skip to content

Going to production

Development runs on nwire dev — a Vite host with watch and Studio. Production is a build step: nwire build bundles the boot entry into one optimized file that runs on plain Node, with no dev dependencies and nothing resolved at runtime.

Build

sh
pnpm build        # nwire build

For each wire, this produces dist/<wire>/main.js. The bundle bakes in the generated virtual:nwire-registry and virtual:nwire-config modules, so the app boots with no filesystem scan and no dynamic import. Workspace dependencies are externalized, so the runtime needs only the production node_modules.

Run

sh
pnpm start        # node dist/*/main.js

Plain Node (or Bun, or Deno) runs the built file. tsx and the rest of the dev toolchain are gone — the runtime image carries only what the app imports.

Health probes

The endpoint serves Kubernetes-style probes on a dedicated operational port (default 9400):

  • GET /live — the process is up.
  • GET /ready — the app can serve. It flips to 503 while booting and again during shutdown drain, so a load balancer stops sending traffic before the process exits.

Configure the probe port and graceful-shutdown budgets in config/app.ts.

Docker

The service and enterprise templates ship a multi-stage Dockerfile. The builder stage installs everything and runs nwire build; the runtime stage installs production dependencies only, copies the bundle, drops to a non-root user, and declares a readiness HEALTHCHECK against the probe port.

dockerfile
# builder — full install + nwire build
FROM node:24-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

# runtime — production deps only, non-root, healthcheck
FROM node:24-alpine AS runtime
RUN corepack enable
WORKDIR /app
ENV NODE_ENV=production
COPY package.json pnpm-lock.yaml* ./
RUN pnpm install --prod --frozen-lockfile
COPY --from=builder --chown=node:node /app/dist ./dist
USER node
EXPOSE 3000 9400
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=3 \
  CMD node -e "fetch('http://127.0.0.1:9400/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD node dist/*/main.js
sh
docker build -t my-app .
docker run -p 3000:3000 --env-file .env my-app

Local infrastructure

Every battery defaults to an in-process driver, so the app runs with no infrastructure at all. When you switch a driver to a real backend, the template's docker-compose.yml brings up what you need — Mailhog, MinIO, Redis, Mongo, Postgres — each mapped to a config/ knob.

sh
nwire infra up            # start everything
nwire infra up mailhog    # start one service
nwire infra down          # stop (keep data); down -v to wipe

Mailhog gives an SMTP sink with a web inbox; MinIO an S3-compatible store with a console; Redis backs cache and queues. Point the matching config/ slice at them and the app picks them up.

See also

MIT licensed.