Ця сторінка ще не перекладена українською. Ви переглядаєте англійську версію. Щоб додати переклад, перегляньте Посібник зі внеску.

Deployment Guide

Production deployment strategies and best practices.

Note: This is an internal backend-to-backend (B2B) microservice. It should be deployed within your private network (VPC, Kubernetes cluster) and NOT exposed to the public internet. Other backend services call this API to send email notifications.

Deployment Options

1. Docker Compose (Simple)

Best for small deployments, single-server setups.

Production docker-compose.yml:

version: '3.8'

services:
  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: notifications
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./migrations:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  rabbitmq:
    image: rabbitmq:3-management-alpine
    restart: unless-stopped
    environment:
      RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  notification-service:
    image: ghcr.io/khodakivskyi/notification-service:latest
    restart: unless-stopped
    ports:
      - "3001:3001"
    environment:
      NODE_ENV: production
      PORT: 3001
      LOG_LEVEL: warn
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/notifications
      RABBITMQ_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
      SMTP_HOST: ${SMTP_HOST}
      SMTP_PORT: ${SMTP_PORT}
      SMTP_USER: ${SMTP_USER}
      SMTP_PASS: ${SMTP_PASS}
      API_KEY: ${API_KEY}
    depends_on:
      db:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  workers:
    image: ghcr.io/khodakivskyi/notification-service:latest
    restart: unless-stopped
    command: ["node", "dist/workers.js"]
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/notifications
      RABBITMQ_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
      SMTP_HOST: ${SMTP_HOST}
      SMTP_PORT: ${SMTP_PORT}
      SMTP_USER: ${SMTP_USER}
      SMTP_PASS: ${SMTP_PASS}
    depends_on:
      db:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
    deploy:
      replicas: 3

volumes:
  postgres_data:
  rabbitmq_data:

Production .env:

# Database
DB_USER=notif_user
DB_PASSWORD=STRONG_PASSWORD_HERE

# RabbitMQ
RABBITMQ_USER=notif_user
RABBITMQ_PASSWORD=STRONG_PASSWORD_HERE

# SMTP
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=SG.xxxxxxxxxxxxxx

# API Security
API_KEY=your-32-character-minimum-api-key-here

Deploy:

# Pull latest image
docker-compose pull

# Start services
docker-compose up -d

# Check status
docker-compose ps

# View logs
docker-compose logs -f notification-service

2. Kubernetes (Scalable)

Best for large deployments, high availability.

namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: notification-service

configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: notification-config
  namespace: notification-service
data:
  NODE_ENV: production
  PORT: "3001"
  LOG_LEVEL: warn
  EMAIL_QUEUE_NAME: email-queue
  EMAIL_RETRY_QUEUE_NAME: email-retry-queue
  EMAIL_DLQ_NAME: email-dlq

secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: notification-secrets
  namespace: notification-service
type: Opaque
stringData:
  DATABASE_URL: postgresql://user:password@postgres:5432/notifications
  RABBITMQ_URL: amqp://user:password@rabbitmq:5672
  SMTP_HOST: smtp.sendgrid.net
  SMTP_PORT: "587"
  SMTP_USER: apikey
  SMTP_PASS: SG.xxxxxxxxxxxxxx
  API_KEY: your-api-key-here

deployment-api.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: notification-api
  namespace: notification-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: notification-api
  template:
    metadata:
      labels:
        app: notification-api
    spec:
      containers:
      - name: api
        image: ghcr.io/khodakivskyi/notification-service:latest
        ports:
        - containerPort: 3001
        envFrom:
        - configMapRef:
            name: notification-config
        - secretRef:
            name: notification-secrets
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /api/health
            port: 3001
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/ready
            port: 3001
          initialDelaySeconds: 10
          periodSeconds: 5

deployment-workers.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: notification-workers
  namespace: notification-service
spec:
  replicas: 5
  selector:
    matchLabels:
      app: notification-workers
  template:
    metadata:
      labels:
        app: notification-workers
    spec:
      containers:
      - name: worker
        image: ghcr.io/khodakivskyi/notification-service:latest
        command: ["node", "dist/workers.js"]
        envFrom:
        - configMapRef:
            name: notification-config
        - secretRef:
            name: notification-secrets
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: notification-api
  namespace: notification-service
