A container image packages a Node.js service with the runtime, dependency install, and start command that Docker will run later. For an HTTP app, a successful image build is not enough; the container should start from that image and answer the same smoke-test route the service exposes.
The Dockerfile pattern assumes the project already has package.json, package-lock.json, and a script named start. It fits a service that can run from its source tree with npm start, uses the Node.js 24 LTS Debian slim image, installs production dependencies with npm ci --omit=dev, and switches to the built-in node user before starting the process.
Keep .env files, local node_modules directories, logs, and version-control data out of the build context. Port publishing stays outside the image, so the container can listen on port 3000 while Docker publishes host port 8080 for the smoke test.
Related: How to create a Dockerfile
Related: How to build a Docker image
Related: How to run a Docker container
Tool: Dockerfile Generator
Steps to dockerize a Node.js app with Docker:
- Change into the Node.js project root.
$ cd /srv/orders-api
- Check the package start script.
$ npm pkg get scripts.start "node server.js"
If no start command appears, add a real start script before building the image.
- Create the Dockerfile.
- /srv/orders-api/Dockerfile
# syntax=docker/dockerfile:1 FROM node:24-bookworm-slim WORKDIR /app ENV NODE_ENV=production COPY package*.json ./ RUN npm ci --omit=dev COPY --chown=node:node . . USER node EXPOSE 3000 CMD ["npm", "start"]
npm ci --omit=dev installs from package-lock.json and leaves development dependencies out of the runtime image.
- Exclude local-only files from the build context.
node_modules npm-debug.log .env .git Dockerfile .dockerignore coverage *.log
Do not bake secrets into the image. Supply environment-specific values at container run time or through the deployment platform.
- Build the container image.
$ docker build --tag orders-api:dockerized . [+] Building 12.9s (14/14) FINISHED ##### snipped ##### => [4/5] RUN npm ci --omit=dev => => # up to date, audited 1 package in 504ms => => # found 0 vulnerabilities ##### snipped ##### => naming to docker.io/library/orders-api:dockerized done
- List the image tag.
$ docker image ls orders-api REPOSITORY TAG SIZE orders-api dockerized 349MB
- Run the image with a host port mapped to the app port.
$ docker run --detach --name orders-api --publish 8080:3000 orders-api:dockerized 0e4fc2aeeb57
--publish 8080:3000 maps host port 8080 to port 3000 inside the container. Use a different host port when 8080 is already in use.
- Check that the container is running.
$ docker ps --filter name=orders-api NAMES IMAGE STATUS PORTS orders-api orders-api:dockerized Up 24 seconds 0.0.0.0:8080->3000/tcp, [::]:8080->3000/tcp
- Request the app health route through the published port.
$ curl -sS http://127.0.0.1:8080/health {"status":"ok","service":"orders-api"}Replace /health with the route that confirms the app is ready to receive traffic.
- Remove the test container.
$ docker rm --force orders-api orders-api
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.