6.3 KiB
6.3 KiB
Deployment Guide
Requirements
- Docker 24.0+
- Docker Compose 2.20+
- 1GB RAM minimum (2GB recommended)
- 10GB disk space
- TLS certificate (Let's Encrypt or custom)
Quick Start
# Clone repository
git clone https://github.com/your-org/cairnquire.git
cd cairnquire
# Copy and edit configuration
cp .env.example .env
# Edit .env with your domain, email, Postmark API key
# Start services
docker-compose up -d
# Verify health
curl https://your-domain.com/health
Configuration
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
DOMAIN |
Yes | — | Public domain name |
DB_PATH |
No | /data/db.sqlite |
Database file path |
FILESTORE_PATH |
No | /data/files |
Content-addressed file storage |
NOTES_PATH |
No | /notes |
Watched markdown directory |
POSTMARK_API_KEY |
Yes | — | Postmark server API token |
POSTMARK_FROM |
No | notifications@DOMAIN |
From email address |
SESSION_SECRET |
Yes | — | 32-byte hex for session signing |
ENV |
No | production |
development or production |
Docker Compose
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
volumes:
- ./data:/data
- ./notes:/notes:ro
environment:
- DOMAIN=${DOMAIN}
- DB_PATH=/data/db.sqlite
- FILESTORE_PATH=/data/files
- NOTES_PATH=/notes
- POSTMARK_API_KEY=${POSTMARK_API_KEY}
- SESSION_SECRET=${SESSION_SECRET}
env_file:
- .env
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=100m
# Optional: Traefik for auto-HTTPS
traefik:
image: traefik:v3.0
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
labels:
- "traefik.enable=true"
restart: unless-stopped
Production Checklist
Pre-Deployment
- Change default
SESSION_SECRET(generate:openssl rand -hex 32) - Configure
POSTMARK_API_KEYwith valid token - Set
DOMAINto public hostname - Configure DNS A/AAAA records
- Set up SPF and DKIM for email domain
- Review
.envfile — no default secrets
Security
- TLS 1.3 enforced (Traefik or reverse proxy)
- HSTS header configured
- Firewall rules: only 80/443 open
- Fail2ban or rate limiting on SSH
- Automatic security updates enabled
- Backup encryption key stored securely
Monitoring
- Health check endpoint accessible
- Log rotation configured
- Disk space alerting (>80%)
- Memory usage alerting
- Uptime monitoring (external)
Backup Procedures
Automated Backup
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/md-hub-$TIMESTAMP.tar.gz"
# Create backup
tar czf "$BACKUP_FILE" \
-C /data \
db.sqlite \
files/
# Encrypt (optional)
gpg --symmetric --cipher-algo AES256 "$BACKUP_FILE"
rm "$BACKUP_FILE"
# Keep only last 30 days
find "$BACKUP_DIR" -name "md-hub-*.tar.gz.gpg" -mtime +30 -delete
Cron: 0 2 * * * /opt/cairnquire/backup.sh
Restore from Backup
# Stop application
docker-compose stop app
# Extract backup
tar xzf md-hub-20240115_020000.tar.gz -C /data
# Restart
docker-compose up -d app
# Verify
curl https://your-domain.com/health
Upgrade Procedures
Minor Update (patch version)
docker-compose pull
docker-compose up -d
Major Update
- Read release notes for breaking changes
- Create backup:
./backup.sh - Pull new image:
docker-compose pull - Run database migrations (auto on startup)
- Update configuration if needed
- Deploy:
docker-compose up -d - Verify health and functionality
- Rollback if issues: restore from backup
Troubleshooting
Application Won't Start
# Check logs
docker-compose logs -f app
# Verify configuration
docker-compose exec app /server -check-config
# Test database
docker-compose exec app sqlite3 /data/db.sqlite ".tables"
Sync Not Working
# Check file watcher
docker-compose exec app ls -la /notes
# Verify WebSocket
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
-H "Sec-WebSocket-Version: 13" \
https://your-domain.com/ws
Email Not Sending
# Test Postmark connection
curl -X POST \
https://api.postmarkapp.com/server \
-H "X-Postmark-Server-Token: YOUR_TOKEN" \
-H "Accept: application/json"
# Check email queue
docker-compose exec app sqlite3 /data/db.sqlite \
"SELECT * FROM email_queue WHERE status='failed'"
Scaling
Vertical Scaling
- Increase container memory/CPU limits
- SQLite WAL mode handles concurrent reads well
- For heavy write loads, consider libsql replication
Horizontal Scaling (Advanced)
# Requires shared storage and load balancer
services:
app1:
# ... same config ...
environment:
- DB_PATH=/shared/db.sqlite
- FILESTORE_PATH=/shared/files
app2:
# ... same config ...
environment:
- DB_PATH=/shared/db.sqlite
- FILESTORE_PATH=/shared/files
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
Note: Horizontal scaling requires:
- Shared filesystem (NFS, EFS)
- libsql replication or external database
- Sticky sessions for WebSocket
Support
- Documentation:
/docsroute (when authenticated) - Health Check:
GET /health - Metrics:
GET /metrics(Prometheus format) - Logs:
docker-compose logs -f app - Issues: https://github.com/your-org/cairnquire/issues