Backend deploy target is self-hosted Docker (VPS / Kubernetes / Docker Engine), not Cloud Run. Add a multi-stage Dockerfile (Node 20, bcrypt compiled in build stage, non-root runtime), .dockerignore, a staging docker-compose, and DEPLOY.md covering install, build, migrate, run, and log mapping/rotation. Pin engines.node>=20. Update deployment.md runbook and backend/CLAUDE.md infra line off Cloud Run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
1.8 KiB
Docker
45 lines
1.8 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stage 1 — builder: install production deps, compiling native addons (bcrypt)
|
|
# ---------------------------------------------------------------------------
|
|
FROM node:20-bookworm-slim AS builder
|
|
WORKDIR /app
|
|
|
|
# Toolchain required to compile native modules (bcrypt) when no prebuilt
|
|
# binary matches the platform. Lives only in this stage.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
python3 make g++ \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Install against the lockfile for reproducible builds. Copy manifests first
|
|
# so this layer caches until deps actually change.
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci --omit=dev
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stage 2 — runtime: slim image with only prod node_modules + app source
|
|
# ---------------------------------------------------------------------------
|
|
FROM node:20-bookworm-slim AS runtime
|
|
ENV NODE_ENV=production
|
|
WORKDIR /app
|
|
|
|
# Compiled node_modules (same base image → ABI-compatible bcrypt .node binary).
|
|
COPY --from=builder /app/node_modules ./node_modules
|
|
COPY package.json ./
|
|
COPY src ./src
|
|
|
|
# Drop privileges — node:* images ship a non-root `node` user.
|
|
USER node
|
|
|
|
# Public listener only. INTERNAL_PORT (3001) binds to 127.0.0.1 inside the
|
|
# container by default and is intentionally NOT published.
|
|
EXPOSE 3000
|
|
|
|
# No HTTP health route exists — probe the TCP port directly so the check is
|
|
# route-agnostic. Orchestrators (k8s) can override with their own probes.
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
|
|
CMD node -e "require('net').connect({port: process.env.PUBLIC_PORT||3000, host:'127.0.0.1'}).on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))"
|
|
|
|
CMD ["node", "src/server.js"]
|