Docker Deployment
Containerize and deploy your MoroJS applications with Docker. Production-ready configurations for Kubernetes, Docker Compose, and cloud platforms.
Basic Docker Setup
Dockerfile
typescript
1# Multi-stage build for optimal image size
2FROM node:18-alpine AS builder
3
4WORKDIR /app
5
6# Copy package files
7COPY package*.json ./
8COPY tsconfig.json ./
9
10# Install dependencies
11RUN npm ci --only=production
12
13# Copy source code
14COPY src/ ./src/
15
16# Build application
17RUN npm run build
18
19# Production image
20FROM node:18-alpine AS runtime
21
22WORKDIR /app
23
24# Install dumb-init for proper signal handling
25RUN apk add --no-cache dumb-init
26
27# Create non-root user
28RUN addgroup -g 1001 -S nodejs
29RUN adduser -S moro -u 1001
30
31# Copy built application and dependencies
32COPY --from=builder --chown=moro:nodejs /app/dist ./dist
33COPY --from=builder --chown=moro:nodejs /app/node_modules ./node_modules
34COPY --from=builder --chown=moro:nodejs /app/package*.json ./
35
36# Set user
37USER moro
38
39# Expose port
40EXPOSE 3000
41
42# Health check
43HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
44 CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
45
46# Start application with dumb-init
47ENTRYPOINT ["dumb-init", "--"]
48CMD ["node", "dist/index.js"]
Docker Compose
typescript
1# docker-compose.yml
2version: '3.8'
3
4services:
5 app:
6 build:
7 context: .
8 dockerfile: Dockerfile
9 target: runtime
10 ports:
11 - "3000:3000"
12 environment:
13 - NODE_ENV=production
14 - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
15 - REDIS_URL=redis://redis:6379
16 - JWT_SECRET=your-jwt-secret-here
17 depends_on:
18 db:
19 condition: service_healthy
20 redis:
21 condition: service_healthy
22 restart: unless-stopped
23 healthcheck:
24 test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"]
25 interval: 30s
26 timeout: 10s
27 retries: 3
28 start_period: 40s
29
30 db:
31 image: postgres:15-alpine
32 environment:
33 - POSTGRES_DB=myapp
34 - POSTGRES_USER=postgres
35 - POSTGRES_PASSWORD=password
36 volumes:
37 - postgres_data:/var/lib/postgresql/data
38 - ./migrations:/docker-entrypoint-initdb.d
39 ports:
40 - "5432:5432"
41 restart: unless-stopped
42 healthcheck:
43 test: ["CMD-SHELL", "pg_isready -U postgres"]
44 interval: 10s
45 timeout: 5s
46 retries: 5
47
48 redis:
49 image: redis:7-alpine
50 ports:
51 - "6379:6379"
52 volumes:
53 - redis_data:/data
54 restart: unless-stopped
55 healthcheck:
56 test: ["CMD", "redis-cli", "ping"]
57 interval: 10s
58 timeout: 3s
59 retries: 3
60
61 nginx:
62 image: nginx:alpine
63 ports:
64 - "80:80"
65 - "443:443"
66 volumes:
67 - ./nginx.conf:/etc/nginx/nginx.conf
68 - ./ssl:/etc/nginx/ssl
69 depends_on:
70 - app
71 restart: unless-stopped
72
73volumes:
74 postgres_data:
75 redis_data:
Production Optimizations
Optimized Production Dockerfile
typescript
1# Production-optimized Dockerfile
2FROM node:18-alpine AS deps
3WORKDIR /app
4COPY package*.json ./
5RUN npm ci --only=production && npm cache clean --force
6
7FROM node:18-alpine AS builder
8WORKDIR /app
9COPY package*.json ./
10COPY tsconfig.json ./
11RUN npm ci
12COPY src/ ./src/
13RUN npm run build && npm prune --production
14
15FROM node:18-alpine AS runtime
16
17# Security updates and utilities
18RUN apk update && apk upgrade && apk add --no-cache dumb-init curl
19
20# Create app directory
21WORKDIR /app
22
23# Create non-root user
24RUN addgroup -g 1001 -S nodejs && adduser -S moro -u 1001
25
26# Copy application
27COPY --from=builder --chown=moro:nodejs /app/dist ./dist
28COPY --from=deps --chown=moro:nodejs /app/node_modules ./node_modules
29COPY --from=builder --chown=moro:nodejs /app/package*.json ./
30
31# Security: Remove package manager
32RUN rm -rf /usr/local/lib/node_modules/npm
33
34# Switch to non-root user
35USER moro
36
37# Expose port
38EXPOSE 3000
39
40# Health check
41HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
42 CMD curl -f http://localhost:3000/health || exit 1
43
44# Use dumb-init to handle signals properly
45ENTRYPOINT ["dumb-init", "--"]
46CMD ["node", "dist/index.js"]
Kubernetes Deployment
typescript
1# k8s/deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: moro-api
6 labels:
7 app: moro-api
8spec:
9 replicas: 3
10 selector:
11 matchLabels:
12 app: moro-api
13 template:
14 metadata:
15 labels:
16 app: moro-api
17 spec:
18 containers:
19 - name: moro-api
20 image: your-registry/moro-api:latest
21 ports:
22 - containerPort: 3000
23 env:
24 - name: NODE_ENV
25 value: "production"
26 - name: DATABASE_URL
27 valueFrom:
28 secretKeyRef:
29 name: app-secrets
30 key: database-url
31 - name: JWT_SECRET
32 valueFrom:
33 secretKeyRef:
34 name: app-secrets
35 key: jwt-secret
36 resources:
37 requests:
38 memory: "256Mi"
39 cpu: "200m"
40 limits:
41 memory: "512Mi"
42 cpu: "500m"
43 livenessProbe:
44 httpGet:
45 path: /health
46 port: 3000
47 initialDelaySeconds: 30
48 periodSeconds: 10
49 readinessProbe:
50 httpGet:
51 path: /health
52 port: 3000
53 initialDelaySeconds: 5
54 periodSeconds: 5
55
56---
57apiVersion: v1
58kind: Service
59metadata:
60 name: moro-api-service
61spec:
62 selector:
63 app: moro-api
64 ports:
65 - protocol: TCP
66 port: 80
67 targetPort: 3000
68 type: ClusterIP
69
70---
71apiVersion: networking.k8s.io/v1
72kind: Ingress
73metadata:
74 name: moro-api-ingress
75 annotations:
76 kubernetes.io/ingress.class: "nginx"
77 cert-manager.io/cluster-issuer: "letsencrypt-prod"
78spec:
79 tls:
80 - hosts:
81 - api.example.com
82 secretName: api-tls
83 rules:
84 - host: api.example.com
85 http:
86 paths:
87 - path: /
88 pathType: Prefix
89 backend:
90 service:
91 name: moro-api-service
92 port:
93 number: 80