feat: add deployment guide, backup functionality, and cron jobs for automated backups

This commit is contained in:
2025-12-02 11:11:47 +01:00
parent 99853bb755
commit e6fab5ba31
8 changed files with 470 additions and 4 deletions

View File

@@ -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!

View File

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

View File

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

40
backup-db.mjs Normal file
View File

@@ -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)}`);

View File

@@ -14,6 +14,7 @@ services:
volumes:
- ./data:/app/data
- ./uploads:/app/public/uploads
- ./backups:/app/backups
environment:
- NODE_ENV=production
- TZ=Europe/Warsaw

View File

@@ -11,6 +11,7 @@ services:
- .:/app
- /app/node_modules
- ./data:/app/data
- ./backups:/app/backups
environment:
- NODE_ENV=development
- TZ=Europe/Warsaw

View File

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

View File

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