## 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
23 KiB
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
- Web Application: http://localhost:3000
- Prisma Studio: http://localhost:5555 (when running)
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
Option 1: Docker Compose (Recommended)
# 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:
- Create admin user
- Configure AI providers (OpenAI or Ollama)
- Set up email for password reset
- Configure backups
- Enable SSL/TLS for production
- Set up monitoring
- Review security settings