Production Deployment - anubissbe/ProjectHub-Mcp GitHub Wiki

Production Deployment

This comprehensive guide covers deploying ProjectHub-MCP v4.5.1 to production environments, including cloud platforms, on-premises servers, and containerized deployments for this enterprise-grade project management system.

🌍 Deployment Overview

Deployment Options

Cloud Platforms:

  • AWS: EC2, ECS, Lambda, RDS
  • Google Cloud: Compute Engine, Cloud Run, Cloud SQL
  • Microsoft Azure: App Service, Container Instances, SQL Database
  • DigitalOcean: Droplets, App Platform, Managed Databases
  • Vercel: Frontend deployment with serverless functions
  • Netlify: Static site deployment with edge functions

Containerized Deployment:

  • Docker Compose: Single-server deployment
  • Kubernetes: Multi-server orchestration
  • Docker Swarm: Simple container orchestration

Traditional Hosting:

  • VPS: Virtual private servers
  • Dedicated Servers: Bare metal hosting
  • Shared Hosting: Budget-friendly option (limited)

📍 Pre-Deployment Checklist

Security Preparation

Environment Variables:

# Production environment variables
NODE_ENV=production
DATABASE_URL=postgresql://user:password@host:5432/database
JWT_SECRET=your-super-secure-jwt-secret-key
CORS_ORIGIN=https://yourdomain.com
SSL_CERT_PATH=/path/to/ssl/cert.pem
SSL_KEY_PATH=/path/to/ssl/private.key

Security Hardening:

  • Strong database passwords
  • JWT secret keys (256-bit minimum)
  • SSL/TLS certificates configured
  • CORS origins restricted to production domains
  • Rate limiting configured
  • Input validation enabled
  • SQL injection protection active

Performance Optimization

Frontend Optimization:

# Build optimized frontend
cd frontend
npm run build

# Verify build size
ls -la dist/

# Test production build locally
npm run preview

Backend Optimization:

# Build backend
cd backend
npm run build

# Remove development dependencies
npm ci --production

Database Optimization:

  • Indexes created for frequently queried columns
  • Connection pooling configured
  • Database backup strategy implemented
  • Monitoring and alerting set up

🐳 Docker Production Deployment

Production Docker Configuration

docker-compose.prod.yml:

version: '3.8'

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.prod
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./ssl:/etc/nginx/ssl:ro
    environment:
      - VITE_API_URL=https://yourdomain.com/api
      - VITE_WS_URL=wss://yourdomain.com
    depends_on:
      - backend
    restart: unless-stopped

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - JWT_SECRET=${JWT_SECRET}
      - CORS_ORIGIN=https://yourdomain.com
    depends_on:
      - postgres
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: ankane/pgvector:latest
    environment:
      - POSTGRES_DB=mcp_learning
      - POSTGRES_USER=mcp_user
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./infrastructure/postgres/init-postgres:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mcp_user -d mcp_learning"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
      - frontend_dist:/usr/share/nginx/html:ro
    depends_on:
      - frontend
      - backend
    restart: unless-stopped

volumes:
  postgres_data:
  frontend_dist:

networks:
  default:
    driver: bridge

Production Dockerfiles

Frontend Dockerfile.prod:

# Build stage
FROM node:18-alpine AS builder

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

COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf

# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html

# Add health check
RUN apk add --no-cache curl
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:80/health || exit 1

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

Backend Dockerfile.prod:

FROM node:18-alpine AS builder

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

COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./
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
USER nodejs

EXPOSE 3001

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node dist/healthcheck.js || exit 1

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

SSL/TLS Configuration

