Files
DreamChat/doc/deployment.md

771 lines
18 KiB
Markdown

# 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
```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
```yaml
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
volumes:
postgres-data:
networks:
dreamchat-network:
driver: bridge
```
**Note:** Keycloak is configured as an external service. Set `KEYCLOAK_URL` in your environment to point to your external Keycloak instance.
### .devcontainer/Dockerfile
```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
```bash
#!/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)
```yaml
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 (static file server)
# Note: External reverse proxy expected for SSL and routing
frontend:
build:
context: .
dockerfile: apps/frontend/Dockerfile
restart: unless-stopped
ports:
- "3001:3000"
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
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:
```yaml
# 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
```bash
# 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
```dockerfile
# apps/backend/Dockerfile
FROM node:24-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
```dockerfile
# apps/frontend/Dockerfile
FROM node:24-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 stage - using serve for static files
# External reverse proxy (nginx/traefik/etc.) expected
FROM node:24-alpine AS production
WORKDIR /app
# Install serve
RUN npm install -g serve
# Copy built assets
COPY --from=build /app/apps/frontend/dist ./dist
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
# Serve static files
# Note: External reverse proxy should handle:
# - SSL/TLS termination
# - Path routing (/api -> backend, / -> frontend)
# - WebSocket proxying
CMD ["serve", "-s", "dist", "-l", "3000"]
```
### External Reverse Proxy Configuration
The frontend container serves static files on port 3000. An external reverse proxy is expected to handle:
- **SSL/TLS termination**
- **Path routing**:
- `/api/*` → Backend (port 3000)
- `/ws` → Backend WebSocket (port 3000)
- `/*` → Frontend (port 3001)
- **Static file caching**
Example nginx configuration:
```nginx
server {
listen 443 ssl;
server_name dreamchat.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# API proxy
location /api {
proxy_pass http://backend:3000/api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 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";
}
# Frontend static files
location / {
proxy_pass http://frontend:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
```
## Keycloak Configuration (External)
Keycloak is configured as an external service with support for group/role/attribute-based authorization and auto-user creation.
### Prerequisites
1. Have a running Keycloak instance (self-hosted or managed)
2. Configure the following environment variables in `.env`:
```bash
# Basic Keycloak settings
KEYCLOAK_ENABLED=true
KEYCLOAK_URL=http://your-keycloak-server:8080
KEYCLOAK_REALM=dreamchat
KEYCLOAK_CLIENT_ID=dreamchat-backend
KEYCLOAK_CLIENT_SECRET=your_keycloak_secret
# Authorization settings (optional but recommended)
KEYCLOAK_REQUIRED_GROUP=dreamchat-users
KEYCLOAK_REQUIRED_ROLE=dreamchat-access
KEYCLOAK_REQUIRED_CLIENT_ROLE=user
KEYCLOAK_REQUIRED_ATTRIBUTE=approved:true
# Auto-create users
KEYCLOAK_AUTO_CREATE_USER=true
KEYCLOAK_DEFAULT_USER_ROLE=USER
```
### Keycloak Realm Setup
1. Access your Keycloak admin console
2. Create new realm: `dreamchat`
3. Create client: `dreamchat-backend`
- Client authentication: ON
- Authorization: ON
- Valid redirect URIs: `http://localhost:3000/*`
- Web origins: `http://localhost:3000`
4. Create client: `dreamchat-frontend`
- Client authentication: OFF
- Valid redirect URIs: `http://localhost:5173/*`
- Web origins: `http://localhost:5173`
### Authorization Configuration
You can restrict access based on:
**1. Group Membership**
```bash
KEYCLOAK_REQUIRED_GROUP=dreamchat-users
```
Users must be members of this Keycloak group to access the application.
**2. Realm Role**
```bash
KEYCLOAK_REQUIRED_ROLE=dreamchat-access
```
Users must have this realm-level role.
**3. Client Role**
```bash
KEYCLOAK_REQUIRED_CLIENT_ROLE=user
```
Users must have this role for the `dreamchat-backend` client.
**4. User Attribute**
```bash
KEYCLOAK_REQUIRED_ATTRIBUTE=department:engineering
# or
KEYCLOAK_REQUIRED_ATTRIBUTE=approved:true
```
Users must have this attribute with the specified value.
### User Auto-Creation
When `KEYCLOAK_AUTO_CREATE_USER=true`:
- Users are automatically created in the database on first Keycloak login
- Username is derived from Keycloak preferred_username
- Email is taken from Keycloak email claim
- Role is set to `KEYCLOAK_DEFAULT_USER_ROLE` (default: USER)
- The `keycloakSub` field links the local user to Keycloak
When `KEYCLOAK_AUTO_CREATE_USER=false`:
- Only existing local users can log in via Keycloak
- The `keycloakSub` must match between Keycloak and local user
### Example Keycloak Group/Role Setup
1. Create a group: `dreamchat-users`
2. Create a realm role: `dreamchat-access`
3. Assign the group and/or role to users who should have access
4. Configure `KEYCLOAK_REQUIRED_GROUP` and/or `KEYCLOAK_REQUIRED_ROLE`
### realm-export.json (Optional)
```json
{
"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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# Backend health
curl http://localhost:3000/health
# Database health
docker-compose exec db pg_isready -U postgres
# Full stack
docker-compose ps
```
### Log Management
```bash
# 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