Files
DreamChat/doc/deployment.md

17 KiB

DreamChat Deployment Guide

Overview

This document covers the deployment configuration including Docker Compose setup, DevContainer configuration, and production deployment procedures.

DevContainer Setup

.devcontainer/devcontainer.json

{
  "name": "DreamChat Development",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {
      "version": "20"
    },
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "bradlc.vscode-tailwindcss",
        "ms-vscode.vscode-typescript-next",
        "nestjs.vscode-nestjs",
        "prisma.prisma"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "typescript.preferences.importModuleSpecifier": "relative"
      }
    }
  },
  "forwardPorts": [3000, 5173, 5432, 8080],
  "portsAttributes": {
    "3000": {
      "label": "Backend API",
      "onAutoForward": "notify"
    },
    "5173": {
      "label": "Frontend Dev Server",
      "onAutoForward": "notify"
    },
    "5432": {
      "label": "PostgreSQL",
      "onAutoForward": "silent"
    },
    "8080": {
      "label": "Keycloak",
      "onAutoForward": "notify"
    }
  },
  "postCreateCommand": "bash .devcontainer/post-create.sh",
  "remoteUser": "node",
  "mounts": [
    "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume",
    "source=${localWorkspaceFolderBasename}-pnpm-store,target=/home/node/.local/share/pnpm/store,type=volume"
  ]
}

.devcontainer/docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile
    volumes:
      - ..:/workspace:cached
      - /var/run/docker.sock:/var/run/docker.sock
    command: sleep infinity
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/dreamchat
      - REDIS_URL=redis://redis:6379
      - KEYCLOAK_URL=http://keycloak:8080
    depends_on:
      - db
      - redis
      - keycloak
    networks:
      - dreamchat-network

  db:
    image: ankane/pgvector:latest
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: dreamchat
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - dreamchat-network

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    ports:
      - "6379:6379"
    networks:
      - dreamchat-network

  keycloak:
    image: quay.io/keycloak/keycloak:23.0
    restart: unless-stopped
    command: start-dev
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://db:5432/keycloak
      KC_DB_USERNAME: postgres
      KC_DB_PASSWORD: postgres
    ports:
      - "8080:8080"
    depends_on:
      - db
    networks:
      - dreamchat-network

volumes:
  postgres-data:

networks:
  dreamchat-network:
    driver: bridge

.devcontainer/Dockerfile

FROM mcr.microsoft.com/devcontainers/typescript-node:20

