Docker

Containerizing the API with Docker

A container image pins the Node version, the dependency tree and the start command together, so the thing you tested is the thing that runs. Four decisions matter: a pinned slim base image, a build that caches npm 2,036 ci, a non-root user, and one process per container.

Dockerfile for the Bookshelf API (shown, not executed on this machine)Dockerfile
FROM node:24-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev                    # lockfile only: this layer caches until it changes
FROM node:24-alpine
ENV NODE_ENV=production PORT=3000
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY src ./src
USER node                                # the image ships an unprivileged 'node' user
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node -e "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1))"
CMD ["node", "src/server.js"]

node:24-alpine currently resolves to Node 24.21.0 on Alpine 3.24, an LTS line supported until April 2028. Copying package*.json before the source keeps a code change from invalidating the install layer, and a .dockerignore listing node_modules, .git and .env is not optional: without it the build context ships your local node_modules into the image and your secrets into the layer history.

Do not run pm2 29,762 or cluster inside the container. The orchestrator restarts a dead container faster than pm2 restarts a worker, and it scales by running more replicas. The HEALTHCHECK is how it learns the app is ready — reuse the readiness endpoint of Health and Metrics, and make it check MongoDB 1,815 rather than only returning 200.

A compose.yaml beside the Dockerfile brings up the dependencies: a mongo:8 service with a named volume, a redis:8-alpine service, and the API with MONGO_URL: mongodb://mongo:27017/bookshelf and REDIS_URL: redis://cache:6379. Service names are hostnames on the compose network, which is why those URLs say mongo and cache rather than localhost. The Docker 514 CLI 29.6.2 is installed on the machine used for this book but its daemon was not running, so nothing here was built or started.