From 046fa2189f0ada4e5af61272714bbc29786cda8c Mon Sep 17 00:00:00 2001 From: Eric Harding Date: Mon, 13 Jul 2026 07:50:57 -0400 Subject: [PATCH] add containerfile --- .dockerignore | 5 +++++ CLAUDE.md | 22 +++++++++++++++++++--- Containerfile | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 Containerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..184a506 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +dist +data +.git +*.log diff --git a/CLAUDE.md b/CLAUDE.md index d0a3ab2..0c9b224 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,9 +55,25 @@ pnpm backup # scripts/backup.ts — see DESIGN.md §10 Nunjucks template changes are not hot-reloaded (chokidar isn't a dependency, to avoid the extra install); restart `pnpm dev` after editing `.njk` files. -The dev server and `dist/server.js` both bind to `127.0.0.1:3000`. The SQLite -file lives at `data/app.db` (gitignored) and bootstraps itself from -`schema.sql` on first run if missing. +The dev server and `dist/server.js` bind to `127.0.0.1:3000` by default; +override with the `HOST`/`PORT` env vars (the `Containerfile` sets +`HOST=0.0.0.0` so container port mapping works). The SQLite file lives at +`data/app.db` (relative to `process.cwd()`, gitignored) and bootstraps itself +from `schema.sql` on first run if missing. + +## Container deployment + +`Containerfile` is a two-stage build (compile w/ native toolchain → slim +alpine runtime). It runs as a non-root `app` user and expects `data/app.db` +to persist via a bind-mounted host folder at `/app/data`: + +``` +docker build -t hardingsclocks . +docker run -d -p 3000:3000 -v /path/on/host/data:/app/data hardingsclocks +``` + +Caddy (or whatever's doing auth/TLS per DESIGN.md §9) should reverse-proxy to +the mapped `3000` port on the host. ## Structure diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..8bc9808 --- /dev/null +++ b/Containerfile @@ -0,0 +1,44 @@ +# syntax=docker/dockerfile:1 + +# --- build stage: compile TypeScript + better-sqlite3's native binding --- +FROM node:20-alpine AS build +WORKDIR /app + +# python3/make/g++ are needed for node-gyp to build better-sqlite3 from source +RUN apk add --no-cache python3 make g++ + +RUN npm install -g pnpm@9 + +COPY package.json pnpm-lock.yaml ./ +# pnpm.onlyBuiltDependencies in package.json allows the better-sqlite3 +# postinstall (native compile) to run non-interactively +RUN pnpm install --frozen-lockfile + +COPY tsconfig.json ./ +COPY src ./src +RUN pnpm build + +# drop devDependencies, keep the already-compiled better-sqlite3 binding +RUN pnpm prune --prod + +# --- runtime stage --- +FROM node:20-alpine +WORKDIR /app +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOST=0.0.0.0 + +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist +COPY --from=build /app/package.json ./package.json +COPY public ./public + +# data/app.db lives here; mount a host folder here to persist it, e.g.: +# docker run -v /srv/hardingsclocks/data:/app/data ... +# node:alpine ships a "node" user at uid/gid 1000 — chown the host folder to +# 1000:1000 (or match this container's uid if you change it) so it's writable. +RUN mkdir -p /app/data && chown -R node:node /app/data + +USER node +EXPOSE 3000 +CMD ["node", "dist/server.js"]