Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on Docker with TypeScript! ๐ In this guide, weโll explore how to containerize your TypeScript applications with Docker.
Youโll discover how Docker can transform your TypeScript development experience. Whether youโre building web applications ๐, server-side code ๐ฅ๏ธ, or libraries ๐, understanding containerization is essential for modern deployment and development workflows.
By the end of this tutorial, youโll feel confident containerizing TypeScript applications in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding Docker Containerization
๐ค What is Docker Containerization?
Docker containerization is like shipping containers ๐ฆ for your applications! Think of it as a standardized box that packages your TypeScript app with everything it needs to run - runtime, dependencies, and configuration files.
In TypeScript terms, Docker creates isolated environments where your code runs consistently across different machines ๐ฅ๏ธ. This means you can:
- โจ Deploy anywhere with confidence
- ๐ Scale applications easily
- ๐ก๏ธ Isolate dependencies and avoid conflicts
๐ก Why Use Docker with TypeScript?
Hereโs why developers love Docker for TypeScript apps:
- Consistency ๐: Same environment everywhere
- Isolation ๐ : No dependency conflicts
- Scalability ๐: Easy horizontal scaling
- Deployment ๐: Simple production deployments
Real-world example: Imagine deploying a TypeScript API ๐. With Docker, you package everything once and it runs identically on development, staging, and production!
๐ง Basic Syntax and Usage
๐ Simple Dockerfile Example
Letโs start with a friendly Dockerfile for TypeScript:
# ๐ Hello, Docker!
FROM node:18-alpine
# ๐จ Set working directory
WORKDIR /app
# ๐ฆ Copy package files first (for better caching)
COPY package*.json ./
# โก Install dependencies
RUN npm ci --only=production
# ๐ Copy source code
COPY . .
# ๐๏ธ Build TypeScript
RUN npm run build
# ๐ Expose port
EXPOSE 3000
# ๐ฏ Start the application
CMD ["npm", "start"]
๐ก Explanation: This Dockerfile creates a lightweight Alpine Linux container with Node.js, installs dependencies, builds your TypeScript code, and starts the app!
๐ฏ TypeScript Project Structure
Hereโs how to organize your TypeScript project for Docker:
// ๐ Project structure
my-typescript-app/
โโโ src/
โ โโโ index.ts // ๐ฏ Entry point
โ โโโ routes/ // ๐ฃ๏ธ API routes
โ โโโ services/ // ๐ง Business logic
โโโ dist/ // ๐๏ธ Compiled JavaScript
โโโ Dockerfile // ๐ณ Docker configuration
โโโ .dockerignore // ๐ซ Ignore unnecessary files
โโโ package.json // ๐ฆ Dependencies
โโโ tsconfig.json // โ๏ธ TypeScript config
๐ก Practical Examples
๐ Example 1: Express.js API Container
Letโs containerize a TypeScript Express API:
// ๐ฏ src/index.ts - Simple Express API
import express from 'express';
interface Product {
id: string;
name: string;
price: number;
emoji: string;
}
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
// ๐๏ธ Sample products
const products: Product[] = [
{ id: '1', name: 'TypeScript Book', price: 29.99, emoji: '๐' },
{ id: '2', name: 'Docker Guide', price: 24.99, emoji: '๐ณ' },
{ id: '3', name: 'Coffee', price: 4.99, emoji: 'โ' }
];
// ๐ฏ Get all products
app.get('/api/products', (req, res) => {
res.json({ products, total: products.length });
});
// ๐ Get product by ID
app.get('/api/products/:id', (req, res) => {
const product = products.find(p => p.id === req.params.id);
if (!product) {
return res.status(404).json({ error: 'Product not found ๐' });
}
res.json(product);
});
// ๐ฅ Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.listen(PORT, () => {
console.log(`๐ Server running on port ${PORT}`);
console.log(`๐ณ Containerized TypeScript API is ready!`);
});
# ๐ณ Optimized Dockerfile for TypeScript API
FROM node:18-alpine AS builder
# ๐ฏ Set working directory
WORKDIR /app
# ๐ฆ Copy package files
COPY package*.json ./
COPY tsconfig.json ./
# โก Install all dependencies (including dev)
RUN npm ci
# ๐ Copy source code
COPY src/ ./src/
# ๐๏ธ Build TypeScript
RUN npm run build
# ๐ Production stage
FROM node:18-alpine AS production
WORKDIR /app
# ๐ฆ Copy package files
COPY package*.json ./
# โก Install only production dependencies
RUN npm ci --only=production && npm cache clean --force
# ๐ Copy built application
COPY --from=builder /app/dist ./dist
# ๐ Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# ๐ก๏ธ Change ownership
USER nodejs
# ๐ Expose port
EXPOSE 3000
# ๐ฏ Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# ๐ฎ Start the application
CMD ["node", "dist/index.js"]
๐ฏ Try it yourself: Add environment variables for database connection and API keys!
๐ฎ Example 2: Multi-Stage Build with Testing
Letโs create a robust Docker setup with testing:
// ๐งช src/services/calculator.ts
export class Calculator {
// โ Add numbers
add(a: number, b: number): number {
return a + b;
}
// โ Subtract numbers
subtract(a: number, b: number): number {
return a - b;
}
// โ๏ธ Multiply numbers
multiply(a: number, b: number): number {
return a * b;
}
// โ Divide numbers
divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero! ๐ฑ');
}
return a / b;
}
}
// ๐งช src/__tests__/calculator.test.ts
import { Calculator } from '../services/calculator';
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
calc = new Calculator();
});
test('โ should add numbers correctly', () => {
expect(calc.add(2, 3)).toBe(5);
expect(calc.add(-1, 1)).toBe(0);
});
test('โ should throw error for division by zero', () => {
expect(() => calc.divide(10, 0)).toThrow('Division by zero! ๐ฑ');
});
});
# ๐ณ Multi-stage Dockerfile with testing
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
# ๐งช Testing stage
FROM base AS testing
COPY . .
RUN npm run test
RUN npm run lint
# ๐๏ธ Build stage
FROM base AS builder
COPY . .
RUN npm run build
# ๐ Production stage
FROM node:18-alpine AS production
WORKDIR /app
# ๐ฆ Copy package files and install production deps
COPY package*.json ./
RUN npm ci --only=production
# ๐ Copy built application
COPY --from=builder /app/dist ./dist
# ๐ Security: non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Docker Compose for TypeScript Services
When youโre ready to level up, try orchestrating multiple services:
# ๐ฏ docker-compose.yml
version: '3.8'
services:
# ๐ TypeScript API service
api:
build:
context: .
dockerfile: Dockerfile
target: production
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
# ๐๏ธ PostgreSQL database
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
# ๐ Redis cache
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
ports:
- "6379:6379"
volumes:
postgres_data:
redis_data:
๐๏ธ Advanced Topic 2: Development vs Production Configurations
For the brave developers:
# ๐ฏ Dockerfile.dev - Development version
FROM node:18-alpine
WORKDIR /app
# ๐ฆ Copy package files
COPY package*.json ./
RUN npm ci
# ๐ Copy source code
COPY . .
# ๐ Install nodemon for hot reloading
RUN npm install -g nodemon ts-node
# ๐ Expose port
EXPOSE 3000
# ๐ฎ Start with hot reload
CMD ["npm", "run", "dev"]
# ๐ฏ docker-compose.dev.yml - Development compose
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DEBUG=true
command: npm run dev
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Large Image Sizes
# โ Wrong way - huge image!
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
# โ
Correct way - optimized image!
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
๐คฏ Pitfall 2: Ignoring .dockerignore
# โ Without .dockerignore - copies everything!
COPY . .
# โ
With proper .dockerignore file
# .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
coverage/
.nyc_output
๐ ๏ธ Best Practices
- ๐ฏ Use Multi-Stage Builds: Separate build and runtime environments
- ๐ Optimize Layer Caching: Copy package.json first, then source code
- ๐ก๏ธ Security First: Use non-root users and scan for vulnerabilities
- ๐จ Keep Images Small: Use Alpine Linux and remove unnecessary files
- โจ Health Checks: Always include health check endpoints
๐งช Hands-On Exercise
๐ฏ Challenge: Containerize a TypeScript Chat API
Create a containerized real-time chat application:
๐ Requirements:
- โ Express.js API with WebSocket support
- ๐ท๏ธ TypeScript with proper types
- ๐ค User authentication
- ๐ Message persistence with MongoDB
- ๐จ Docker Compose with Redis for sessions
๐ Bonus Points:
- Add nginx reverse proxy
- Implement horizontal scaling
- Create CI/CD pipeline with Docker
๐ก Solution
๐ Click to see solution
// ๐ฏ src/types/chat.ts
export interface User {
id: string;
username: string;
emoji: string;
isOnline: boolean;
}
export interface Message {
id: string;
userId: string;
username: string;
content: string;
timestamp: Date;
emoji: string;
}
export interface Room {
id: string;
name: string;
users: User[];
messages: Message[];
}
// ๐ฏ src/server.ts
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import { User, Message, Room } from './types/chat';
const app = express();
const server = createServer(app);
const io = new Server(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
const PORT = process.env.PORT || 3000;
// ๐ In-memory storage (use Redis in production)
const rooms = new Map<string, Room>();
const users = new Map<string, User>();
app.use(express.json());
// ๐ฅ Health check
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
rooms: rooms.size,
users: users.size
});
});
// ๐ WebSocket connection handling
io.on('connection', (socket) => {
console.log(`๐ User connected: ${socket.id}`);
// ๐ User joins a room
socket.on('join-room', (data: { roomId: string; user: User }) => {
const { roomId, user } = data;
// ๐ Create room if it doesn't exist
if (!rooms.has(roomId)) {
rooms.set(roomId, {
id: roomId,
name: `Room ${roomId}`,
users: [],
messages: []
});
}
const room = rooms.get(roomId)!;
// ๐ค Add user to room
users.set(socket.id, user);
room.users.push(user);
socket.join(roomId);
// ๐ข Broadcast user joined
socket.to(roomId).emit('user-joined', {
user,
message: `${user.emoji} ${user.username} joined the chat! ๐`
});
// ๐ Send room info to user
socket.emit('room-info', room);
});
// ๐ฌ Handle new messages
socket.on('send-message', (data: { roomId: string; content: string }) => {
const user = users.get(socket.id);
if (!user) return;
const message: Message = {
id: Date.now().toString(),
userId: user.id,
username: user.username,
content: data.content,
timestamp: new Date(),
emoji: user.emoji
};
const room = rooms.get(data.roomId);
if (room) {
room.messages.push(message);
// ๐ค Broadcast message to room
io.to(data.roomId).emit('new-message', message);
}
});
// ๐ Handle disconnect
socket.on('disconnect', () => {
const user = users.get(socket.id);
if (user) {
// ๐๏ธ Remove user from all rooms
rooms.forEach((room, roomId) => {
room.users = room.users.filter(u => u.id !== user.id);
socket.to(roomId).emit('user-left', {
user,
message: `${user.emoji} ${user.username} left the chat ๐`
});
});
users.delete(socket.id);
}
console.log(`๐ User disconnected: ${socket.id}`);
});
});
server.listen(PORT, () => {
console.log(`๐ Chat server running on port ${PORT}`);
console.log(`๐ณ Containerized chat API is ready! ๐ฌ`);
});
# ๐ณ Production Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# ๐ฆ Copy package files
COPY package*.json ./
COPY tsconfig.json ./
# โก Install dependencies
RUN npm ci
# ๐ Copy source code
COPY src/ ./src/
# ๐๏ธ Build TypeScript
RUN npm run build
# ๐ Production stage
FROM node:18-alpine AS production
WORKDIR /app
# ๐ฆ Install production dependencies
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# ๐ Copy built app
COPY --from=builder /app/dist ./dist
# ๐ Security: non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# ๐ก๏ธ Change ownership
RUN chown -R nodejs:nodejs /app
USER nodejs
# ๐ Expose port
EXPOSE 3000
# ๐ฅ Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# ๐ฎ Start the application
CMD ["node", "dist/server.js"]
# ๐ฏ docker-compose.yml
version: '3.8'
services:
chat-api:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
- MONGODB_URL=mongodb://mongo:27017/chatapp
depends_on:
- redis
- mongo
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
restart: unless-stopped
mongo:
image: mongo:6
environment:
MONGO_INITDB_DATABASE: chatapp
volumes:
- mongo_data:/data/db
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- chat-api
restart: unless-stopped
volumes:
redis_data:
mongo_data:
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create Docker containers for TypeScript apps with confidence ๐ช
- โ Avoid common mistakes that trip up beginners ๐ก๏ธ
- โ Apply best practices in real projects ๐ฏ
- โ Debug containerization issues like a pro ๐
- โ Build production-ready Docker setups! ๐
Remember: Docker is your deployment superpower! It ensures your TypeScript apps run consistently everywhere. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered Docker containerization with TypeScript!
Hereโs what to do next:
- ๐ป Practice with the chat API exercise above
- ๐๏ธ Build a microservices architecture with Docker Compose
- ๐ Move on to our next tutorial: Kubernetes Container Orchestration
- ๐ Share your containerized apps with the community!
Remember: Every DevOps expert was once a beginner. Keep coding, keep containerizing, and most importantly, have fun! ๐
Happy containerizing! ๐๐ณโจ