Nginx SSL Configuration:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/private.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Frontend
    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;
        
        # Security headers
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    }

    # Backend API
    location /api {
        proxy_pass http://backend:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    # WebSocket
    location /socket.io {
        proxy_pass http://backend:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

☁️ Cloud Platform Deployments

AWS Deployment

EC2 + RDS Setup:

# Launch EC2 instance (Ubuntu 22.04 LTS)
# Instance type: t3.medium or larger
# Security groups: HTTP (80), HTTPS (443), SSH (22)

# Connect to instance
ssh -i your-key.pem ubuntu@your-ec2-instance

# Install Docker
sudo apt update
sudo apt install docker.io docker-compose-plugin
sudo usermod -aG docker ubuntu

# Clone repository
git clone https://github.com/anubissbe/ProjectHub-Mcp.git
cd ProjectHub-Mcp

# Configure environment
cp .env.example .env
nano .env  # Edit with production values

# Deploy
docker compose -f docker-compose.prod.yml up -d

AWS RDS PostgreSQL:

# Create RDS instance
# Engine: PostgreSQL 15
# Instance class: db.t3.micro (minimum)
# Storage: 20GB SSD
# Security group: Allow PostgreSQL (5432) from application servers

# Update DATABASE_URL in .env
DATABASE_URL=postgresql://username:password@rds-endpoint:5432/database

DigitalOcean App Platform

app.yaml:

name: projecthub-mcp
services:
- name: frontend
  source_dir: frontend
  github:
    repo: anubissbe/ProjectHub-Mcp
    branch: main
  build_command: npm run build
  output_dir: dist
  routes:
  - path: /
  environment_slug: node-js
  instance_count: 1
  instance_size_slug: basic-xxs

- name: backend
  source_dir: backend
  github:
    repo: anubissbe/ProjectHub-Mcp
    branch: main
  build_command: npm run build
  run_command: npm start
  environment_slug: node-js
  instance_count: 1
  instance_size_slug: basic-xxs
  envs:
  - key: NODE_ENV
    value: production
  - key: DATABASE_URL
    value: ${db.DATABASE_URL}
  routes:
  - path: /api

databases:
- name: db
  engine: PG
  version: "15"
  size: db-s-dev-database

Vercel Deployment (Frontend Only)

vercel.json:

{
  "version": 2,
  "builds": [
    {
      "src": "package.json",
      "use": "@vercel/static-build",
      "config": {
        "distDir": "dist"
      }
    }
  ],
  "routes": [
    {
      "handle": "filesystem"
    },
    {
      "src": "/(.*)",
      "dest": "/index.html"
    }
  ],
  "env": {
    "VITE_API_URL": "https://your-backend-api.com/api",
    "VITE_WS_URL": "wss://your-backend-api.com"
  }
}

⚙️ Kubernetes Deployment

Kubernetes Configuration

namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: task-management

configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: task-management
data:
  NODE_ENV: "production"
  CORS_ORIGIN: "https://yourdomain.com"
  VITE_API_URL: "https://yourdomain.com/api"
  VITE_WS_URL: "wss://yourdomain.com"

secrets.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: task-management
type: Opaque
data:
  DATABASE_URL: <base64-encoded-database-url>
  JWT_SECRET: <base64-encoded-jwt-secret>
  POSTGRES_PASSWORD: <base64-encoded-password>

backend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: task-management
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: ghcr.io/anubissbe/projecthub-mcp-backend:v4.5.1
        ports:
        - containerPort: 3001
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets
        livenessProbe:
          httpGet:
            path: /api/health
            port: 3001
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/health
            port: 3001
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 250m
            memory: 256Mi

frontend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: task-management
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: ghcr.io/anubissbe/projecthub-mcp-frontend:v4.5.1
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          limits:
            cpu: 250m
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 128Mi

ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: task-management
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - yourdomain.com
    secretName: app-tls
  rules:
  - host: yourdomain.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 3001
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

📊 Monitoring and Logging

Application Monitoring

Health Check Endpoints:

// backend/src/routes/health.ts
app.get('/api/health', (req, res) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    version: process.env.APP_VERSION || '1.0.0'
  });
});

app.get('/api/health/detailed', async (req, res) => {
  try {
    // Check database connection
    await pool.query('SELECT 1');
    
    res.json({
      status: 'healthy',
      checks: {
        database: 'connected',
        memory: process.memoryUsage(),
        uptime: process.uptime()
      }
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message
    });
  }
});

