Keep/docs/deployment-guide.md
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

23 KiB

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

docker --version
docker compose version

Quick Start (Docker Compose)

1. Clone Repository

git clone <repository-url>
cd Keep

2. Configure Environment

# Copy environment template
cp .env.example .env

# Edit with your configuration
nano .env

3. Start Services

# Build and start all services
docker compose up -d

# View logs
docker compose logs -f

# Check service status
docker compose ps

4. Access Application

5. Stop Services

# 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

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

# ============================================
# 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

// 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

# 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)

# ============================================
# 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

# Linux/Mac
openssl rand -base64 32

# Windows (PowerShell)
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_})

Production Deployment

# 1. Clone repository
git clone <repository-url>
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

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

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

# 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)

# Create seed file
# keep-notes/prisma/seed.ts

# Run seed
docker compose exec keep-notes npx prisma db seed

Access Database Directly

# Open SQLite CLI
docker compose exec keep-notes sqlite3 /app/prisma/dev.db

# Query notes
SELECT * FROM Note LIMIT 10;

# Exit
.quit

Backup Database

# 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

# 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

# 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

# Restart all
docker compose restart

# Restart specific service
docker compose restart keep-notes

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

Update Services

# 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

# Scale keep-notes (requires load balancer)
docker compose up -d --scale keep-notes=3

Reverse Proxy Configuration

Nginx Configuration

# /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)

# 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

# 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)

# 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

# Check health status
docker compose ps

# Inspect container
docker inspect memento-web

# Resource usage
docker stats

Log Aggregation

# 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):

# 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

#!/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

# Edit crontab
crontab -e

# Add daily backup at 2 AM
0 2 * * * /path/to/backup.sh

Restore from Backup

# 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

# 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

# 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

# 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

# Update to latest Alpine
docker pull node:20-alpine

# Rebuild
docker compose build --no-cache

Troubleshooting

Container Won't Start

# 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

# 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

# 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

# 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

# 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

# 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

# Cache node_modules
COPY package*.json ./
RUN npm ci
COPY . .

4. Resource Limits

# docker-compose.yml
services:
  keep-notes:
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 1G

5. Database Optimization

# 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

# .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)

# Use local Next.js dev server
npm run dev

# Hot reload enabled
# Source maps enabled
# Detailed error messages

Production (Docker)

# 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

# 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

# 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

# Copy .env to .env.production
cp .env .env.production

# Update URLs for production
NEXTAUTH_URL="https://your-domain.com"

Update Strategy

Rolling Updates

# 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

# Revert to previous version
docker compose down
git checkout <previous-commit>
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:

git clone <repo> && 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