From e6fab5ba31fab91dc05dafa4c0ffe753a2777cbf Mon Sep 17 00:00:00 2001 From: RKWojs Date: Tue, 2 Dec 2025 11:11:47 +0100 Subject: [PATCH] feat: add deployment guide, backup functionality, and cron jobs for automated backups --- DEPLOYMENT_GUIDE_TEMPLATE.md | 410 +++++++++++++++++++++++++++++++++++ Dockerfile | 4 +- Dockerfile.dev | 4 +- backup-db.mjs | 40 ++++ docker-compose.prod.yml | 1 + docker-compose.yml | 1 + docker-entrypoint-dev.sh | 7 + docker-entrypoint.sh | 7 + 8 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 DEPLOYMENT_GUIDE_TEMPLATE.md create mode 100644 backup-db.mjs diff --git a/DEPLOYMENT_GUIDE_TEMPLATE.md b/DEPLOYMENT_GUIDE_TEMPLATE.md new file mode 100644 index 0000000..a30e04c --- /dev/null +++ b/DEPLOYMENT_GUIDE_TEMPLATE.md @@ -0,0 +1,410 @@ +# Docker Git Deployment Strategy - Quick Guide + +A proven deployment strategy for Next.js apps (or any Node.js app) on a VPS using Docker with Git integration. + +## Quick Overview + +**Strategy**: Docker containers + Git repo integration + Zero-downtime deployments +**Benefits**: Reproducible builds, easy rollbacks, consistent environments +**Time to setup**: ~30 minutes + +--- + +## Step 1: Create Docker Files + +### `Dockerfile` (Production) +```dockerfile +FROM node:22.11.0 + +# Set timezone (adjust for your region) +ENV TZ=Europe/Warsaw +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Install git for repo cloning +RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Support building from Git repo +ARG GIT_REPO_URL +ARG GIT_BRANCH=main +ARG GIT_COMMIT + +# Clone from git OR use local files +RUN if [ -n "$GIT_REPO_URL" ]; then \ + git clone --branch ${GIT_BRANCH} ${GIT_REPO_URL} . && \ + if [ -n "$GIT_COMMIT" ]; then git checkout ${GIT_COMMIT}; fi; \ + fi + +COPY package*.json ./ +RUN npm install + +COPY . . +RUN npm run build + +# Copy entrypoint script +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +EXPOSE 3000 +ENTRYPOINT ["/docker-entrypoint.sh"] +``` + +### `docker-entrypoint.sh` +```bash +#!/bin/bash +echo "🚀 Starting application..." + +# Create necessary directories +mkdir -p /app/data +mkdir -p /app/public/uploads + +# Initialize database, create admin, etc. +node scripts/init-setup.js + +# Start the app +exec npm start +``` + +### `docker-compose.prod.yml` +```yaml +version: "3.9" + +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + - GIT_REPO_URL=${GIT_REPO_URL} + - GIT_BRANCH=${GIT_BRANCH:-main} + - GIT_COMMIT=${GIT_COMMIT} + ports: + - "3001:3000" # HOST:CONTAINER + volumes: + - ./data:/app/data # Persist database + - ./uploads:/app/public/uploads # Persist files + environment: + - NODE_ENV=production + - TZ=Europe/Warsaw + - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} + - NEXTAUTH_URL=${NEXTAUTH_URL} + - AUTH_TRUST_HOST=true + restart: unless-stopped +``` + +--- + +## Step 2: Create Deployment Script + +### `deploy.sh` (Linux/Mac) +```bash +#!/bin/bash +set -e + +GIT_REPO_URL=${1:-""} +GIT_BRANCH=${2:-"main"} +GIT_COMMIT=${3:-""} + +# Load environment variables +if [ -f .env.production ]; then + export $(grep -v '^#' .env.production | xargs) +fi + +# Validate critical vars +if [ -z "$NEXTAUTH_SECRET" ] || [ -z "$NEXTAUTH_URL" ]; then + echo "ERROR: Set NEXTAUTH_SECRET and NEXTAUTH_URL in .env.production" + exit 1 +fi + +# Build from Git or local files +if [ -z "$GIT_REPO_URL" ]; then + echo "Building from local files..." + docker-compose -f docker-compose.prod.yml build +else + echo "Building from git: $GIT_REPO_URL (branch: $GIT_BRANCH)" + GIT_REPO_URL=$GIT_REPO_URL GIT_BRANCH=$GIT_BRANCH GIT_COMMIT=$GIT_COMMIT \ + docker-compose -f docker-compose.prod.yml build +fi + +# Deploy +echo "Deploying..." +docker-compose -f docker-compose.prod.yml down +docker-compose -f docker-compose.prod.yml up -d + +echo "✅ Deployment completed!" +echo "Application running at: $NEXTAUTH_URL (port 3001 on host)" +``` + +Make it executable: +```bash +chmod +x deploy.sh +``` + +--- + +## Step 3: Configure Environment + +### `.env.production` +```bash +# Generate secret: openssl rand -base64 32 +NEXTAUTH_SECRET=your-super-long-random-secret-at-least-32-chars + +# Your public URL +NEXTAUTH_URL=https://yourdomain.com + +NODE_ENV=production +AUTH_TRUST_HOST=true +``` + +**⚠️ NEVER commit `.env.production` to Git!** + +--- + +## Step 4: Setup VPS + +### Initial VPS Setup +```bash +# SSH to VPS +ssh user@your-vps-ip + +# Install Docker & Docker Compose +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh +sudo usermod -aG docker $USER +sudo apt install docker-compose-plugin + +# Logout and login again for docker group to take effect +exit +``` + +### Setup Project Directory +```bash +ssh user@your-vps-ip + +# Create project directory +mkdir -p ~/app +cd ~/app + +# Copy deployment files (from local machine): +# scp docker-compose.prod.yml deploy.sh user@vps-ip:~/app/ +# scp .env.production user@vps-ip:~/app/ + +# OR clone entire repo if using local file deployment: +git clone https://your-repo-url.git . +``` + +--- + +## Step 5: Setup Nginx Reverse Proxy + +### Install Nginx +```bash +sudo apt update +sudo apt install nginx certbot python3-certbot-nginx +``` + +### Configure Nginx +Create `/etc/nginx/sites-available/yourapp`: +```nginx +server { + listen 80; + server_name yourdomain.com; + + location / { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + 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; + } +} +``` + +Enable site: +```bash +sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl restart nginx +``` + +### Setup SSL (HTTPS) +```bash +sudo certbot --nginx -d yourdomain.com +# Follow prompts to get free SSL certificate +``` + +--- + +## Step 6: Deploy! + +### Deployment Methods + +**Method 1: From Local Files** +```bash +cd ~/app +git pull origin main # Update code +./deploy.sh +``` + +**Method 2: From Git Repository** +```bash +cd ~/app +./deploy.sh https://git.yourserver.com/user/repo.git main +``` + +**Method 3: Specific Commit** +```bash +./deploy.sh https://git.yourserver.com/user/repo.git main abc123def +``` + +--- + +## Ongoing Maintenance + +### View Logs +```bash +docker-compose -f docker-compose.prod.yml logs -f +``` + +### Restart App +```bash +docker-compose -f docker-compose.prod.yml restart +``` + +### Full Rebuild (for Dockerfile changes) +```bash +docker-compose -f docker-compose.prod.yml down +docker-compose -f docker-compose.prod.yml build --no-cache +docker-compose -f docker-compose.prod.yml up -d +``` + +### Check Container Status +```bash +docker-compose -f docker-compose.prod.yml ps +docker-compose -f docker-compose.prod.yml exec app date # Check timezone +``` + +### Backup Data +```bash +# Automated daily database backups are scheduled at 2 AM +# Backups are stored in ./backups/ directory, keeping last 30 +# Check backup logs: docker-compose -f docker-compose.prod.yml exec app cat /app/data/backup.log + +# Manual backup (if needed) +tar -czf backup-$(date +%Y%m%d).tar.gz data/ uploads/ + +# Download backups to local machine +scp user@vps-ip:~/app/backups/backup-*.sqlite ./local-backups/ +``` + +### Rollback to Previous Version +```bash +# If using Git commits +./deploy.sh https://git.yourserver.com/user/repo.git main PREVIOUS_COMMIT_HASH +``` + +--- + +## Troubleshooting + +### Container won't start +```bash +docker-compose -f docker-compose.prod.yml logs app +docker-compose -f docker-compose.prod.yml exec app sh # Get shell inside container +``` + +### Timezone issues +- Make sure `TZ` env var is set in docker-compose +- Rebuild image: timezone config is baked in during build +- Verify: `docker-compose -f docker-compose.prod.yml exec app date` + +### Permission issues with volumes +```bash +# Fix ownership +sudo chown -R $USER:$USER data/ uploads/ +``` + +### Port already in use +```bash +# Check what's using port 3001 +sudo netstat -tulpn | grep 3001 +# Change port in docker-compose.prod.yml if needed +``` + +--- + +## Security Checklist + +- [ ] Strong `NEXTAUTH_SECRET` generated (min 32 chars) +- [ ] `.env.production` has secure permissions: `chmod 600 .env.production` +- [ ] Firewall configured: `sudo ufw allow 80,443/tcp` +- [ ] SSL certificate installed via Certbot +- [ ] Regular security updates: `sudo apt update && sudo apt upgrade` +- [ ] Docker images updated periodically +- [ ] Database backups automated +- [ ] Git credentials NOT stored in environment files + +--- + +## Quick Reference + +```bash +# Deploy from Git +./deploy.sh https://git.server.com/user/repo.git main + +# Deploy from local files +git pull && ./deploy.sh + +# View logs +docker-compose -f docker-compose.prod.yml logs -f + +# Restart +docker-compose -f docker-compose.prod.yml restart + +# Rebuild completely +docker-compose -f docker-compose.prod.yml down +docker-compose -f docker-compose.prod.yml build --no-cache +docker-compose -f docker-compose.prod.yml up -d + +# Backup +tar -czf backup-$(date +%Y%m%d).tar.gz data/ uploads/ +``` + +--- + +## Key Advantages of This Strategy + +✅ **Git Integration**: Deploy specific commits, branches, or tags +✅ **Reproducible**: Same build every time +✅ **Easy Rollbacks**: Just deploy previous commit +✅ **Isolated**: Container doesn't pollute host system +✅ **Persistent Data**: Volumes survive container rebuilds +✅ **Zero-Config Deployment**: Clone and run `./deploy.sh` +✅ **Works Offline**: Can build from local files without Git +✅ **Auto-Restart**: Container restarts on crash or reboot + +--- + +## Notes to Future Self + +1. **Always use volumes** for data persistence (database, uploads) +2. **Timezone matters**: Set it in both Dockerfile and docker-compose +3. **Rebuild vs Restart**: Dockerfile changes need rebuild, code changes just restart +4. **Port mapping**: Be consistent (I use 3001:3000 - HOST:CONTAINER) +5. **Environment secrets**: Never commit, always use `.env.production` +6. **Nginx**: Don't forget to setup reverse proxy and SSL +7. **Git auth**: For private repos, use SSH keys or tokens in URL +8. **Test locally first**: Use `docker-compose.yml` with `Dockerfile.dev` +9. **Monitor logs**: Set up log rotation if app is chatty +10. **Automate backups**: Cron job for daily database/file backups + +--- + +**Time to deploy a new app with this strategy: ~20 minutes** ⚡ + +Copy these files, adjust for your app (mainly environment variables and init scripts), and you're production-ready! diff --git a/Dockerfile b/Dockerfile index 894d6a7..0df481a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,8 @@ FROM node:22.11.0 ENV TZ=Europe/Warsaw RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -# Install git -RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* +# Install git and cron +RUN apt-get update && apt-get install -y git cron && rm -rf /var/lib/apt/lists/* # Set the working directory WORKDIR /app diff --git a/Dockerfile.dev b/Dockerfile.dev index 74f11ef..cba2006 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -5,8 +5,8 @@ FROM node:22.11.0 ENV TZ=Europe/Warsaw RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -# Install git for development -RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* +# Install git and cron for development +RUN apt-get update && apt-get install -y git cron && rm -rf /var/lib/apt/lists/* # Set the working directory WORKDIR /app diff --git a/backup-db.mjs b/backup-db.mjs new file mode 100644 index 0000000..e5082f4 --- /dev/null +++ b/backup-db.mjs @@ -0,0 +1,40 @@ +import Database from "better-sqlite3"; +import fs from "fs"; +import path from "path"; + +const dbPath = "data/database.sqlite"; +const backupDir = "backups"; + +// Ensure backup directory exists +if (!fs.existsSync(backupDir)) { + fs.mkdirSync(backupDir); +} + +// Generate timestamp for backup filename +const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); +const backupPath = path.join(backupDir, `backup-${timestamp}.sqlite`); + +// Create backup by copying the database file +fs.copyFileSync(dbPath, backupPath); + +console.log(`✅ Backup created: ${backupPath}`); + +// Cleanup: keep only last 30 backups +const files = fs.readdirSync(backupDir) + .filter(f => f.startsWith('backup-')) + .map(f => ({ + name: f, + path: path.join(backupDir, f), + mtime: fs.statSync(path.join(backupDir, f)).mtime + })) + .sort((a, b) => b.mtime - a.mtime); // Sort by modification time, newest first + +if (files.length > 30) { + const toDelete = files.slice(30); + toDelete.forEach(f => { + fs.unlinkSync(f.path); + console.log(`🗑️ Deleted old backup: ${f.name}`); + }); +} + +console.log(`📁 Total backups kept: ${Math.min(files.length, 30)}`); \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e7b6901..fd4fccc 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -14,6 +14,7 @@ services: volumes: - ./data:/app/data - ./uploads:/app/public/uploads + - ./backups:/app/backups environment: - NODE_ENV=production - TZ=Europe/Warsaw diff --git a/docker-compose.yml b/docker-compose.yml index 311ac50..517dc64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - .:/app - /app/node_modules - ./data:/app/data + - ./backups:/app/backups environment: - NODE_ENV=development - TZ=Europe/Warsaw diff --git a/docker-entrypoint-dev.sh b/docker-entrypoint-dev.sh index 1b46caa..a281ccb 100644 --- a/docker-entrypoint-dev.sh +++ b/docker-entrypoint-dev.sh @@ -20,6 +20,13 @@ chmod -R 755 /app/public/uploads echo "🔧 Setting up admin account..." node scripts/create-admin.js +# Set up daily backup cron job (runs at 2 AM daily) +echo "⏰ Setting up daily backup cron job..." +echo "0 2 * * * cd /app && node backup-db.mjs >> /app/data/backup.log 2>&1" > /etc/cron.d/backup-cron +chmod 0644 /etc/cron.d/backup-cron +crontab /etc/cron.d/backup-cron +service cron start + # Start the development server echo "✅ Starting development server..." exec npm run dev diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 217da9a..63bfcea 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -24,6 +24,13 @@ node scripts/create-admin.js echo "🔄 Running database migrations..." ./run-migrations.sh +# Set up daily backup cron job (runs at 2 AM daily) +echo "⏰ Setting up daily backup cron job..." +echo "0 2 * * * cd /app && node backup-db.mjs >> /app/data/backup.log 2>&1" > /etc/cron.d/backup-cron +chmod 0644 /etc/cron.d/backup-cron +crontab /etc/cron.d/backup-cron +service cron start + # Start the application echo "✅ Starting production server..." exec npm start