spec:
  selector:
    app: notification-api
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3001
  type: ClusterIP

Network Policy (Restrict Access):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: notification-api-policy
  namespace: notification-service
spec:
  podSelector:
    matchLabels:
      app: notification-api
  policyTypes:
  - Ingress
  ingress:
  - from:
    # Only allow traffic from specific namespaces (your other backend services)
    - namespaceSelector:
        matchLabels:
          allowed-to-notification-service: "true"
    ports:
    - protocol: TCP
      port: 3001

Note: No Ingress resource is needed since this is an internal service. Other services in the cluster access it via the ClusterIP Service.

Deploy to Kubernetes:

# Create namespace
kubectl apply -f namespace.yaml

# Create secrets (edit first!)
kubectl apply -f secret.yaml

# Create config
kubectl apply -f configmap.yaml

# Deploy API
kubectl apply -f deployment-api.yaml

# Deploy workers
kubectl apply -f deployment-workers.yaml

# Create service
kubectl apply -f service.yaml

# Apply network policy (optional, for restricting access)
kubectl apply -f network-policy.yaml

# Check status
kubectl get all -n notification-service

# View logs
kubectl logs -n notification-service -l app=notification-api -f

3. Cloud Platforms

AWS ECS

{
  "family": "notification-service",
  "taskRoleArn": "arn:aws:iam::ACCOUNT:role/notification-task-role",
  "executionRoleArn": "arn:aws:iam::ACCOUNT:role/notification-execution-role",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "notification-api",
      "image": "ghcr.io/khodakivskyi/notification-service:latest",
      "portMappings": [
        {
          "containerPort": 3001,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "NODE_ENV",
          "value": "production"
        }
      ],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:database-url"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/notification-service",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "api"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3001/api/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3
      }
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024"
}

Google Cloud Run

# Deploy API
gcloud run deploy notification-api \
  --image ghcr.io/khodakivskyi/notification-service:latest \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars NODE_ENV=production,PORT=3001 \
  --set-secrets DATABASE_URL=database-url:latest,SMTP_PASS=smtp-pass:latest \
  --min-instances 1 \
  --max-instances 10 \
  --memory 512Mi \
  --cpu 1

# Deploy workers
gcloud run deploy notification-workers \
  --image ghcr.io/khodakivskyi/notification-service:latest \
  --platform managed \
  --region us-central1 \
  --no-allow-unauthenticated \
  --set-env-vars NODE_ENV=production \
  --set-secrets DATABASE_URL=database-url:latest,SMTP_PASS=smtp-pass:latest \
  --command node \
  --args dist/workers.js \
  --min-instances 2 \
  --max-instances 20 \
  --memory 512Mi \
  --cpu 1

Internal Load Balancing

Since this is a B2B microservice, it should only be accessible within your internal network.

Kubernetes Service (Recommended)

The service is already exposed via Kubernetes Service (ClusterIP) which provides internal load balancing:

apiVersion: v1
kind: Service
metadata:
  name: notification-api
  namespace: notification-service
spec:
  selector:
    app: notification-api
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3001
  type: ClusterIP  # Internal only, not exposed to internet

Other services in the cluster can reach it at: http://notification-api.notification-service.svc.cluster.local

Docker Compose (Internal Network)

For Docker Compose deployments, services communicate over the internal Docker network:

networks:
  internal:
    driver: bridge

services:
  notification-service:
    image: ghcr.io/khodakivskyi/notification-service:latest
    networks:
      - internal
    # No ports exposed to host - only internal network access

Other services on the same network can reach it at: http://notification-service:3001


Database Setup

PostgreSQL Production

# Create database
createdb -h db.yourdomain.com -U postgres notifications

# Create user
psql -h db.yourdomain.com -U postgres << EOF
CREATE USER notif_user WITH PASSWORD 'STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON DATABASE notifications TO notif_user;
EOF

# Run migrations
psql postgresql://notif_user:PASSWORD@db.yourdomain.com:5432/notifications \
  -f migrations/01_create_notifications_table.sql

