Introduction
Deployment
Deploy Routecraft on Bun for the CLI path, or embed it inside a Node application.
Choose a path
The two paths can be mixed within the same project. See the Runtime reference for the rationale.
Bun CLI on a server
Routecraft's craft bin requires Bun on the host. A project scaffolded by create-routecraft already has the script that boots it:
{
"scripts": {
"start": "craft start --log-level info"
}
}
Then run bun run start. Any provider that lets you install Bun (a long-running container, a VM, or a Bun-native runtime) will work.
Docker
A Bun project scaffolded by create-routecraft ships a Dockerfile and a .dockerignore, so the image is one command:
docker build -t my-app .
The image is built to pass the container scans a bank or an enterprise registry runs:
- Distroless runtime. Production dependencies install on the full Bun image, and only
node_modulesis copied intooven/bun:<version>-distroless: no shell and no package manager, so a scanner finds almost nothing to flag. - Not root. The process runs as the unprivileged
nonrootuser. The project files are read-only to it;.routecraft/(telemetry and other local state) is the one directory it can write. - Pinned. The base images use the Bun version the project pins in
packageManager, never a floating tag. - No secrets in the build context.
.dockerignorekeeps.envfiles,.npmrc,node_modulesand local state out. A private registry token belongs in a build secret, never in the context.
The runtime stage, excerpted from the scaffolded Dockerfile:
FROM oven/bun:1.3.9-distroless
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps --chown=nonroot:nonroot /app/.routecraft ./.routecraft
COPY . .
USER nonroot
ENTRYPOINT ["/usr/local/bin/bun"]
CMD ["node_modules/.bin/craft", "start", "--log-level", "info"]
The runtime has no shell, so the image calls craft directly rather than through bun run start. Every change to Routecraft builds this image from a freshly scaffolded project and scans it with Trivy; a fixable HIGH or CRITICAL finding fails the change, and each build publishes the image's SBOM.
Three things change once the project runs in a container:
- Listen on every interface. A server binds
127.0.0.1unless itshostsays otherwise, which nothing outside the container can reach. Sethost: '0.0.0.0'on the servers you publish (see servers and http). - Keep state on a volume. Deferred exchanges, agent sessions and telemetry are stored under
.routecraft/, which a redeploy replaces. Mount a named volume there (docker run -v my-app-state:/app/.routecraft my-app) so pending work survives; a named volume keeps thenonrootownership. - No host binaries.
shell()andagentBrowser()from@routecraft/osrun programs the distroless runtime does not contain. A project that uses them needs a runtime stage that ships those programs, and is scanned as such.
A project on npm, pnpm or yarn gets no Dockerfile, because the scaffolded one installs from bun.lock. Install its production dependencies with its own package manager in the first stage (npm ci --omit=dev, pnpm install --frozen-lockfile --prod), and keep the distroless Bun runtime stage as it is: the runtime requirement is unchanged.
Node embedding on a server
When you embed @routecraft/routecraft inside a Node service, deploy it the same way you would any Node application: build the service (or use Node's runtime type stripping on Node 22.6+), then run it with node. No Bun on the host.
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "--experimental-strip-types", "src/server.ts"]
The Node 23.6+ image enables type stripping by default; drop the flag in that case. For a production image, pin the exact Node version (or digest) you scanned rather than the 22-alpine line, and keep .env and .npmrc out of the build context with a .dockerignore.
See the Programmatic Invocation guide for the embedding API and runnable examples.
Related
Runtime reference
Bun-only CLI, Node embedding, version floors.
CLI reference
All craft CLI commands including run.
Programmatic Invocation
Embed Routecraft inside Node, Express, or Next.js.