librelad 875a60f90f LibrePortal v0.1.0 — initial release
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.

Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-21 20:37:54 +01:00

82 lines
3.4 KiB
Docker

# syntax=docker/dockerfile:1.7
# Multi-stage build for moneyapp.
#
# Stages:
# deps — install all deps with the npm cache mounted, so re-builds
# reuse it instead of re-downloading every package.
# builder — compile migrate.ts → migrate.js (drops the runtime tsx
# dep), build the standalone Next.js bundle, then prune dev
# dependencies so only runtime packages survive into runner.
# runner — minimal final image: standalone Next.js bundle + the
# pruned production node_modules (better-sqlite3's native
# binding lives there).
#
# Notes:
# * We do NOT run `npm run db:generate` at build time. The drizzle/
# SQL migrations are committed to the repo, so generating them on
# every build is wasted work and a frequent source of permission /
# filesystem errors during `docker build`.
# * `output: "standalone"` in next.config.ts means we don't need to
# copy the entire builder node_modules into runner — just the bits
# standalone marks as external (better-sqlite3, drizzle).
FROM node:22-bookworm-slim AS deps
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 build-essential \
&& rm -rf /var/lib/apt/lists/*
COPY app/package.json app/package-lock.json* ./
RUN --mount=type=cache,target=/root/.npm npm ci
FROM node:22-bookworm-slim AS builder
WORKDIR /build
COPY --from=deps /build/node_modules ./node_modules
COPY app/ ./
ENV NEXT_TELEMETRY_DISABLED=1
# Compile the TS migrator into a single self-contained JS file so the
# runtime image doesn't need tsx (50+ MB) just to run migrations.
# better-sqlite3 + drizzle stay external so they resolve from
# node_modules at runtime.
RUN npx --yes esbuild src/db/migrate.ts \
--bundle --platform=node --target=node22 \
--external:better-sqlite3 --external:drizzle-orm \
--outfile=migrate.js
# Next.js's "Collecting page data" phase imports server modules to
# gather route metadata. Auth.js + the Drizzle adapter open a sqlite
# connection on import, so /data has to exist and AUTH_SECRET has to
# be non-empty *during the build* even though both get overridden at
# runtime by the real values.
RUN mkdir -p /tmp/build-db \
&& DATABASE_FILE=/tmp/build-db/moneyapp.db \
AUTH_SECRET=build-time-placeholder-not-used-at-runtime \
AUTH_URL=http://localhost:3000 \
npm run build
# Drop devDependencies so the node_modules we copy into runner has
# only what production needs (no typescript, drizzle-kit, eslint...).
RUN npm prune --omit=dev
FROM node:22-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_FILE=/data/moneyapp.db
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd -r app && useradd -r -g app app \
&& mkdir -p /data && chown app:app /data
COPY --from=builder /build/public ./public
COPY --from=builder /build/.next/standalone ./
COPY --from=builder /build/.next/static ./.next/static
COPY --from=builder /build/drizzle ./drizzle
COPY --from=builder /build/migrate.js ./migrate.js
# Last so it overlays standalone's traced node_modules with the full
# pruned tree — that way better-sqlite3's native .node binding (which
# standalone marks external) is present at runtime.
COPY --from=builder /build/node_modules ./node_modules
USER app
EXPOSE 3000
VOLUME ["/data"]
CMD ["sh", "-c", "node migrate.js && node server.js"]