Skip to content

Instantly share code, notes, and snippets.

@barthez-kenwou
Created June 19, 2026 14:31
Show Gist options
  • Select an option

  • Save barthez-kenwou/59ac0e1534db8f951ab39595b3c8b642 to your computer and use it in GitHub Desktop.

Select an option

Save barthez-kenwou/59ac0e1534db8f951ab39595b3c8b642 to your computer and use it in GitHub Desktop.
Secure Production-Ready Dockerfile for Node.js

Secure Production-Ready Dockerfile for Node.js

Overview

This Dockerfile follows modern DevSecOps and container security best practices for deploying a Node.js application in production.

Security Features

  • Multi-stage build
  • Minimal attack surface
  • Non-root user execution
  • Dependency isolation
  • Reproducible builds
  • Health checks
  • Production-only dependencies
  • Lightweight Alpine image
  • Reduced image size
  • Better container startup performance

Dockerfile

########################################
# Stage 1: Build Stage
########################################

# Use a lightweight official Node.js image
FROM node:22-alpine AS builder

# Set working directory
WORKDIR /app

# Copy dependency files first for better layer caching
COPY package.json package-lock.json ./

# Install all dependencies (including devDependencies)
RUN npm ci

# Copy application source code
COPY . .

# Build the application
RUN npm run build

########################################
# Stage 2: Production Stage
########################################

# Use a fresh minimal image
FROM node:22-alpine AS production

# Create application group
RUN addgroup -S nodegroup

# Create non-root application user
RUN adduser -S nodeuser -G nodegroup

# Set working directory
WORKDIR /app

# Copy package files
COPY package.json package-lock.json ./

# Install production dependencies only
RUN npm ci --omit=dev \
    && npm cache clean --force

# Copy compiled application from builder stage
COPY --from=builder /app/dist ./dist

# Change ownership of application files
RUN chown -R nodeuser:nodegroup /app

# Switch to non-root user
USER nodeuser

# Expose application port
EXPOSE 3000

# Healthcheck configuration
HEALTHCHECK --interval=30s \
            --timeout=10s \
            --start-period=20s \
            --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# Environment configuration
ENV NODE_ENV=production

# Start application
CMD ["node", "dist/index.js"]

Why This Dockerfile Is Secure

1. Multi-Stage Build

Instead of shipping build tools and development dependencies to production, the application is built in a separate stage.

Benefits:

  • Smaller image size
  • Reduced attack surface
  • Faster deployments
  • Less vulnerability exposure
FROM node:22-alpine AS builder

2. Non-Root User

Running containers as root is one of the most common security mistakes.

Bad:

USER root

Good:

RUN adduser -S nodeuser -G nodegroup

USER nodeuser

Benefits:

  • Limits privilege escalation
  • Prevents accidental host modifications
  • Reduces impact of container compromise

3. Production Dependencies Only

Development dependencies should never be installed in production.

npm ci --omit=dev

Benefits:

  • Smaller image
  • Fewer vulnerabilities
  • Faster startup

4. Deterministic Dependency Installation

Using:

npm ci

instead of:

npm install

ensures:

  • Reproducible builds
  • Consistent dependency versions
  • Faster installation

5. Health Checks

Containers should expose their health status.

HEALTHCHECK ...

Example endpoint:

app.get('/health', (_, res) => {
  res.status(200).json({
    status: 'UP'
  });
});

Benefits:

  • Automatic restart detection
  • Better orchestration
  • Improved observability

6. Minimal Base Image

Using Alpine Linux:

FROM node:22-alpine

Benefits:

  • Smaller image size
  • Fewer packages installed
  • Lower vulnerability count

7. Proper File Ownership

RUN chown -R nodeuser:nodegroup /app

Benefits:

  • Prevents unauthorized writes
  • Improves filesystem security

Recommended .dockerignore

Always create a .dockerignore file.

node_modules
.git
.github
.env
.env.*
coverage
dist
Dockerfile
docker-compose.yml
npm-debug.log
*.md
.vscode
.idea

Benefits:

  • Faster builds
  • Smaller context
  • Prevents secret leakage

Additional Security Recommendations

Use Read-Only Filesystem

Docker Compose:

services:
  app:
    read_only: true

Drop Linux Capabilities

services:
  app:
    cap_drop:
      - ALL

Prevent Privilege Escalation

services:
  app:
    security_opt:
      - no-new-privileges:true

Limit Resources

services:
  app:
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 512M

Scan Images for Vulnerabilities

Example with Trivy:

trivy image my-node-app:latest

Integrate into CI/CD pipelines before deployment.


Security Checklist

  • Multi-stage build
  • Non-root user
  • Production dependencies only
  • Health check
  • Docker ignore file
  • Small base image
  • Reproducible builds
  • Proper file ownership
  • Resource limits
  • Vulnerability scanning
  • No privilege escalation
  • Reduced attack surface

Final Verdict

This Dockerfile provides a strong baseline for secure Node.js container deployments in:

  • Kubernetes
  • Docker Swarm
  • ECS
  • EKS
  • AKS
  • GKE
  • On-Premise Docker Hosts

It follows modern DevSecOps principles and significantly improves the security posture of a production Node.js application compared to traditional single-stage Dockerfiles running as root.

########################################
# Stage 1: Build Stage
########################################
# Use a lightweight official Node.js image
FROM node:22-alpine AS builder
# Set working directory
WORKDIR /app
# Copy dependency files first for better layer caching
COPY package.json package-lock.json ./
# Install all dependencies (including devDependencies)
RUN npm ci
# Copy application source code
COPY . .
# Build the application
RUN npm run build
########################################
# Stage 2: Production Stage
########################################
# Use a fresh minimal image
FROM node:22-alpine AS production
# Create application group
RUN addgroup -S nodegroup
# Create non-root application user
RUN adduser -S nodeuser -G nodegroup
# Set working directory
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install production dependencies only
RUN npm ci --omit=dev \
&& npm cache clean --force
# Copy compiled application from builder stage
COPY --from=builder /app/dist ./dist
# Change ownership of application files
RUN chown -R nodeuser:nodegroup /app
# Switch to non-root user
USER nodeuser
# Expose application port
EXPOSE 3000
# Healthcheck configuration
HEALTHCHECK --interval=30s \
--timeout=10s \
--start-period=20s \
--retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Environment configuration
ENV NODE_ENV=production
# Start application
CMD ["node", "dist/index.js"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment