iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Dockerising

Containerising Node services - small images, fast cold-starts, signals, secrets.

Node + Docker

EXAMPLE
# Dockerfile (multi-stage, distroless)
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build

FROM gcr.io/distroless/nodejs20-debian12 AS run
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER nonroot
EXPOSE 3000
ENV NODE_OPTIONS='--enable-source-maps'
CMD ['dist/server.js']

# .dockerignore
node_modules
dist
.git
.env*
coverage
*.log

# Build + tag
# docker build -t myapp:1.2.3 .
# docker image inspect myapp:1.2.3 --format '{{.Size}}'

# Run locally
# docker run --rm -p 3000:3000 -e DATABASE_URL=postgres://... myapp:1.2.3

# Signals + graceful shutdown - Node ignores SIGTERM by default
# in non-pid-1 setups. Make sure your code handles it.

// in src/server.ts
const server = app.listen(3000);

const shutdown = async (signal: string) => {
  console.log(\`got ${signal}; shutting down\`);
  server.close(() => process.exit(0));
  await new Promise((r) => setTimeout(r, 10_000));
  console.warn('force exit');
  process.exit(1);
};

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT',  () => shutdown('SIGINT'));

# Secrets - never bake into image. Pass via:
# - docker run -e SECRET=...
# - Mounted file (e.g. /run/secrets/...) and read at boot
# - Cloud secret stores fetched by sidecar or init container

# Security checks
# docker scout cves myapp:1.2.3
# trivy image myapp:1.2.3
# Pin base image to a SHA: FROM node:20-alpine@sha256:abcd...

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget -qO- http://127.0.0.1:3000/healthz || exit 1

Why it matters

Distroless or scratch base + multi-stage + non-root user + graceful shutdown + pinned base image gets you 90 percent of the way. The other 10 percent is image scanning in CI and never baking secrets into layers.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "src/index.js"]
Try it Yourself »

Discussion

Loading…