# ============================================================================= # Dockerfile — Greg Kilmer Profile Site # ============================================================================= # Multi-stage build producing a minimal Node server image for the Cosmos homelab. # # Stage 1 (builder): install all deps, run astro build -> dist/server/entry.mjs # Stage 2 (runtime): copy only dist + prod node_modules, run the Node server # # The @astrojs/node standalone adapter emits a self-contained server, so the # runtime image needs only node_modules + dist. Build once, run anywhere. # ============================================================================= # ---------- Stage 1: build ---------- FROM node:22.12.0-alpine AS builder WORKDIR /app # Install deps. Copy manifests first to leverage Docker layer caching. COPY package.json package-lock.json* ./ RUN npm ci --no-audit --no-fund || npm install --no-audit --no-fund --legacy-peer-deps # Copy source and build. SITE_URL can be overridden at build time. COPY . . ARG SITE_URL=https://gregkilmer.com ENV SITE_URL=$SITE_URL RUN npm run build # Prune to production dependencies for the runtime stage. RUN npm prune --omit=dev # ---------- Stage 2: runtime ---------- FROM node:20-alpine AS runtime WORKDIR /app # Run as a non-root user for defense in depth. RUN addgroup -S app && adduser -S app -G app # Copy production node_modules and the built server + client assets. COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/package.json ./package.json # Astro Node standalone server reads HOST/PORT from env at runtime. ENV HOST=0.0.0.0 ENV PORT=4321 EXPOSE 4321 USER app # Healthcheck: the server responds 200 on /. HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD wget -qO- http://127.0.0.1:${PORT}/ >/dev/null 2>&1 || exit 1 CMD ["node", "./dist/server/entry.mjs"]