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…