Documentation

Docker

What you'll have at the end: a Vura app running in a Docker container, buildable and runnable on any Docker-capable host.

Supports all route kinds: serverless, hot (WebSocket), task.


How the Dockerfile is generated

When vura build detects hot routes, it emits dist/Dockerfile automatically. The build context for that Dockerfile is the dist/ directory itself — all paths inside the file are relative to dist/.

When no hot routes are present, the same Dockerfile pattern still works; you just need to provide it yourself (copy the one below).


Steps

1. Scaffold and build

npm create vura@latest my-app
cd my-app
npm install
npm run build

After npm run build, dist/Dockerfile will be present if the project has hot routes.

2. The Dockerfile

If dist/Dockerfile was not emitted (no hot routes), create it:

# dist/Dockerfile
# Generated by vura build — hand-edits will be overwritten on next build.
# Build context must be the dist/ directory:
#   docker build -f dist/Dockerfile dist
FROM node:22-slim
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev --no-audit --no-fund
COPY . ./
ENV NODE_ENV=production PORT=3000
EXPOSE 3000
CMD ["node", "server/entry.js"]

This is the exact content vura build emits into dist/Dockerfile. The build context is dist/ — note the trailing argument below.

3. Build the image

docker build -f dist/Dockerfile -t my-app dist

Note: the build context is dist, not .COPY . ./ copies the contents of dist/ into the image.

4. Run the container

docker run -d -p 3000:3000 --name my-app my-app

5. Verify

curl -fsS http://localhost:3000/api/hello

Expected: JSON response from the hello route.

Check logs:

docker logs my-app

Expected: [vura] listening on port 3000

6. Healthcheck (optional, recommended)

Add to your docker run command or Docker Compose service:

docker run -d -p 3000:3000 \
  --health-cmd="curl -fsS http://localhost:3000/api/hello || exit 1" \
  --health-interval=10s \
  --health-timeout=5s \
  --health-retries=3 \
  --name my-app my-app

Or in docker-compose.yml:

services:
  app:
    build:
      context: dist
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - VURA_REVALIDATE_SECRET=change-me
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/hello"]
      interval: 10s
      timeout: 5s
      retries: 3

Smoke test

# Static page
curl -fsS http://localhost:3000/ | grep -q '<h1'

# API route
curl -fsS http://localhost:3000/api/hello

# WebSocket (requires wscat)
wscat -c ws://localhost:3000/api/chat

CI-tested: this guide is verified by the docker job in .github/workflows/selfhost.yml. The job extracts the Dockerfile from this guide (fenced dockerfile block), builds the image with dist/ as the build context, runs the container, and curls / and /api/hello. It does not test Docker Compose or healthcheck configuration.


Route kind support

All kinds: serverless, hot (WebSocket), task / cron.