psql postgresql://notif_user:PASSWORD@db.yourdomain.com:5432/notifications \
  -f migrations/02_create_notification_statuses_table.sql

Managed Database Services

AWS RDS:

aws rds create-db-instance \
  --db-instance-identifier notification-db \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --engine-version 15.4 \
  --master-username notif_admin \
  --master-user-password STRONG_PASSWORD \
  --allocated-storage 20 \
  --backup-retention-period 7 \
  --multi-az

Google Cloud SQL:

gcloud sql instances create notification-db \
  --database-version=POSTGRES_15 \
  --tier=db-f1-micro \
  --region=us-central1 \
  --backup-start-time=03:00 \
  --enable-bin-log

Scaling Strategies

Horizontal Scaling

Scale API:

# Docker Compose
docker-compose up -d --scale notification-service=5

# Kubernetes
kubectl scale deployment notification-api --replicas=10 -n notification-service

Scale Workers:

# Docker Compose
docker-compose up -d --scale workers=10

# Kubernetes
kubectl scale deployment notification-workers --replicas=20 -n notification-service

Auto-scaling

Kubernetes HPA:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: notification-api-hpa
  namespace: notification-service
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: notification-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Health Checks

Application Health

# Liveness (is app running?)
curl http://localhost:3001/api/health

# Readiness (is app ready to serve traffic?)
curl http://localhost:3001/api/ready

External Monitoring

Uptime monitoring:

  • Use UptimeRobot, Pingdom, or similar
  • Monitor /api/health endpoint
  • Alert on failures

Example with curl:

#!/bin/bash
while true; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://notifications.yourdomain.com/api/health)
  if [ "$STATUS" != "200" ]; then
    echo "ALERT: Service unhealthy (HTTP $STATUS)"
    # Send notification
  fi
  sleep 60
done

Backup Strategy

Database Backups

Automated daily backup:

#!/bin/bash
BACKUP_DIR="/backups/postgres"
DATE=$(date +%Y-%m-%d-%H%M)
DATABASE_URL="postgresql://user:pass@db:5432/notifications"

# Create backup
pg_dump $DATABASE_URL | gzip > "$BACKUP_DIR/backup-$DATE.sql.gz"

# Retain last 30 days
find $BACKUP_DIR -name "backup-*.sql.gz" -mtime +30 -delete

# Upload to S3 (optional)
aws s3 cp "$BACKUP_DIR/backup-$DATE.sql.gz" s3://backups/notification-service/

Cron job:

0 3 * * * /opt/scripts/backup-database.sh

Application Data

# Backup RabbitMQ definitions
curl -u admin:password http://rabbitmq:15672/api/definitions > rabbitmq-definitions.json

# Backup configuration
tar -czf config-backup.tar.gz .env nginx.conf docker-compose.yml

Rollback Procedure

Docker Compose

# Rollback to previous version
docker-compose pull
docker-compose down
docker-compose up -d --force-recreate

# Or specify version
docker-compose down
docker tag ghcr.io/khodakivskyi/notification-service:1.0.0 notification-service:latest
docker-compose up -d

Kubernetes

# Rollback deployment
kubectl rollout undo deployment/notification-api -n notification-service

# Rollback to specific revision
kubectl rollout undo deployment/notification-api --to-revision=2 -n notification-service

# Check rollout history
kubectl rollout history deployment/notification-api -n notification-service

Security Checklist

  • Deploy within private network (VPC/Kubernetes cluster) - NO public internet exposure
  • Use mTLS or TLS for service-to-service communication
  • Set strong passwords for database and RabbitMQ
  • Enable API key authentication (recommended for production)
  • Generate unique API keys for each calling service (if auth enabled)
  • Enable rate limiting per API key
  • Restrict database access to internal network only
  • Keep Docker images updated
  • Use secrets management (Kubernetes Secrets, AWS Secrets Manager, Vault)
  • Enable network policies to restrict ingress/egress
  • Regular security updates
  • Monitor for vulnerabilities
  • Audit API key usage and rotate keys periodically

Next Steps