Files
panel/DEPLOYMENT_GUIDE_TEMPLATE.md

9.6 KiB

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)

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

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

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)

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

chmod +x deploy.sh

Step 3: Configure Environment

.env.production

# 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

# 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

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

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

Configure Nginx

Create /etc/nginx/sites-available/yourapp:

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:

sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Setup SSL (HTTPS)

sudo certbot --nginx -d yourdomain.com
# Follow prompts to get free SSL certificate

Step 6: Deploy!

Deployment Methods

Method 1: From Local Files

cd ~/app
git pull origin main  # Update code
./deploy.sh

Method 2: From Git Repository

cd ~/app
./deploy.sh https://git.yourserver.com/user/repo.git main

Method 3: Specific Commit

./deploy.sh https://git.yourserver.com/user/repo.git main abc123def

Ongoing Maintenance

View Logs

docker-compose -f docker-compose.prod.yml logs -f

Restart App

docker-compose -f docker-compose.prod.yml restart

Full Rebuild (for Dockerfile changes)

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

docker-compose -f docker-compose.prod.yml ps
docker-compose -f docker-compose.prod.yml exec app date  # Check timezone

Backup Data

# 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

# If using Git commits
./deploy.sh https://git.yourserver.com/user/repo.git main PREVIOUS_COMMIT_HASH

Troubleshooting

Container won't start

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

# Fix ownership
sudo chown -R $USER:$USER data/ uploads/

Port already in use

# 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

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