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.

Steps to dockerize a Node.js app with Docker:

  1. Change into the Node.js project root.
    $ cd /srv/orders-api
  2. 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.

  3. 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.

  4. 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.

  5. 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
  6. List the image tag.
    $ docker image ls orders-api
    REPOSITORY   TAG          SIZE
    orders-api   dockerized   349MB
  7. 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.

  8. 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
  9. 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.

  10. Remove the test container.
    $ docker rm --force orders-api
    orders-api