Prometheus Metrics:

import prometheus from 'prom-client';

// Create metrics
const httpRequestDuration = new prometheus.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status']
});

const httpRequestTotal = new prometheus.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status']
});

// Middleware to collect metrics
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    const route = req.route?.path || req.path;
    
    httpRequestDuration
      .labels(req.method, route, res.statusCode.toString())
      .observe(duration);
      
    httpRequestTotal
      .labels(req.method, route, res.statusCode.toString())
      .inc();
  });
  
  next();
});

// Metrics endpoint
app.get('/metrics', (req, res) => {
  res.set('Content-Type', prometheus.register.contentType);
  res.end(prometheus.register.metrics());
});

Logging Configuration

Winston Logger Setup:

import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'task-management-api' },
  transports: [
    new winston.transports.File({ 
      filename: 'logs/error.log', 
      level: 'error' 
    }),
    new winston.transports.File({ 
      filename: 'logs/combined.log' 
    })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

🛡️ Security Hardening

Application Security

Security Headers:

import helmet from 'helmet';
import rateLimit from 'express-rate-limit';

// Security middleware
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "wss:"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
});

app.use('/api', limiter);

Database Security

Connection Security:

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === 'production' ? {
    rejectUnauthorized: false
  } : false,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

🗺 Deployment Pipeline

CI/CD with GitHub Actions

.github/workflows/deploy.yml:

name: Deploy to Production

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'
        cache-dependency-path: '**/package-lock.json'
    
    - name: Install and test frontend
      run: |
        cd frontend
        npm ci
        npm run lint
        npm run type-check
        npm test
        npm run build
    
    - name: Install and test backend
      run: |
        cd backend
        npm ci
        npm run lint
        npm run test
        npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    
    - name: Build and push images
      run: |
        docker buildx build --platform linux/amd64,linux/arm64 \
          -t ghcr.io/anubissbe/projecthub-mcp-frontend:v4.5.1 \
          -t ghcr.io/anubissbe/projecthub-mcp-frontend:latest \
          --push ./frontend
        
        docker buildx build --platform linux/amd64,linux/arm64 \
          -t ghcr.io/anubissbe/projecthub-mcp-backend:v4.5.1 \
          -t ghcr.io/anubissbe/projecthub-mcp-backend:latest \
          --push ./backend
    
    - name: Deploy to server
      uses: appleboy/[email protected]
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          cd /opt/ProjectHub-Mcp
          git pull origin main
          docker compose -f docker-compose.prod.yml pull
          docker compose -f docker-compose.prod.yml up -d
          docker system prune -f

Blue-Green Deployment

Blue-Green Script:

#!/bin/bash
# blue-green-deploy.sh

set -e

CURRENT_ENV=$(docker compose -f docker-compose.prod.yml ps --services --filter "status=running" | head -1 | grep -o 'blue\|green' || echo 'blue')
NEW_ENV=$([ "$CURRENT_ENV" = "blue" ] && echo "green" || echo "blue")

echo "Current environment: $CURRENT_ENV"
echo "Deploying to: $NEW_ENV"

# Deploy to new environment
docker compose -f docker-compose.$NEW_ENV.yml pull
docker compose -f docker-compose.$NEW_ENV.yml up -d

# Health check
echo "Waiting for health check..."
sleep 30

if curl -f http://localhost:8080/api/health; then
    echo "Health check passed. Switching traffic..."
    
    # Update load balancer to point to new environment
    # This depends on your load balancer setup
    
    # Stop old environment
    docker compose -f docker-compose.$CURRENT_ENV.yml down
    
    echo "Deployment successful!"
else
    echo "Health check failed. Rolling back..."
    docker compose -f docker-compose.$NEW_ENV.yml down
    exit 1
fi

Next: Learn about Docker Configuration for advanced container setup.