411 lines
9.6 KiB
Markdown
411 lines
9.6 KiB
Markdown
# 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!
|