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