FROM node:22-slim
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Each line has a job: a small, pinned base image, cached dependencies, no root user, and clean shutdown.
Base image and environment:
node:22-slimpins the Node major version. It uses a small base image built on glibc (the standard C library on most Linux systems).NODE_ENV=productiontells frameworks and libraries to turn on their production behaviour.
Dependencies and caching:
- The file copies only
package*.jsonbefore it runsnpm ci. Docker caches each step as a layer. So the dependency layer stays cached until the lock file changes. npm ciinstalls exactly the versions in the lock file.--omit=devskips dev dependencies.COPY . .comes last, because the source code changes most often..dockerignorekeepsnode_modules,.gitand.envout of the image.
Security and running:
USER nodestops the app from running as root. It uses thenodeuser that the official image already has.EXPOSE 3000only documents the port. You still need-pto publish it.- The exec-form
CMD(the JSON array form) runsnodedirectly as PID 1, the first process in the container. Sonodereceives SIGTERM, the stop signal.
The app should still handle SIGTERM itself. It should also listen on 0.0.0.0, so traffic from outside the container can reach it.
- The base image is slim and pinned to a Node version.
- Dependencies are copied and installed before the source code, so Docker can cache them.
npm ci --omit=devgives the same production install every time.USER nodestops the app from running as root.- The exec-form
CMDrunsnodedirectly, so it receives signals.
Read more