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: publicstatus/deploy/Dockerfile.
Anatomy
Section titled “Anatomy”Two stages, both Bun: build produces dist/ + dist-server/, runtime
ships only the bundle artifacts + checked-in SQL under kumiko/.
ARG BUN_VERSION=1.4.0ARG NPM_AUTH_TOKEN=ARG BUILD_VERSION=devARG BUILD_TIME=unknown
# ---------- build: produces dist/ + dist-server/ ----------FROM oven/bun:${BUN_VERSION}-alpine AS buildWORKDIR /app
ARG NPM_AUTH_TOKENENV GITHUB_TOKEN=${NPM_AUTH_TOKEN}
COPY . .RUN bun install --frozen-lockfileRUN bun run build
# ---------- runtime: bun-alpine, bundle artifacts only ----------FROM oven/bun:${BUN_VERSION}-alpine AS runtimeARG BUILD_VERSION=devARG BUILD_TIME=unknownWORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/dist-server ./RUN bun install --production
COPY --from=build --chown=app:app /app/dist ./distCOPY --from=build --chown=app:app /app/kumiko ./kumikoCOPY --from=build --chown=app:app /app/seeds ./seeds
USER app
ENV NODE_ENV=productionENV PORT=3000ENV KUMIKO_REPO_ROOT=/appENV INIT_CWD=/appENV BUILD_VERSION=$BUILD_VERSIONENV BUILD_TIME=$BUILD_TIME
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD wget --quiet --spider "http://127.0.0.1:${PORT}/health" || exit 1
CMD ["sh", "-c", "exec bun run server.js"]Self-contained runtime image (Bun + production node_modules for native
externals + your app). Build context is the app repository root.
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:latestSchema apply (pre-deploy step)
Section titled “Schema apply (pre-deploy step)”The image includes a bundled kumiko.js CLI. Run it as an ephemeral
container before starting your app:
docker run --rm \ -e DATABASE_URL="postgresql://user:pass@host:5432/db" \ ghcr.io/your-org/your-app:latest \ bun /app/kumiko.js schema applyRequired before every deploy. Idempotent, fast no-op if nothing pending.
The boot gate refuses to start the app if the schema doesn’t match the
journal (SchemaDriftError).
Build args (for CI)
Section titled “Build args (for CI)”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 deploy/Dockerfile .Tag both :latest and :${SHA}, :latest for rolling deploys, :${SHA} for rollbacks.
Multi-arch (arm64)
Section titled “Multi-arch (arm64)”For Hetzner CAX (arm) or Apple Silicon servers, build with QEMU emulation:
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.rocks build-image.yml for a working example.