# Deployment Guide - Memento Project ## Overview Complete deployment guide for the Memento note-taking application using Docker and Docker Compose. Covers production deployment, environment configuration, and service orchestration. **Architecture:** Multi-container deployment with keep-notes (Next.js web app) and mcp-server (Express MCP server) --- ## Prerequisites ### Required Software | Tool | Version | Purpose | |------|---------|---------| | **Docker** | 20.10+ | Container runtime | | **Docker Compose** | 2.0+ | Multi-container orchestration | | **Git** | Latest | Clone repository | ### Verify Installation ```bash docker --version docker compose version ``` --- ## Quick Start (Docker Compose) ### 1. Clone Repository ```bash git clone cd Keep ``` ### 2. Configure Environment ```bash # Copy environment template cp .env.example .env # Edit with your configuration nano .env ``` ### 3. Start Services ```bash # Build and start all services docker compose up -d # View logs docker compose logs -f # Check service status docker compose ps ``` ### 4. Access Application - **Web Application:** http://localhost:3000 - **Prisma Studio:** http://localhost:5555 (when running) ### 5. Stop Services ```bash # Stop all services docker compose down # Stop and remove volumes (⚠️ deletes data) docker compose down -v ``` --- ## Docker Configuration Files ### Root Directory Structure ``` Keep/ ├── docker-compose.yml # Main orchestration ├── .env # Environment variables ├── keep-notes/ │ ├── Dockerfile # Web app container │ ├── .dockerignore # Build exclusions │ └── next.config.ts # Next.js config └── mcp-server/ ├── Dockerfile # MCP server container └── .dockerignore # Build exclusions ``` --- ## Docker Compose Configuration ### Complete docker-compose.yml ```yaml version: '3.8' services: # ============================================ # keep-notes - Next.js Web Application # ============================================ keep-notes: build: context: ./keep-notes dockerfile: Dockerfile container_name: memento-web ports: - "3000:3000" environment: - DATABASE_URL=file:/app/prisma/dev.db - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - NODE_ENV=production # Email Configuration (SMTP) - SMTP_HOST=${SMTP_HOST} - SMTP_PORT=${SMTP_PORT:-587} - SMTP_USER=${SMTP_USER} - SMTP_PASS=${SMTP_PASS} - SMTP_FROM=${SMTP_FROM:-noreply@memento.app} # AI Providers - OPENAI_API_KEY=${OPENAI_API_KEY} - OLLAMA_API_URL=${OLLAMA_API_URL:-http://ollama:11434} volumes: - db-data:/app/prisma - uploads-data:/app/public/uploads depends_on: - ollama restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - memento-network # ============================================ # mcp-server - MCP Protocol Server # ============================================ mcp-server: build: context: ./mcp-server dockerfile: Dockerfile container_name: memento-mcp volumes: - db-data:/app/db depends_on: - keep-notes restart: unless-stopped networks: - memento-network # ============================================ # Ollama - Local LLM Provider (Optional) # ============================================ ollama: image: ollama/ollama:latest container_name: memento-ollama ports: - "11434:11434" volumes: - ollama-data:/root/.ollama restart: unless-stopped networks: - memento-network # ============================================ # Prisma Studio - Database GUI (Optional) # ============================================ prisma-studio: build: context: ./keep-notes dockerfile: Dockerfile container_name: memento-studio command: npx prisma studio ports: - "5555:5555" environment: - DATABASE_URL=file:/app/prisma/dev.db volumes: - db-data:/app/prisma depends_on: - keep-notes restart: unless-stopped networks: - memento-network profiles: - admin # Only start with: docker compose --profile admin up # ============================================ # Volumes - Data Persistence # ============================================ volumes: db-data: driver: local uploads-data: driver: local ollama-data: driver: local # ============================================ # Networks - Service Communication # ============================================ networks: memento-network: driver: bridge ``` --- ## Dockerfile: keep-notes ### Complete Dockerfile ```dockerfile # ============================================ # Multi-stage build for Next.js 16 # ============================================ # Stage 1: Dependencies FROM node:20-alpine AS deps RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on package manager COPY package*.json ./ RUN npm ci # Stage 2: Builder FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Generate Prisma client RUN npx prisma generate # Build Next.js application ENV NEXT_TELEMETRY_DISABLED=1 RUN npm run build # Stage 3: Production Runner FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # Create non-root user RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # Copy necessary files COPY --from=builder /app/public ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/prisma ./prisma COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma # Create directories with proper permissions RUN mkdir -p /app/prisma /app/public/uploads/notes RUN chown -R nextjs:nodejs /app USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:3000/api/health || exit 1 CMD ["node", "server.js"] ``` ### Update next.config.ts for Standalone Output ```typescript // keep-notes/next.config.ts import type { NextConfig } from 'next' const nextConfig: NextConfig = { // ... existing config // Enable standalone output for Docker output: 'standalone', // Optimize for production reactStrictMode: true, swcMinify: true, // Image optimization images: { unoptimized: true, // Required for standalone }, } export default nextConfig ``` ### .dockerignore for keep-notes ``` node_modules .next .git .gitignore *.md .env*.local .vscode idea .DS_Store *.log coverage test-results playwright-report ``` --- ## Dockerfile: mcp-server ### Complete Dockerfile ```dockerfile # Base image FROM node:20-alpine # Install dependencies WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Copy application code COPY . . # Copy Prisma schema and client COPY prisma ./prisma # Create non-root user RUN addgroup -g 1001 -S mcp && \ adduser -u 1001 -S mcp -G mcp # Create database directory RUN mkdir -p /app/db && \ chown -R mcp:mcp /app USER mcp WORKDIR /app # Expose (not needed for stdio, but useful for SSE variant) EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD node -e "console.log('healthy')" || exit 1 # Start MCP server CMD ["node", "index.js"] ``` ### .dockerignore for mcp-server ``` node_modules .git .gitignore *.md .env .env.* *.log .DS_Store index-sse.js # Only use main index.js README-SSE.md N8N-CONFIG.md ``` --- ## Environment Configuration ### .env.example (Root Directory) ```bash # ============================================ # Database Configuration # ============================================ DATABASE_URL="file:/app/prisma/dev.db" # ============================================ # NextAuth Configuration # ============================================ # Generate with: openssl rand -base64 32 NEXTAUTH_SECRET="your-secret-key-here-min-32-chars" NEXTAUTH_URL="http://localhost:3000" # ============================================ # Email Configuration (SMTP) # ============================================ # Required for password reset and reminders SMTP_HOST="smtp.gmail.com" SMTP_PORT="587" SMTP_USER="your-email@gmail.com" SMTP_PASS="your-app-password" SMTP_FROM="noreply@memento.app" # ============================================ # AI Provider Configuration # ============================================ # OpenAI (Optional - for GPT models) OPENAI_API_KEY="sk-..." # Ollama (Optional - for local models) OLLAMA_API_URL="http://ollama:11434" # ============================================ # Application Settings # ============================================ NODE_ENV="production" PORT="3000" # ============================================ # Docker-Specific Settings # ============================================ # These are usually set in docker-compose.yml # Keep for local development reference ``` ### Generate NEXTAUTH_SECRET ```bash # Linux/Mac openssl rand -base64 32 # Windows (PowerShell) -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_}) ``` --- ## Production Deployment ### Option 1: Docker Compose (Recommended) ```bash # 1. Clone repository git clone cd Keep # 2. Configure environment cp .env.example .env nano .env # Edit configuration # 3. Start services docker compose up -d # 4. Initialize database (first time only) docker compose exec keep-notes npx prisma migrate deploy # 5. Create admin user (optional) docker compose exec keep-notes node scripts/promote-admin.js your-email@example.com # 6. Check status docker compose ps docker compose logs -f keep-notes ``` ### Option 2: Individual Containers #### Build keep-notes Image ```bash cd keep-notes docker build -t memento-web:latest . docker run -d \ --name memento-web \ -p 3000:3000 \ -v $(pwd)/prisma:/app/prisma \ -e DATABASE_URL=file:/app/prisma/dev.db \ -e NEXTAUTH_SECRET=your-secret \ -e NEXTAUTH_URL=http://localhost:3000 \ memento-web:latest ``` #### Build mcp-server Image ```bash cd mcp-server docker build -t memento-mcp:latest . docker run -d \ --name memento-mcp \ -v /path/to/keep-notes/prisma:/app/db \ memento-mcp:latest ``` --- ## Database Management in Docker ### Run Migrations ```bash # Apply pending migrations docker compose exec keep-notes npx prisma migrate deploy # Create new migration docker compose exec keep-notes npx prisma migrate dev --name my_changes # Reset database (⚠️ deletes all data) docker compose exec keep-notes npx prisma migrate reset ``` ### Seed Database (Optional) ```bash # Create seed file # keep-notes/prisma/seed.ts # Run seed docker compose exec keep-notes npx prisma db seed ``` ### Access Database Directly ```bash # Open SQLite CLI docker compose exec keep-notes sqlite3 /app/prisma/dev.db # Query notes SELECT * FROM Note LIMIT 10; # Exit .quit ``` ### Backup Database ```bash # Backup database docker compose exec keep-notes \ sqlite3 /app/prisma/dev.db ".backup /app/prisma/backup.db" # Copy backup to host docker cp memento-web:/app/prisma/backup.db ./backup-$(date +%Y%m%d).db ``` ### Restore Database ```bash # Copy backup to container docker cp ./backup.db memento-web:/app/prisma/restore.db # Restore docker compose exec keep-notes \ sqlite3 /app/prisma/dev.db ".restore /app/prisma/restore.db" ``` --- ## Service Management ### View Logs ```bash # All services docker compose logs -f # Specific service docker compose logs -f keep-notes docker compose logs -f mcp-server # Last 100 lines docker compose logs --tail=100 keep-notes ``` ### Restart Services ```bash # Restart all docker compose restart # Restart specific service docker compose restart keep-notes # Rebuild and restart docker compose up -d --build ``` ### Update Services ```bash # Pull latest code git pull # Rebuild and restart docker compose up -d --build # Or force rebuild without cache docker compose build --no-cache docker compose up -d ``` ### Scale Services ```bash # Scale keep-notes (requires load balancer) docker compose up -d --scale keep-notes=3 ``` --- ## Reverse Proxy Configuration ### Nginx Configuration ```nginx # /etc/nginx/sites-available/memento server { listen 80; server_name your-domain.com; # Redirect to HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your-domain.com; # SSL certificates ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; # Next.js application location / { proxy_pass http://localhost: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; proxy_cache_bypass $http_upgrade; } # WebSocket support (if needed) location /ws { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` ### Traefik Configuration (Alternative) ```yaml # docker-compose.yml addition services: traefik: image: traefik:v2.10 command: - "--api.insecure=true" - "--providers.docker=true" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" ports: - "80:80" - "443:443" - "8080:8080" # Dashboard volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" keep-notes: # ... existing config labels: - "traefik.enable=true" - "traefik.http.routers.keep-notes.rule=Host(`your-domain.com`)" - "traefik.http.routers.keep-notes.entrypoints=websecure" - "traefik.http.services.keep-notes.loadbalancer.server.port=3000" ``` --- ## SSL/TLS Setup ### Let's Encrypt with Certbot ```bash # Install Certbot sudo apt install certbot python3-certbot-nginx # Get certificate sudo certbot --nginx -d your-domain.com # Auto-renewal (configured automatically) sudo certbot renew --dry-run ``` ### Self-Signed Certificate (Development) ```bash # Generate self-signed certificate openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/ssl/private/memento-selfsigned.key \ -out /etc/ssl/certs/memento-selfsigned.crt ``` --- ## Monitoring & Logging ### Container Health ```bash # Check health status docker compose ps # Inspect container docker inspect memento-web # Resource usage docker stats ``` ### Log Aggregation ```bash # View logs from all services docker compose logs > logs-$(date +%Y%m%d).txt # Rotate logs logrotate configure in /etc/logrotate.d/docker-compose ``` ### Monitoring Tools **Prometheus + Grafana** (Optional): ```yaml # docker-compose.yml addition services: prometheus: image: prom/prometheus volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090" grafana: image: grafana/grafana ports: - "3001:3000" volumes: - grafana-data:/var/lib/grafana ``` --- ## Backup & Disaster Recovery ### Automated Backup Script ```bash #!/bin/bash # backup.sh - Daily backup script BACKUP_DIR="/backups/memento" DATE=$(date +%Y%m%d_%H%M%S) # Create backup directory mkdir -p "$BACKUP_DIR/$DATE" # Backup database docker compose exec keep-notes \ sqlite3 /app/prisma/dev.db ".backup /app/prisma/backup.db" # Copy to host docker cp memento-web:/app/prisma/backup.db \ "$BACKUP_DIR/$DATE/database.db" # Backup uploads docker cp memento-web:/app/public/uploads \ "$BACKUP_DIR/$DATE/" # Compress tar -czf "$BACKUP_DIR/memento-$DATE.tar.gz" -C "$BACKUP_DIR" $DATE rm -rf "$BACKUP_DIR/$DATE" # Keep last 7 days find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete echo "Backup completed: memento-$DATE.tar.gz" ``` ### Schedule with Cron ```bash # Edit crontab crontab -e # Add daily backup at 2 AM 0 2 * * * /path/to/backup.sh ``` ### Restore from Backup ```bash # Extract backup tar -xzf memento-20240109_020000.tar.gz # Restore database docker cp memento-20240109_020000/database.db \ memento-web:/app/prisma/restore.db docker compose exec keep-notes \ sqlite3 /app/prisma/dev.db ".restore /app/prisma/restore.db" # Restore uploads docker cp memento-20240109_020000/uploads/. \ memento-web:/app/public/uploads/ # Restart services docker compose restart ``` --- ## Security Hardening ### 1. Docker Security ```yaml # docker-compose.yml hardening services: keep-notes: # ... existing config security_opt: - no-new-privileges:true read_only: true tmpfs: - /tmp cap_drop: - ALL cap_add: - NET_BIND_SERVICE ``` ### 2. Network Security ```yaml # Isolate services networks: memento-network: driver: bridge internal: false # Set to true to block external access ipam: config: - subnet: 172.20.0.0/16 ``` ### 3. Secrets Management ```bash # Use Docker Secrets (Swarm mode) echo "your-secret" | docker secret create nextauth_secret - # In docker-compose.yml services: keep-notes: secrets: - nextauth_secret environment: NEXTAUTH_SECRET_FILE: /run/secrets/nextauth_secret secrets: nextauth_secret: external: true ``` ### 4. Update Base Images ```bash # Update to latest Alpine docker pull node:20-alpine # Rebuild docker compose build --no-cache ``` --- ## Troubleshooting ### Container Won't Start ```bash # Check logs docker compose logs keep-notes # Inspect container docker inspect memento-web # Check resource usage docker stats # Restart Docker daemon sudo systemctl restart docker ``` ### Database Connection Issues ```bash # Verify database file exists docker compose exec keep-notes ls -la /app/prisma/dev.db # Check permissions docker compose exec keep-notes ls -la /app/prisma/ # Re-run migrations docker compose exec keep-notes npx prisma migrate deploy ``` ### Port Conflicts ```bash # Check what's using port 3000 sudo lsof -i :3000 # Change port in docker-compose.yml ports: - "3001:3000" # Use 3001 instead ``` ### Out of Memory ```bash # Increase Docker memory limit # Docker Desktop -> Settings -> Resources -> Memory: 4GB+ # Or add memory limit in docker-compose.yml services: keep-notes: deploy: resources: limits: memory: 2G ``` ### Permission Issues ```bash # Fix file permissions docker compose exec keep-notes chown -R nextjs:nodejs /app/prisma docker compose exec keep-notes chmod 644 /app/prisma/dev.db ``` --- ## Performance Optimization ### 1. Enable BuildKit ```bash # Set environment variable export DOCKER_BUILDKIT=1 export COMPOSE_DOCKER_CLI_BUILD=1 ``` ### 2. Multi-Stage Build Already implemented in Dockerfiles to reduce image size. ### 3. Layer Caching ```dockerfile # Cache node_modules COPY package*.json ./ RUN npm ci COPY . . ``` ### 4. Resource Limits ```yaml # docker-compose.yml services: keep-notes: deploy: resources: limits: cpus: '1' memory: 2G reservations: cpus: '0.5' memory: 1G ``` ### 5. Database Optimization ```bash # Run VACUUM to optimize SQLite docker compose exec keep-notes sqlite3 /app/prisma/dev.db "VACUUM;" # Analyze query performance docker compose exec keep-notes npx prisma studio ``` --- ## CI/CD Integration ### GitHub Actions Example ```yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push run: | docker build -t yourusername/memento-web:latest ./keep-notes docker push yourusername/memento-web:latest - name: Deploy to server uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.SSH_KEY }} script: | cd /app/memento docker compose pull docker compose up -d ``` --- ## Development vs Production ### Development (Local) ```bash # Use local Next.js dev server npm run dev # Hot reload enabled # Source maps enabled # Detailed error messages ``` ### Production (Docker) ```bash # Use optimized production build docker compose up -d # Minified code # Optimized images # Server-side rendering # Health checks enabled ``` --- ## Migration from Local to Docker ### 1. Export Existing Data ```bash # From local development sqlite3 keep-notes/prisma/dev.db ".dump" > backup.sql # Or copy database file cp keep-notes/prisma/dev.db ./dev-backup.db ``` ### 2. Import to Docker ```bash # Copy to container docker cp ./dev-backup.db memento-web:/app/prisma/dev.db # Verify docker compose exec keep-notes sqlite3 /app/prisma/dev.db "SELECT COUNT(*) FROM Note;" ``` ### 3. Update Environment ```bash # Copy .env to .env.production cp .env .env.production # Update URLs for production NEXTAUTH_URL="https://your-domain.com" ``` --- ## Update Strategy ### Rolling Updates ```bash # Zero-downtime deployment docker compose up -d --no-deps --build keep-notes # Or use multiple instances docker compose up -d --scale keep-notes=2 ``` ### Rollback ```bash # Revert to previous version docker compose down git checkout docker compose up -d --build ``` --- ## Cost Estimation (Cloud Deployment) ### Self-Hosted Options | Provider | Instance | Monthly Cost | Capacity | |----------|----------|--------------|----------| | DigitalOcean | Basic Droplet | $6-12 | 1-2 GB RAM, 1 vCPU | | Linode | Nanode | $5 | 1 GB RAM, 1 vCPU | | AWS EC2 | t3.micro | $8-15 | 1 GB RAM, 2 vCPU | | Google Cloud | e2-micro | $6-12 | 1 GB RAM, 1 vCPU | | Hetzner | CX11 | €4.20 | 4 GB RAM, 1 vCPU | ### Managed Services | Service | Cost | Notes | |---------|------|-------| | Render | Free+$7/plan | Free tier available | | Railway | $5-20/plan | Simple setup | | Fly.io | $0-16/plan | Pay per use | | Vercel | $0-20/plan | Next.js optimized | --- ## Summary This deployment guide provides: - ✅ Complete Docker setup for both services - ✅ Docker Compose orchestration - ✅ Production-ready configuration - ✅ Database management in containers - ✅ SSL/TLS setup - ✅ Backup and restore procedures - ✅ Security hardening - ✅ Monitoring and troubleshooting - ✅ CI/CD integration **Quick Start:** ```bash git clone && cd Keep cp .env.example .env && nano .env docker compose up -d ``` **Access:** http://localhost:3000 --- ## Next Steps After deployment: 1. Create admin user 2. Configure AI providers (OpenAI or Ollama) 3. Set up email for password reset 4. Configure backups 5. Enable SSL/TLS for production 6. Set up monitoring 7. Review security settings