# Install additional tools
RUN apt-get update && apt-get install -y \
    postgresql-client \
    redis-tools \
    && rm -rf /var/lib/apt/lists/*

# Install pnpm
RUN npm install -g pnpm@8

# Set working directory
WORKDIR /workspace

# Install global packages
RUN npm install -g @nestjs/cli@latest

# Create non-root user
USER node

.devcontainer/post-create.sh

#!/bin/bash
set -e

echo "🚀 Setting up DreamChat monorepo development environment..."

# Install pnpm globally
npm install -g pnpm@8

# Install all dependencies (uses pnpm workspaces)
echo "📦 Installing dependencies..."
cd /workspace
pnpm install

# Build shared packages first
echo "📦 Building shared packages..."
pnpm --filter @dreamchat/shared build

# Generate Prisma client
pnpm db:generate

# Copy environment files if they don't exist
if [ ! -f /workspace/apps/backend/.env ]; then
    echo "⚙️  Creating backend .env file..."
    cat > /workspace/apps/backend/.env << EOF
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://postgres:postgres@db:5432/dreamchat
JWT_SECRET=dev-jwt-secret-change-in-production
JWT_EXPIRES_IN=1h
JWT_REFRESH_EXPIRES_IN=7d
LLM_PROVIDER=openrouter
LLM_API_KEY=your-openrouter-api-key
LLM_MODEL=openai/gpt-4o
KEYCLOAK_URL=http://localhost:8080
KEYCLOAK_REALM=dreamchat
KEYCLOAK_CLIENT_ID=dreamchat-backend
EOF
fi

if [ ! -f /workspace/apps/frontend/.env ]; then
    echo "⚙️  Creating frontend .env file..."
    cat > /workspace/apps/frontend/.env << EOF
VITE_API_URL=http://localhost:3000/api
VITE_WS_URL=ws://localhost:3000
VITE_KEYCLOAK_URL=http://localhost:8080
VITE_KEYCLOAK_REALM=dreamchat
VITE_KEYCLOAK_CLIENT_ID=dreamchat-frontend
EOF
fi

echo "✅ Development environment setup complete!"
echo ""
echo "Next steps:"
echo "1. Start all apps: pnpm dev"
echo "2. Or start individually:"
echo "   - Backend: pnpm --filter @dreamchat/backend dev"
echo "   - Frontend: pnpm --filter @dreamchat/frontend dev"
echo "3. Access Keycloak admin at http://localhost:8080 (admin/admin)"

Docker Compose Production

docker-compose.yml (Root)

version: '3.8'

services:
  # Backend API
  backend:
    build:
      context: .
      dockerfile: apps/backend/Dockerfile
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/dreamchat
      - JWT_SECRET=${JWT_SECRET}
      - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-1h}
      - JWT_REFRESH_EXPIRES_IN=${JWT_REFRESH_EXPIRES_IN:-7d}
      - LLM_PROVIDER=${LLM_PROVIDER}
      - LLM_API_KEY=${LLM_API_KEY}
      - LLM_MODEL=${LLM_MODEL}
      - EMBEDDING_PROVIDER=${EMBEDDING_PROVIDER:-local}
      - EMBEDDING_MODEL=${EMBEDDING_MODEL:-Xenova/all-MiniLM-L6-v2}
      - EMBEDDING_DIMENSION=${EMBEDDING_DIMENSION:-384}
      - EMBEDDING_DEVICE=${EMBEDDING_DEVICE:-cpu}
      - HUGGINGFACE_API_KEY=${HUGGINGFACE_API_KEY}
      - KEYCLOAK_URL=${KEYCLOAK_URL}
      - KEYCLOAK_REALM=${KEYCLOAK_REALM}
      - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID}
      - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET}
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - backend-logs:/app/logs
      - model-cache:/app/models
    networks:
      - dreamchat-network
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Frontend
  frontend:
    build:
      context: .
      dockerfile: apps/frontend/Dockerfile
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      - VITE_API_URL=/api
      - VITE_WS_URL=/ws
    depends_on:
      - backend
    networks:
      - dreamchat-network

  # Database
  db:
    image: ankane/pgvector:latest
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: dreamchat
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d
    networks:
      - dreamchat-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis (optional, for session storage and caching)
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redis-data:/data
    networks:
      - dreamchat-network

  # Nginx Reverse Proxy (optional)
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - model-cache:/model-cache:ro
    depends_on:
      - backend
      - frontend
    networks:
      - dreamchat-network

volumes:
  postgres-data:
  redis-data:
  backend-logs:
  model-cache:  # Persist downloaded embedding models

networks:
  dreamchat-network:
    driver: bridge

Embedding Model Cache (Optional)

To avoid re-downloading models on every restart, models are cached in a Docker volume:

# Add to docker-compose volumes
volumes:
  model-cache:

Models are downloaded on first use and cached at /app/models in the backend container.

Common models and their sizes:

  • Xenova/all-MiniLM-L6-v2: ~80MB (384 dimensions)
  • Xenova/all-mpnet-base-v2: ~420MB (768 dimensions)
  • BAAI/bge-small-en: ~130MB (384 dimensions)

.env.example

# Database
POSTGRES_PASSWORD=your_secure_password_here

# JWT
JWT_SECRET=your_jwt_secret_key_min_32_chars
JWT_EXPIRES_IN=1h
JWT_REFRESH_EXPIRES_IN=7d

# LLM Configuration
LLM_PROVIDER=openrouter
LLM_API_KEY=sk-or-v1-...
LLM_MODEL=openai/gpt-4o

# Embedding Configuration (Local HuggingFace by default)
EMBEDDING_PROVIDER=local
EMBEDDING_MODEL=Xenova/all-MiniLM-L6-v2
EMBEDDING_DIMENSION=384
EMBEDDING_DEVICE=cpu

# HuggingFace API (optional - if not using local embeddings)
# HUGGINGFACE_API_KEY=hf_...

# Keycloak (optional - for external auth)
KEYCLOAK_URL=http://keycloak:8080
KEYCLOAK_REALM=dreamchat
KEYCLOAK_CLIENT_ID=dreamchat-backend
KEYCLOAK_CLIENT_SECRET=your_keycloak_secret

Backend Dockerfile

# apps/backend/Dockerfile
FROM node:20-alpine AS base
RUN npm install -g pnpm@8

FROM base AS dependencies
WORKDIR /app

# Copy workspace configuration
COPY pnpm-workspace.yaml package.json ./
COPY apps/backend/package.json ./apps/backend/
COPY packages/shared/package.json ./packages/shared/

# Install dependencies
RUN pnpm install --frozen-lockfile

FROM base AS build
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=dependencies /app/apps/backend/node_modules ./apps/backend/node_modules
COPY --from=dependencies /app/packages/shared/node_modules ./packages/shared/node_modules

# Copy source code
COPY packages/shared ./packages/shared
COPY apps/backend ./apps/backend

# Build shared packages first
RUN pnpm --filter @dreamchat/shared build

# Build backend
RUN pnpm --filter @dreamchat/backend build

FROM base AS production
WORKDIR /app

# Copy only production dependencies
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=dependencies /app/apps/backend/node_modules ./apps/backend/node_modules
COPY --from=build /app/apps/backend/dist ./dist
COPY --from=build /app/packages/shared/dist ./node_modules/@dreamchat/shared/dist

# Create logs directory
RUN mkdir -p /app/logs

# Non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs

EXPOSE 3000

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

Frontend Dockerfile

# apps/frontend/Dockerfile
FROM node:20-alpine AS base
RUN npm install -g pnpm@8

FROM base AS dependencies
WORKDIR /app

# Copy workspace configuration
COPY pnpm-workspace.yaml package.json ./
COPY apps/frontend/package.json ./apps/frontend/
COPY packages/shared/package.json ./packages/shared/

# Install dependencies
RUN pnpm install --frozen-lockfile

FROM base AS build
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=dependencies /app/apps/frontend/node_modules ./apps/frontend/node_modules
COPY --from=dependencies /app/packages/shared/node_modules ./packages/shared/node_modules

# Copy source code
COPY packages/shared ./packages/shared
COPY apps/frontend ./apps/frontend

# Build shared packages first
RUN pnpm --filter @dreamchat/shared build

# Build frontend
RUN pnpm --filter @dreamchat/frontend build

# Production with Nginx
FROM nginx:alpine

# Copy built assets
COPY --from=build /app/apps/frontend/dist /usr/share/nginx/html

# Copy nginx config
COPY apps/frontend/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

frontend/nginx.conf

server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;

    # API proxy
    location /api {
        proxy_pass http://backend:3000/api;
        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 proxy
    location /ws {
        proxy_pass http://backend:3000;
        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;
    }

    # Static files
    location / {
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # Health check
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

Keycloak Configuration

Initial Setup

  1. Access Keycloak admin console: http://localhost:8080/admin

  2. Login with admin credentials

  3. Create new realm: dreamchat

  4. Create client: dreamchat-backend

    • Client authentication: ON
    • Authorization: ON
    • Valid redirect URIs: http://localhost:3000/*
    • Web origins: http://localhost:3000
  5. Create client: dreamchat-frontend

    • Client authentication: OFF
    • Valid redirect URIs: http://localhost:5173/*
    • Web origins: http://localhost:5173

realm-export.json (Optional)

{
  "realm": "dreamchat",
  "enabled": true,
  "clients": [
    {
      "clientId": "dreamchat-backend",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": ["http://localhost:3000/*"],
      "webOrigins": ["http://localhost:3000"],
      "protocol": "openid-connect"
    },
    {
      "clientId": "dreamchat-frontend",
      "enabled": true,
      "publicClient": true,
      "redirectUris": ["http://localhost:5173/*"],
      "webOrigins": ["http://localhost:5173"],
      "protocol": "openid-connect"
    }
  ]
}

Deployment Procedures

Local Development

# Using DevContainer
1. Open project in VS Code
2. Click "Reopen in Container"
3. Wait for setup to complete
4. Start services:
   - Terminal 1: pnpm --filter @dreamchat/backend dev
   - Terminal 2: pnpm --filter @dreamchat/frontend dev
   - Or run all: pnpm dev

Production Deployment

# 1. Clone repository
git clone https://github.com/yourusername/dreamchat.git
cd dreamchat

# 2. Create environment file
cp .env.example .env
# Edit .env with production values

# 3. Build and start
docker-compose up -d --build

# 4. Run database migrations
docker-compose exec backend pnpm db:migrate

# 5. Check health
curl http://localhost:3000/health

# 6. View logs
docker-compose logs -f backend

Backup and Restore

# Backup database
docker-compose exec db pg_dump -U postgres -Fc dreamchat > backup_$(date +%Y%m%d).dump

# Restore database
docker-compose exec -T db pg_restore -U postgres -d dreamchat < backup_20240223.dump

# Backup volumes
docker run --rm -v dreamchat_postgres-data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz -C /data .

Updates

# Pull latest changes
git pull origin main

# Rebuild and restart
docker-compose down
docker-compose up -d --build

# Run migrations
docker-compose exec backend npx prisma migrate deploy

Monitoring and Logging

Health Checks

# Backend health
curl http://localhost:3000/health

# Database health
docker-compose exec db pg_isready -U postgres

# Full stack
docker-compose ps

Log Management

# View all logs
docker-compose logs

# View specific service
docker-compose logs -f backend

# View last 100 lines
docker-compose logs --tail=100 backend

Security Considerations

  1. Change default passwords in production
  2. Use HTTPS with valid SSL certificates
  3. Enable firewall rules for required ports only
  4. Regular updates of base images and dependencies
  5. Secrets management - use Docker secrets or external vault
  6. Network isolation - separate networks for different services

Troubleshooting

Common Issues

  1. Database connection failed

    • Check DATABASE_URL environment variable
    • Ensure db service is healthy: docker-compose ps
  2. WebSocket not connecting

    • Verify VITE_WS_URL matches your domain
    • Check Nginx proxy configuration
  3. Keycloak authentication issues

    • Verify client configuration in Keycloak admin
    • Check redirect URIs match exactly
  4. File upload fails

    • Check file size limits in Nginx config
    • Verify disk space in backend container