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
pnpm build # nwire buildFor 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
pnpm start # node dist/*/main.jsPlain 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 to503while 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.
# 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.jsdocker build -t my-app .
docker run -p 3000:3000 --env-file .env my-appLocal 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.
nwire infra up # start everything
nwire infra up mailhog # start one service
nwire infra down # stop (keep data); down -v to wipeMailhog 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
- Configuration — the config baked into the build
- Graceful shutdown — drain budgets and signals
- Observability — telemetry in production