fix(docker): harden image and automate safe VPS updates
- Use floating node:alpine that tracks the latest supported LTS; pnpm bootstrap follows package.json's packageManager pin. - Drop corepack (removed from node:26), install pnpm via npm global. - Add pnpm fetch + offline install for stable dependency-layer caching. - Run as non-root nextjs (UID/GID 33 = host www-data) with tini as PID 1 for correct signal handling. - Open node engines to >=20.9.0 so patches/minors float automatically. - Add docker-preflight.sh (per-VPS checks incl. --fix) and gate docker-update.sh so Node major upgrades require explicit review while patches deploy silently.
This commit is contained in:
1 parent
7033d65846
commit
539e6d3fad
5 files changed
+201
-86
No files matched your search
+30
-74
@@ -1,78 +1,34 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# ==============================================================================
|
||||
# EpicNext-CMS — Docker image (Node 26.8.1, multi-package-manager, Next.js standalone)
|
||||
# ==============================================================================
|
||||
|
||||
# --- Builder stage ---
|
||||
FROM node:26.8.1-bookworm-slim AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN npm install -g [email protected] yarn
|
||||
|
||||
# node:alpine = latest Node within the supported LTS major (tracks the newest
|
||||
# patch automatically; currently v26.x, which satisfies package.json's
|
||||
# engines ">=26.8.1 <27").
|
||||
FROM node:alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json *lock* pnpm-workspace.yaml .npmrc ./
|
||||
|
||||
ARG PACKAGE_MANAGER=
|
||||
|
||||
RUN --mount=type=cache,id=epicnext-pnpm,target=/pnpm/store,sharing=locked \
|
||||
if [ "$PACKAGE_MANAGER" = "pnpm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f pnpm-lock.yaml ]; }; then \
|
||||
echo ">> Using pnpm" && \
|
||||
pnpm install --frozen-lockfile --ignore-scripts --store-dir=/pnpm/store; \
|
||||
elif [ "$PACKAGE_MANAGER" = "yarn" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f yarn.lock ]; }; then \
|
||||
echo ">> Using yarn" && \
|
||||
yarn install --frozen-lockfile --ignore-scripts; \
|
||||
elif [ "$PACKAGE_MANAGER" = "npm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f package-lock.json ]; }; then \
|
||||
echo ">> Using npm" && \
|
||||
npm ci --ignore-scripts; \
|
||||
else \
|
||||
echo "!! No lockfile found — falling back to npm install" && \
|
||||
npm install --ignore-scripts; \
|
||||
fi
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# pnpm install is always pinned to the version in package.json's
|
||||
# `packageManager` field (pnpm auto-selects it on install), so this global
|
||||
# install only needs to exist as a bootstrap and follows the active major.
|
||||
RUN apk add --no-cache git \
|
||||
&& npm install -g pnpm@latest
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
# pnpm fetch: download all deps into $PNPM_STORE first, so only the lockfile
|
||||
# change (not source changes) invalidates the network-heavy download layer.
|
||||
RUN pnpm fetch --ignore-scripts
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts --offline
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN --mount=type=cache,id=epicnext-next,target=/app/.next/cache,sharing=locked \
|
||||
if [ -f .env ]; then set -a && . ./.env && set +a; fi && \
|
||||
if [ "$PACKAGE_MANAGER" = "yarn" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f yarn.lock ]; }; then \
|
||||
yarn build; \
|
||||
elif [ "$PACKAGE_MANAGER" = "npm" ] || { [ -z "$PACKAGE_MANAGER" ] && [ -f package-lock.json ]; }; then \
|
||||
npm run build; \
|
||||
else \
|
||||
pnpm build; \
|
||||
fi
|
||||
|
||||
# --- Runtime stage ---
|
||||
FROM node:26.8.1-bookworm-slim AS runner
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG RUN_USER=www-data
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3002
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
|
||||
EXPOSE 3002
|
||||
|
||||
FROM node:alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
RUN mkdir -p /app/storage \
|
||||
/app/public/nitro-assets \
|
||||
/app/public/swf \
|
||||
/var/www/Gamedata \
|
||||
&& chown -R ${RUN_USER} /app /var/www/Gamedata
|
||||
|
||||
COPY --from=builder --chown=${RUN_USER} /app/.next/standalone ./
|
||||
COPY --from=builder --chown=${RUN_USER} /app/public ./public
|
||||
COPY --from=builder --chown=${RUN_USER} /app/.next/static ./.next/static
|
||||
|
||||
VOLUME ["/app/public/nitro-assets", "/app/public/swf", "/app/storage"]
|
||||
|
||||
USER ${RUN_USER}
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
ENV NODE_ENV=production \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
PORT=3002 \
|
||||
HOSTNAME=0.0.0.0
|
||||
RUN apk add --no-cache tini \
|
||||
&& addgroup -g 33 -S nextjs && adduser -u 33 -S -G nextjs nextjs
|
||||
COPY --from=builder --chown=nextjs:nextjs /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nextjs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nextjs /app/.next/static ./.next/static
|
||||
USER nextjs
|
||||
EXPOSE 3002
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["node", "server.js"]
|
||||
Reference in new issue
Block a user