September 17, 2026 路 #1
The image that ships as root
This image builds and the server starts. What is the most important problem?
FROM node:22-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]Next puzzle in UTC
One try. The first answer sticks.
How to fix it
There is no USER instruction, so PID 1 is root. Create an unprivileged user and switch to it before CMD.
The Dockerfile never sets a user. Official Node images default to root, and CMD ["node", "server.js"] therefore runs as root inside the container. EXPOSE only documents a port. It does not publish anything. The exec form of CMD is the form you want, because it does not wrap Node in a shell.
A root process turns a remote code execution bug into a much larger incident: the attacker can write anywhere the container can write, read secrets mounted into the pod, and break out more easily if a kernel bug shows up.
Fix
Create a user after the files are in place, and hand ownership of the app directory to that user.
RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin app \
&& chown -R app:app /app
USER app
CMD ["node", "server.js"]
Pair this with a Kubernetes securityContext of runAsNonRoot: true and allowPrivilegeEscalation: false. If the image still starts as root, the pod is rejected instead of silently shipping the bug.