FROM node:22-alpine AS builder WORKDIR /app COPY package.json package-lock.json* ./ # `npm ci` installs the locked versions exactly; `npm install` would # silently rewrite package-lock.json mid-build. CI (.gitea/workflows) # also uses `npm ci`, so this keeps the image build deterministic and # matches what the test job validated. RUN npm ci COPY . . RUN npm run build FROM node:22-alpine WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 # node:22-alpine ships a `node` user (UID 1000); use it instead of # running the SvelteKit server as root. COPY --from=builder --chown=node:node /app/build ./build COPY --from=builder --chown=node:node /app/node_modules ./node_modules COPY --from=builder --chown=node:node /app/package.json ./ USER node EXPOSE 3000 # Alpine's busybox `wget` is the canonical lightweight HTTP probe. # `--spider` doesn't follow redirects; `node build` serves a 200 on # `/` for the homepage so this works without a dedicated /health. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -q --spider http://localhost:3000/ || exit 1 CMD ["node", "build"]