Dockerfile


https://hub.docker.com/_/node
https://docs.docker.com/build/building/best-practices/
https://docs.docker.com/build/building/secrets/
https://docs.npmjs.com/cli/v11/commands/npm-ci
https://nodejs.org/api/cli.html

1. Important Points#

Node.js container rule:
    use npm ci
    do not copy .env into image
    run as non-root user
    write logs to stdout/stderr
    pass runtime config by env / secret / mounted file

common runtime env:
    NODE_ENV=production
    PORT=3000
    LOG_LEVEL=info
    NODE_OPTIONS=--enable-source-maps --max-old-space-size=512

2. Multi-stage Dockerfile#

# syntax=docker/dockerfile:1

FROM node:22-bookworm-slim AS deps
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-bookworm-slim AS runtime
WORKDIR /app

ENV NODE_ENV=production \
    PORT=3000 \
    LOG_LEVEL=info \
    NODE_OPTIONS="--enable-source-maps --max-old-space-size=512"

COPY --from=deps /app/node_modules ./node_modules
COPY package.json package-lock.json ./
COPY src ./src

RUN npm prune --omit=dev && npm cache clean --force

USER node

EXPOSE 3000

CMD ["node", "src/main.js"]
why:
    deps stage uses npm ci
    runtime stage keeps only production dependencies
    image runs as node user

3. Runtime-only Dockerfile#

# syntax=docker/dockerfile:1

FROM node:22-bookworm-slim
WORKDIR /app

ENV NODE_ENV=production \
    PORT=3000 \
    LOG_LEVEL=info \
    NODE_OPTIONS="--enable-source-maps --max-old-space-size=512"

COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force

COPY src ./src

USER node

EXPOSE 3000

CMD ["node", "src/main.js"]
use this when:
    project is plain JavaScript
    no compile step is required
    CI already ran lint/test

4. Runtime Environment Variables#

docker run --rm \
  -p 3000:3000 \
  -e NODE_ENV=production \
  -e PORT=3000 \
  -e LOG_LEVEL=info \
  -e NODE_OPTIONS="--enable-source-maps --max-old-space-size=768" \
  order-api:runtime

common Node.js runtime env#

Env Example What It Does
NODE_ENV production application/runtime mode
PORT 3000 app listen port
LOG_LEVEL info application log level
NODE_OPTIONS --max-old-space-size=768 pass Node.js CLI flags
NODE_EXTRA_CA_CERTS /etc/ssl/private/company-ca.pem add extra trusted CA
NODE_TLS_REJECT_UNAUTHORIZED 1 TLS certificate verification
HTTP_PROXY http://proxy:8080 outbound HTTP proxy
HTTPS_PROXY http://proxy:8080 outbound HTTPS proxy
NO_PROXY localhost,127.0.0.1,.svc bypass proxy
TZ UTC timezone

common NODE_OPTIONS#

memory:
    --max-old-space-size=768
        set V8 heap max size in MB

diagnostics:
    --enable-source-maps
        better stack traces with source maps

    --trace-warnings
        print stack traces for process warnings

    --heapsnapshot-near-heap-limit=3
        write heap snapshots near heap limit

    --report-uncaught-exception
        generate diagnostic report on uncaught exception

    --report-on-fatalerror
        generate diagnostic report on fatal error

security / crypto:
    --tls-min-v1.2
        reject TLS versions below 1.2

    --tls-cipher-list=<cipher-list>
        customize TLS cipher list

    --use-openssl-ca
        use OpenSSL CA store

    --use-bundled-ca
        use Node.js bundled CA store

5. .dockerignore#

node_modules
coverage
.git
.env
.env.*
npm-debug.log
Dockerfile
docker-compose.yml

6. Build And Run#

docker build -t order-api:local .
docker run --rm \
  -p 3000:3000 \
  -e NODE_ENV=production \
  -e PORT=3000 \
  order-api:local

7. Docker Compose#

services:
  order-api:
    build:
      context: .
    ports:
      - "3000:3000"
    env_file:
      - .env.local
    environment:
      NODE_ENV: local
      PORT: "3000"
      LOG_LEVEL: debug
      NODE_OPTIONS: "--enable-source-maps --max-old-space-size=512"

8. Build Secrets#

docker build \
  --secret id=npmrc,src="$HOME/.npmrc" \
  -t order-api:local .
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

9. Production Checklist#

image:
    npm ci
    production dependencies only
    no .env copied
    non-root user
    pinned Node major version

runtime:
    config injected by platform
    NODE_OPTIONS reviewed
    health check configured
    logs to stdout/stderr

CI:
    npm ci
    npm run lint
    npm test
    docker build
    image scan