Skip to content

Deploy: Docker

Multi-stage Dockerfile pattern for any Kumiko app. Used by all production deploys (single-VM, K3s, anywhere a container runtime exists).

Reference implementation: samples/showcases/publicstatus/deploy/Dockerfile.

Anatomy

Two stages: build with Node + Yarn 4 (workspace resolution needs them), runtime with Bun-only.

ARG BUN_VERSION=1.2.20
# ---------- build: produces dist/ + dist-server/ ----------
FROM node:20-alpine AS build
WORKDIR /app
RUN apk add --no-cache curl bash unzip
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}"
ENV PATH=/root/.bun/bin:$PATH
COPY . .
RUN corepack enable && yarn install --immutable
WORKDIR /app/samples/showcases/your-app
RUN yarn build
# ---------- runtime: bun + dist-server + dist + drizzle ----------
FROM oven/bun:${BUN_VERSION}-alpine AS runtime
WORKDIR /app
COPY --from=build /app/samples/showcases/your-app/dist-server ./
RUN bun install --production
COPY --from=build /app/samples/showcases/your-app/dist ./dist
COPY --from=build /app/samples/showcases/your-app/drizzle ./drizzle
ENV KUMIKO_REPO_ROOT=/app
ENV INIT_CWD=/app
ENV KUMIKO_MIGRATION_HOOKS=/app/migration-hooks.js
CMD ["sh", "-c", "exec bun run server.js"]

Image size: ~270 MB total (Bun + 7 native externals + your app). Self-contained.

Run

Terminal window
docker run --rm \
-e DATABASE_URL="postgresql://user:pass@host:5432/db" \
-e REDIS_URL="redis://host:6379" \
-p 3000:3000 \
ghcr.io/your-org/your-app:latest

Migrate (pre-deploy step)

The image includes a bundled kumiko.js CLI. Run it as an ephemeral container before starting your app:

Terminal window
docker run --rm \
-e DATABASE_URL="postgresql://user:pass@host:5432/db" \
ghcr.io/your-org/your-app:latest \
bun /app/kumiko.js migrate apply

Required before every deploy. Idempotent — fast no-op if nothing pending. Boot-gate refuses to start the app if the schema doesn’t match the journal.

Build args (for CI)

Terminal window
docker build \
--build-arg BUILD_VERSION="$(git describe --tags --always)" \
--build-arg BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-t ghcr.io/your-org/your-app:${SHA} \
-t ghcr.io/your-org/your-app:latest \
-f samples/showcases/your-app/deploy/Dockerfile .

Tag both :latest and :${SHA}:latest for rolling deploys, :${SHA} for rollbacks.

Multi-arch (arm64)

For Hetzner CAX (arm) or Apple Silicon servers, build with QEMU emulation:

Terminal window
docker buildx build --platform linux/arm64 --push -t ... .

In GitHub Actions: docker/setup-qemu-action@v3 + platforms: linux/arm64 on docker/build-push-action. See docs.kumiko.so build-image.yml for a working example.