This comprehensive guide covers all deployment options for CheckMate, including environment configuration, backup procedures, and maintenance tasks.
- Deployment Options Overview
- Option 1: Homelab with Cloudflare Tunnel (Zero Cost)
- Option 2: VPS with Coolify
- Option 3: Standard VPS Deployment
- Environment Variables Reference
- Backup and Restore Procedures
- Maintenance and Updates
- Troubleshooting
| Option | Cost | Best For | Complexity |
|---|---|---|---|
| Homelab + Cloudflare Tunnel | $0/month | Home server, Raspberry Pi, NAS | Medium |
| VPS + Coolify | $5-10/month | No homelab, managed deployments | Low |
| Standard VPS | $5-10/month | Full control, custom setups | Medium |
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU / ARM64 | 2 vCPU |
| RAM | 1GB | 2GB |
| Storage | 10GB SSD | 20GB+ SSD |
| OS | Linux (any distro with Docker) | Ubuntu 22.04 LTS / Debian 12 |
This is the recommended option for users with home servers, Raspberry Pi, NAS devices, or any always-on computer.
- $0/month - Only electricity costs
- No port forwarding - Outbound-only connections
- Free SSL - Automatic HTTPS from Cloudflare
- DDoS protection - Cloudflare's edge network
- Global CDN - Static assets cached worldwide
- Docker and Docker Compose installed
- Cloudflare account (free): https://dash.cloudflare.com/sign-up
- (Optional) Domain name added to Cloudflare
# 1. Clone the repository
git clone https://github.com/keyurgolani/Checkmate.git
cd Checkmate
# 2. Copy environment file
cp .env.example .env
# 3. Configure your Cloudflare Tunnel token (see detailed guide)
# Edit .env and set CLOUDFLARE_TUNNEL_TOKEN
# 4. Start with Cloudflare Tunnel
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up -d
# 5. Check status
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml psFor step-by-step Cloudflare Tunnel configuration, see: Cloudflare Tunnel Setup Guide
Coolify is a self-hosted alternative to Vercel/Netlify that simplifies deployments.
- Git-based deployments - Push to deploy
- Automatic SSL - Let's Encrypt certificates
- Web UI - Manage deployments visually
- Multiple apps - Host other projects too
- VPS with 2GB+ RAM (Hetzner, DigitalOcean, Linode, etc.)
- Domain name with DNS access
# SSH into your VPS
ssh root@your-vps-ip
# Install Coolify (one-liner)
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bashAfter installation, access Coolify at http://your-vps-ip:8000
- Create an account on first visit
- Add your server (localhost is auto-detected)
- Add your domain in Settings → Domains
- Create a new project in Coolify
- Add a new resource → Docker Compose
- Connect your Git repository or upload files
- Configure environment variables (see Environment Variables)
- Deploy
Point your domain to your VPS IP:
A checkmate.yourdomain.com → your-vps-ip
A api.checkmate.yourdomain.com → your-vps-ip
When deploying via Coolify, use this modified configuration:
# docker-compose.coolify.yml
services:
nextjs:
build:
context: .
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- NEXT_PUBLIC_POCKETBASE_URL=${NEXT_PUBLIC_POCKETBASE_URL}
- POCKETBASE_URL=http://pocketbase:8090
depends_on:
pocketbase:
condition: service_healthy
labels:
- "coolify.managed=true"
pocketbase:
build:
context: ./docker/pocketbase
dockerfile: Dockerfile
volumes:
- pocketbase_data:/pb/pb_data
healthcheck:
test:
["CMD", "wget", "--spider", "-q", "http://localhost:8090/api/health"]
interval: 30s
timeout: 10s
retries: 3
labels:
- "coolify.managed=true"
volumes:
pocketbase_data:For users who want full control without Coolify.
- VPS with Docker and Docker Compose
- Domain name
- SSL certificate (Let's Encrypt recommended)
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Install Docker Compose
sudo apt install docker-compose-plugin -y
# Log out and back in for group changes
exit# Clone repository
git clone https://github.com/keyurgolani/Checkmate.git
cd Checkmate
# Configure environment
cp .env.example .env
nano .env # Edit with your settings
# Start services
docker compose up -dInstall and configure Nginx as a reverse proxy with SSL:
# Install Nginx and Certbot
sudo apt install nginx certbot python3-certbot-nginx -yCreate Nginx configuration:
# /etc/nginx/sites-available/checkmate
server {
listen 80;
server_name checkmate.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name checkmate.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/checkmate.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/checkmate.yourdomain.com/privkey.pem;
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;
}
}
server {
listen 80;
server_name api.checkmate.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.checkmate.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/api.checkmate.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.checkmate.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:8090;
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;
}
}Enable the site and get SSL certificates:
# Enable site
sudo ln -s /etc/nginx/sites-available/checkmate /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Get SSL certificates
sudo certbot --nginx -d checkmate.yourdomain.com -d api.checkmate.yourdomain.com
# Reload Nginx
sudo systemctl reload nginx| Variable | Description | Example |
|---|---|---|
NEXT_PUBLIC_POCKETBASE_URL |
Public URL for PocketBase API | https://api.checkmate.yourdomain.com |
POCKETBASE_URL |
Internal URL for server-side requests | http://pocketbase:8090 |
| Variable | Description | Default |
|---|---|---|
NEXTJS_PORT |
Port for Next.js application | 3000 |
POCKETBASE_PORT |
Port for PocketBase | 8090 |
NODE_ENV |
Node environment | production |
CLOUDFLARE_TUNNEL_TOKEN |
Cloudflare Tunnel token | - |
PB_ADMIN_EMAIL |
PocketBase admin email | - |
PB_ADMIN_PASSWORD |
PocketBase admin password | - |
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
GITHUB_CLIENT_ID |
GitHub OAuth client ID |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret |
| Variable | Description |
|---|---|
SMTP_HOST |
SMTP server hostname |
SMTP_PORT |
SMTP server port |
SMTP_USER |
SMTP username |
SMTP_PASSWORD |
SMTP password |
SMTP_FROM |
From email address |
PocketBase stores all data in SQLite database files within the pb_data directory. Regular backups are essential.
# Create backup directory
mkdir -p ~/checkmate-backups
# Stop services (optional but recommended for consistency)
docker compose stop pocketbase
# Create timestamped backup
docker run --rm \
-v checkmate_pocketbase_data:/data:ro \
-v ~/checkmate-backups:/backup \
alpine tar czf /backup/pocketbase-$(date +%Y%m%d-%H%M%S).tar.gz -C /data .
# Restart services
docker compose start pocketbase
# Verify backup
ls -la ~/checkmate-backups/Create a backup script for scheduled backups:
#!/bin/bash
# /opt/checkmate/backup.sh
BACKUP_DIR="/opt/checkmate-backups"
RETENTION_DAYS=30
COMPOSE_DIR="/opt/checkmate"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Create backup
docker run --rm \
-v checkmate_pocketbase_data:/data:ro \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/pocketbase-$TIMESTAMP.tar.gz" -C /data .
# Remove old backups
find "$BACKUP_DIR" -name "pocketbase-*.tar.gz" -mtime +$RETENTION_DAYS -delete
# Log
echo "$(date): Backup completed - pocketbase-$TIMESTAMP.tar.gz" >> "$BACKUP_DIR/backup.log"Make it executable and schedule with cron:
# Make executable
chmod +x /opt/checkmate/backup.sh
# Add to crontab (daily at 2 AM)
crontab -e
# Add line: 0 2 * * * /opt/checkmate/backup.sh# Stop services
cd /path/to/checkmate
docker compose down
# Remove existing data volume
docker volume rm checkmate_pocketbase_data
# Create new volume
docker volume create checkmate_pocketbase_data
# Restore from backup
docker run --rm \
-v checkmate_pocketbase_data:/data \
-v ~/checkmate-backups:/backup:ro \
alpine sh -c "cd /data && tar xzf /backup/pocketbase-YYYYMMDD-HHMMSS.tar.gz"
# Start services
docker compose up -dFor additional safety, sync backups to cloud storage:
# Using rclone (supports S3, Google Drive, Dropbox, etc.)
# Install: https://rclone.org/install/
# Configure rclone
rclone config
# Sync backups to remote
rclone sync ~/checkmate-backups remote:checkmate-backupsAdd to backup script:
# After local backup
rclone copy "$BACKUP_DIR/pocketbase-$TIMESTAMP.tar.gz" remote:checkmate-backups/cd /path/to/checkmate
# Pull latest code
git pull origin main
# Pull latest images
docker compose pull
# Rebuild and restart
docker compose up -d --builddocker compose -f docker-compose.yml -f docker-compose.tunnel.yml pull
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up -d --build# All services
docker compose logs -f
# Specific service
docker compose logs -f nextjs
docker compose logs -f pocketbase
# Last 100 lines
docker compose logs --tail 100# Check service status
docker compose ps
# Check PocketBase health
curl http://localhost:8090/api/health
# Check Next.js health
curl http://localhost:3000PocketBase automatically handles SQLite maintenance, but you can manually optimize:
# Access PocketBase container
docker exec -it checkmate-pocketbase sh
# Run SQLite vacuum (compacts database)
sqlite3 /pb/pb_data/data.db "VACUUM;"
# Exit container
exit# Check logs for errors
docker compose logs
# Check if ports are in use
sudo lsof -i :3000
sudo lsof -i :8090
# Restart Docker
sudo systemctl restart docker# Verify PocketBase is healthy
docker compose ps pocketbase
# Check PocketBase logs
docker compose logs pocketbase
# Test internal connectivity
docker exec checkmate-app wget -q -O- http://pocketbase:8090/api/health# Check tunnel logs
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml logs cloudflared
# Verify token is set
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml config | grep TUNNEL_TOKEN
# Check tunnel status in Cloudflare dashboard
# https://one.dash.cloudflare.com/ → Networks → Tunnels# Check disk usage
df -h
# Clean Docker resources
docker system prune -a
# Remove old backups
find ~/checkmate-backups -name "*.tar.gz" -mtime +7 -delete# Check memory usage
docker stats
# Limit container memory in docker-compose.yml
services:
nextjs:
deploy:
resources:
limits:
memory: 512M- GitHub Issues: Report bugs and request features
- Documentation: Check the
/docsfolder for guides - PocketBase Docs: https://pocketbase.io/docs/
- Next.js Docs: https://nextjs.org/docs
- Keep software updated - Regularly update Docker images and dependencies
- Use strong passwords - Especially for PocketBase admin
- Enable HTTPS - Always use SSL in production
- Backup regularly - Automate backups and test restores
- Monitor logs - Watch for suspicious activity
- Limit access - Use firewalls and Cloudflare Access policies
- Never commit secrets - Keep
.envout of version control