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
TZenv 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_SECRETgenerated (min 32 chars) .env.productionhas 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
- Always use volumes for data persistence (database, uploads)
- Timezone matters: Set it in both Dockerfile and docker-compose
- Rebuild vs Restart: Dockerfile changes need rebuild, code changes just restart
- Port mapping: Be consistent (I use 3001:3000 - HOST:CONTAINER)
- Environment secrets: Never commit, always use
.env.production - Nginx: Don't forget to setup reverse proxy and SSL
- Git auth: For private repos, use SSH keys or tokens in URL
- Test locally first: Use
docker-compose.ymlwithDockerfile.dev - Monitor logs: Set up log rotation if app is chatty
- 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!