108 lines
4.2 KiB
Docker
108 lines
4.2 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# Prisma 7 supports Node 20.19+, 22.12+ and 24.x and prints an unsupported
|
|
# warning on newer majors, so this tracks the current LTS rather than latest.
|
|
ARG NODE_VERSION=24-slim
|
|
ARG PNPM_VERSION=11.25.0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# base - shared runtime, with pnpm baked in so no stage fetches it later
|
|
# ---------------------------------------------------------------------------
|
|
FROM node:${NODE_VERSION} AS base
|
|
ARG PNPM_VERSION
|
|
ENV PNPM_HOME=/pnpm \
|
|
PATH=/pnpm:$PATH \
|
|
CHECKPOINT_DISABLE=1
|
|
ENV CI=true
|
|
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
|
WORKDIR /app
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# build - install, generate the Prisma client, compile all three packages
|
|
# ---------------------------------------------------------------------------
|
|
FROM base AS build
|
|
|
|
# better-sqlite3's install step is `prebuild-install || node-gyp rebuild`, so a
|
|
# toolchain has to exist for the fallback. It never reaches a runtime stage.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends python3 make g++ openssl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Manifests first: dependencies only reinstall when the lockfile actually moves.
|
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
COPY apps/backend/package.json apps/backend/
|
|
COPY apps/frontend/package.json apps/frontend/
|
|
COPY packages/database/package.json packages/database/
|
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
|
pnpm install --frozen-lockfile
|
|
|
|
COPY . .
|
|
|
|
# `prisma generate` resolves datasource.url through prisma7.config.ts, so the
|
|
# variable must exist - but generate only writes the client, it never connects.
|
|
ENV DATABASE_URL="file:/tmp/build.db"
|
|
RUN pnpm --filter database db:generate \
|
|
&& pnpm --filter database build \
|
|
&& pnpm --filter backend build \
|
|
&& pnpm --filter frontend build
|
|
|
|
# Everything is compiled, so drop devDependencies in place. Safe here because
|
|
# every runtime import in both apps resolves to a production dependency; the
|
|
# one devDependency reference, `import("vite")` in server.js, sits in the dev
|
|
# branch that --prod never takes.
|
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
|
pnpm install --prod --frozen-lockfile
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# backend - Express API. Not published to the host; the frontend proxies to it.
|
|
# ---------------------------------------------------------------------------
|
|
FROM base AS backend
|
|
ENV NODE_ENV=production
|
|
|
|
COPY --from=build /app /app
|
|
|
|
# Applying migrations on boot keeps a fresh volume usable. `migrate deploy` only
|
|
# applies migrations it has not already recorded, so restarts are a no-op.
|
|
COPY <<'EOF' /usr/local/bin/blog-backend
|
|
#!/bin/sh
|
|
set -e
|
|
cd /app/packages/database
|
|
./node_modules/.bin/prisma migrate deploy
|
|
cd /app/apps/backend
|
|
exec node dist/index.js
|
|
EOF
|
|
RUN chmod +x /usr/local/bin/blog-backend
|
|
|
|
# The SQLite file lives on a volume mounted here.
|
|
RUN mkdir -p /data && chown node:node /data
|
|
USER node
|
|
WORKDIR /app/apps/backend
|
|
|
|
# src/index.ts calls app.listen(3000) directly - env.PORT is parsed but unused.
|
|
EXPOSE 3000
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD ["node", "-e", "fetch('http://127.0.0.1:3000/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
|
|
CMD ["blog-backend"]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# frontend - SSR server: renders / and /post/:slug, proxies /api to the backend
|
|
# ---------------------------------------------------------------------------
|
|
FROM base AS frontend
|
|
ENV NODE_ENV=production \
|
|
PORT=5173 \
|
|
BACKEND_ORIGIN=http://backend:3000
|
|
|
|
COPY --from=build /app /app
|
|
|
|
USER node
|
|
WORKDIR /app/apps/frontend
|
|
|
|
EXPOSE 5173
|
|
# /healthz, not /: rendering / reaches the backend, so probing it would report
|
|
# a backend outage as a dead frontend, and Traefik drops unhealthy containers.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD ["node", "-e", "fetch('http://127.0.0.1:'+(process.env.PORT||5173)+'/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
|
|
CMD ["node", "server.js", "--prod"]
|