Vellum/Dockerfile
mithral 5283e073f5 Web UI: Svelte app, embedded into the binary
The frontend the app needs to be usable at all.

- web/: Svelte 5 + Vite + TypeScript SPA. Login, app shell with per-type nav,
  library list/create/scan/delete, item grid with covers, admin user
  management, and an epub.js reader that syncs progress.
- Reader resumes by spine index — an integer — which is why the polymorphic
  progress table needs no text column.
- Session state uses classic stores in a plain .ts module rather than runes in
  a .svelte.ts module, avoiding special module resolution.
- Embedding: web/embed.go embeds web/dist. A committed .gitkeep keeps the
  directive valid before the frontend is built, so `go build ./...` never
  fails. Unknown non-API paths serve index.html for client routes; /api/* and
  /healthz always return JSON and never fall back to HTML.
- Dockerfile builds the UI in a node stage and overlays dist into the Go stage.
- pnpm 12 needs allowBuilds in pnpm-workspace.yaml; esbuild approved, the two
  unused transitive scripts explicitly denied so install exits 0.

svelte-check: 0 errors, 0 warnings. Verified served: SPA shell at /, route
fallback, hashed asset, and JSON 404 on /api/*.
2026-10-09 16:40:58 -04:00

68 lines
2.4 KiB
Docker

# syntax=docker/dockerfile:1
# ── Frontend ───────────────────────────────────────────────────────────
# Built first so the Go stage can embed the result. Runs on the builder's
# native platform; the output is architecture-independent.
FROM --platform=$BUILDPLATFORM node:22-alpine AS web
WORKDIR /web
RUN corepack enable
# Dependency layer, cached independently of source changes.
COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY web/ ./
RUN pnpm build
# ── Go build ───────────────────────────────────────────────────────────
# CGO_ENABLED=0 works because the SQLite driver (modernc.org/sqlite) is pure
# Go, so there is no cgo and no libc to match against.
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS build
ARG TARGETOS=linux
ARG TARGETARCH=amd64
ARG VERSION=dev
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Overlay the freshly built UI over the committed placeholder.
COPY --from=web /web/dist ./web/dist
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o /out/vellum ./cmd/vellum
# ── Runtime ────────────────────────────────────────────────────────────
FROM alpine:3.22
RUN apk add --no-cache ca-certificates tzdata su-exec
COPY --from=build /out/vellum /usr/local/bin/vellum
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# /data persistent state (SQLite database) — back this up
# /config regenerable cache (thumbnails, extracted pages)
# /media the library, mounted read-only; Vellum never writes here
VOLUME ["/data", "/config"]
ENV VELLUM_DATA_DIR=/data \
VELLUM_CONFIG_DIR=/config \
VELLUM_LISTEN=:7788
# PUID/PGID are consumed by entrypoint.sh, which drops root privileges after
# taking ownership of the data and cache directories.
ENV PUID=1001 \
PGID=1001
EXPOSE 7788
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://127.0.0.1:7788/healthz || exit 1
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]