Docker and TypeScript: Containerizing Applications from Scratch

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 Containerize TypeScript Applications?



Modern software development is unthinkable without containerization. Docker has become the de facto standard for packaging, delivering, and running applications in isolated environments. Combined with TypeScript — a strictly typed superset of JavaScript — Docker ensures predictability, portability, and simplifies CI/CD processes.



In this article, we will break down how to properly containerize a TypeScript application: from a basic Dockerfile to a production-ready build using docker-compose and multi-stage builds. You will learn how to avoid common mistakes, reduce image size, and speed up deployment.



Basics: TypeScript and Node.js in Docker



TypeScript is a language that compiles to JavaScript. This means that to run a TypeScript application in a container, we first need to compile the source code, and then execute the resulting JavaScript using Node.js. Docker is ideal for automating this process.



Creating a Simple TypeScript Application



To start, let's create a minimal TypeScript application. Project structure:



my-ts-app/├── src/│   └── index.ts├── package.json├── tsconfig.json└── Dockerfile


src/index.ts:



import express, { Request, Response } from 'express';

const app = express();const PORT = process.env.PORT || 3000;

app.get('/', (req: Request, res: Response) => { res.json({ message: 'Hello from TypeScript + Docker!' });});

app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`);});


package.json:



{  "name": "my-ts-app",  "version": "1.0.0",  "scripts": {    "build": "tsc",    "start": "node dist/index.js",    "dev": "ts-node src/index.ts"  },  "dependencies": {    "express": "^4.18.2"  },  "devDependencies": {    "@types/express": "^4.17.21",    "@types/node": "^20.11.0",    "typescript": "^5.3.3",    "ts-node": "^10.9.2"  }}


tsconfig.json:



{  "compilerOptions": {    "target": "ES2020",    "module": "commonjs",    "outDir": "./dist",    "rootDir": "./src",    "strict": true,    "esModuleInterop": true,    "skipLibCheck": true  },  "include": ["src/**/*"]}


Basic Dockerfile



The first, simplest Dockerfile option:



FROM node:20-alpine

WORKDIR /app

COPY package*.json ./RUN npm install

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]


This Dockerfile copies the entire project, installs dependencies, compiles TypeScript, and runs the application. However, this approach has drawbacks: the image size is large, and layer caching is inefficient.



Optimization: Multi-Stage Builds and Size Reduction



For a production environment, it is recommended to use multi-stage builds. This allows you to separate the build and execution stages, leaving only the necessary files in the final image.



Production Dockerfile with Multi-Stage Build



# Stage 1: BuildFROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./RUN npm ci --only=production

COPY . .RUN npm run build

# Stage 2: ProductionFROM node:20-alpine

WORKDIR /app

COPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/package.json ./

EXPOSE 3000

USER node

CMD ["node", "dist/index.js"]


Advantages of this approach:

  • Smaller image size — the final layer contains only the compiled code and production dependencies.
  • Security — the TypeScript source code and dev

Blogs

Book Recommendations