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

Next Steps