Docker and TypeScript: Advanced Application Containerization

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: Why Advanced TypeScript Containerization is a Necessity

Containerizing TypeScript applications with Docker has long ceased to be just a trend — it is a standard of modern development. However, basic Dockerfiles that simply copy the entire project and run npm start are far from ideal. At an advanced level, we face challenges such as optimizing image size, ensuring security, organizing a microservice architecture, and configuring automatic recovery.

In this article, we will explore techniques that will turn your Docker pipeline for TypeScript into an industry standard: multi-stage builds, layer minimization, using .dockerignore, Docker Compose for microservices, healthchecks, and working with environment variables. All examples are written in TypeScript and ready for integration into real projects.



1. Multi-Stage Build: From Development to Production

The main problem with basic Dockerfiles is that they include everything in the final image: source code, development node_modules, build tools. This is critical for TypeScript, as tsc and devDependencies are not needed in production.

Multi-stage build solves this problem: the first stage builds and compiles TypeScript, the second creates a minimal image with only the necessary files.



Example Dockerfile with Two Stages

# Stage 1: Build TypeScriptFROM node:20-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY tsconfig.json ./COPY src/ ./src/RUN npm run build

# Stage 2: Production ImageFROM node:20-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY package.json ./EXPOSE 3000CMD ["node", "dist/index.js"]

Key points:

  • Using npm ci — exact reproduction of dependencies from the lock file.
  • Copy only dist/ and node_modules — TypeScript source code does not end up in production.
  • Alpine image — minimal base (about 5 MB).

Such an image weighs 3-5 times less than a naive build. For a large project with dozens of dependencies, the difference can reach hundreds of megabytes.



Three-Stage Build with Dev Dependencies

Sometimes you need to separate dev and prod dependencies. Add a third stage for installing only production dependencies:

# Stage 1: Install all dependenciesFROM node:20-alpine AS depsWORKDIR /appCOPY package*.json ./RUN npm ci

# Stage 2: BuildFROM node:20-alpine AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .RUN npm run build

# Stage 3: ProductionFROM node:20-alpine AS runnerWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=deps /app/node_modules ./node_modulesCOPY package.json ./EXPOSE 3000CMD ["node", "dist/index.js"]


2. Image Optimization: .dockerignore, Caching, and Layers

Each COPY, RUN, and ADD instruction creates a new layer in Docker. The fewer layers and the more efficiently they are cached, the faster the build process.



.dockerignore — Your First Helper

Create a .dockerignore file in the project root:

node_modulesdist.git.gitignore*.md.env.env.*Dockerfile.dockerignorecoverage.vscode.idea__tests__

This prevents copying junk into the build context and speeds up file transfer to the Docker daemon.



Correct Order of Instructions

Docker caches layers. If you first copy package.json and then run npm ci, this layer will be reused as long as package.json does not change. Then copy the source

Blogs

Book Recommendations