Docker Setup
Run the VeloxKit docs site in a container for a reproducible local preview, CI builds, or a static-site deployment — without polluting your host with Node, Bun, or pnpm versions.
Docker is optional. Use it if you want hermetic builds, CI parity, or a static export you can host on any static provider. For day-to-day docs work, the local npm run dev flow in the Installation page is faster.
What's Included
| File | Purpose |
|---|---|
Dockerfile | Multi-stage build: deps → builder → runner (Next.js standalone) and static (nginx) |
docker-compose.yml | Local stack with three profiles: default preview, dev (hot reload), static (nginx) |
.dockerignore | Excludes .git, node_modules, .next, secrets |
docker/nginx.conf | Hardened nginx config for the static stage |
Quick Start
Build and run the production preview
docker build -t veloxkit-docs .
docker run --rm -p 3000:3000 veloxkit-docsOpen http://localhost:3000 (opens in a new tab) in your browser.
Or use Compose
docker compose up docsSame port, same image, same behaviour. Compose also defines dev and static profiles.
The Three Targets
Stage 3 — runner
A minimal Node image that runs Next.js's standalone server.
docker build --target runner -t veloxkit-docs .
docker run --rm -p 3000:3000 veloxkit-docs- Image size: ~180 MB
- Cold start: ~600 ms
- Best for: production preview, internal hosting
Build Matrix
| Goal | Command | Image size | Cold start |
|---|---|---|---|
| Local dev (hot reload) | docker compose --profile dev up | ~250 MB | n/a |
| Local prod preview | docker compose up docs | ~180 MB | ~600 ms |
| Static export | docker compose --profile static up | ~25 MB | ~80 ms |
| CI build (Next.js standalone) | docker build --target runner . | ~180 MB | ~600 ms |
| CI build (static) | docker build --target static . | ~25 MB | ~80 ms |
Switching Package Managers
The Dockerfile autodetects the lockfile at build time:
bun.lock/bun.lockb→ Bun is installed globally and used.pnpm-lock.yaml→ pnpm via corepack.yarn.lock→ Yarn via corepack.- Otherwise → npm.
This means the Dockerfile works whether your team uses bun, pnpm, yarn, or npm. No edits needed.
Health Check
The runner stage exposes a health endpoint at /api/health. The Compose file wires docker compose ps to show a healthy state once Nextra boots.
If you do not have a /api/health route yet, add this to pages/api/health.ts:
import type { NextApiRequest, NextApiResponse } from 'next'
export default function handler(_req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ ok: true, ts: Date.now() })
}CI Recipes
GitHub Actions — build and push to GHCR
name: docker
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
target: static
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=maxThe static target is the right pick for a docs site — small, fast, deployable to any static host.
Vercel
You can keep using Vercel. The Dockerfile is for cases when you want a fully self-hosted preview or a static export to a non-Vercel provider.
If you switch to static export and host on Vercel, set output: 'export' in next.config.js and the existing Vercel flow picks it up automatically.
Troubleshooting
Port 3000 is already in use
Change the host port in docker-compose.yml or the run command:
docker run --rm -p 4000:3000 veloxkit-docsHot reload not picking up changes
On Linux, ensure your user can watch files inside the container. Either:
- Use Docker Desktop with the new
develop.watch(already configured indocker-compose.yml). - Or run the dev server on the host (
npm run dev).
"Cannot find module 'next'"
The volume mount may have wiped the image's node_modules. Re-run:
docker compose --profile dev down -v
docker compose --profile dev upStatic export fails
Nextra's static export requires the site to be a pure static site — no API routes, no server components, no ISR. If the export fails, fall back to the runner target and host the Next.js server.
Operational Notes
- The
runnerstage runs as the unprivilegednodeuser that the officialnode:20-bookworm-slimimage ships with. - The
staticstage uses the officialnginx:1.27-alpineand is non-root by default. NEXT_TELEMETRY_DISABLED=1is set in every stage that runs the Next.js build, so you do not accidentally opt into Next.js telemetry.- The
staticstage adds minimal security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy) but does not enforce HTTPS — terminate TLS at your edge or load balancer.
Verifying the Build
# 1. Build should succeed
docker build -t veloxkit-docs:test .
# 2. Health check should return 200
docker run --rm -d --name vk-test -p 3000:3000 veloxkit-docs:test
sleep 5
docker exec vk-test node -e "require('http').get('http://127.0.0.1:3000/api/health',r=>console.log('status',r.statusCode)).on('error',e)=>console.error(e))"
docker stop vk-testIf both pass, your Dockerfile is healthy and the docs site is reproducible.