Docker and JavaScript: Containerization of Node.js Applications (Advanced Level)

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: From Simple to Complex in the World of Containerization

\n\n

At the beginner level, working with Docker and JavaScript (Node.js) often comes down to creating a simple Dockerfile and running a container. However, in real production systems, this is not enough. The advanced level implies a deep understanding of build mechanisms, image size optimization, dependency management, network isolation, and security.

\n\n

In this article, we will cover the key aspects of professional containerization of Node.js applications. You will learn how to use multi-stage builds, configure Docker Compose for a microservice architecture, manage environment variables, and minimize the attack surface. The material is aimed at developers already familiar with the basics of Docker and JavaScript.

\n\n

1. Multi-stage builds for Node.js

\n\n

One of the main problems when containerizing Node.js applications is the excessive size of the final image. The standard approach with a single Dockerfile includes build tools (TypeScript, Webpack, Babel) that are not needed at runtime. Multi-stage builds allow you to split the process into stages: build and execution.

\n\n

1.1. Example Dockerfile with two stages

\n\n
# Build stage\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=production\nCOPY . .\nRUN npm run build  # Assumes the build script compiles the project\n\n# Production stage\nFROM node:20-alpine AS production\nWORKDIR /app\n# Copy only node_modules and built files\nCOPY --from=builder /app/node_modules ./node_modules\nCOPY --from=builder /app/dist ./dist\nCOPY package*.json ./\n\nEXPOSE 3000\nUSER node\nCMD ["node", "dist/index.js"]
\n\n

Key points:

\n
    \n
  • node:20-alpine — a lightweight image based on Alpine Linux (about 30 MB).
  • \n
  • npm ci — a clean installation of dependencies strictly according to package-lock.json.
  • \n
  • --only=production — excludes dev-dependencies (TypeScript, tests).
  • \n
  • USER node — avoids using the root user for security.
  • \n
\n\n

As a result, the final image contains only the files necessary for running, and the size is reduced from 300-400 MB to 80-120 MB.

\n\n

1.2. Optimizing layer caching

\n\n

Docker caches each layer (command). To speed up rebuilds, copy package*.json before copying the entire source code. If the dependencies haven't changed, the layer with npm ci will be taken from the cache.

\n\n
COPY package*.json ./\nRUN npm ci --only=production\nCOPY . .  # Only copied if the source code has changed
\n\n

2. Docker Compose for Microservice Architecture

\n\n

In production, a Node.js application rarely works in isolation. It usually requires a database (PostgreSQL, MongoDB), a cache (Redis), or a message queue (RabbitMQ). Docker Compose allows you to describe the entire infrastructure in a single YAML file.

\n\n

2.1. Example docker-compose.yml for Node.js + Redis

\n\n
version: '3.8'\nservices:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile.prod\n    ports:\n      - "3000:3000"\n    environment:\n      - NODE_ENV=production\n      - REDIS_URL=redis://redis:6379\n    depends_on:\n      - redis\n    networks:\n      - backend\n\n  redis:\n    image: redis:7-alpine\n    ports:\n      - "6379:6379"\n    volumes:\n      - redis_data:/data\n    networks:\n      - backend\n\nvolumes:\n  redis_data:\n\nnetworks:\n  backend:\n    driver: bridge
\n\n

Important aspects:

\n
    \n
  • Network isolation: services communicate through the internal backend network. Redis is not accessible from outside (unless the port is exposed).
  • \n
  • Named volumes: redis_data saves

Blogs

Book Recommendations