mirror of
https://github.com/thecyberlearn/modern-django-starter.git
synced 2026-08-18 06:52:54 +00:00
Add comprehensive deployment automation and prevention system
🎯 Future-proof deployment system: 1. DEPLOYMENT_CHECKLIST.md - Zero-fail step-by-step guide 2. scripts/pre-deploy-check.sh - Validates everything before deployment 3. scripts/post-deploy-verify.sh - Tests deployment after completion 4. .github/workflows/deploy.yml - Automated GitHub Actions validation ✅ Key features: - Catches static files, migrations, Docker issues before deployment - Provides clear error messages and next steps - Tests all endpoints after deployment - 5-minute deployment target with this system - Copy-paste templates for new projects Run: ./scripts/pre-deploy-check.sh before every deployment Run: ./scripts/post-deploy-verify.sh after deployment Never face deployment issues again! 🚀
This commit is contained in:
parent
3827944aa5
commit
85ebac761d
89
.github/workflows/deploy.yml
vendored
Normal file
89
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
name: 🚀 Deploy to Dokploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre-deploy-checks:
|
||||
runs-on: ubuntu-latest
|
||||
name: 🔍 Pre-deployment validation
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('requirements/production.txt') }}
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements/production.txt
|
||||
|
||||
- name: Install and build Tailwind CSS
|
||||
run: |
|
||||
cd theme/static_src
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Run Django system checks
|
||||
run: |
|
||||
export DJANGO_SETTINGS_MODULE=django_project.settings.production
|
||||
export SECRET_KEY=github-actions-secret-key
|
||||
export ALLOWED_HOSTS=localhost
|
||||
export DEBUG=False
|
||||
python manage.py check --deploy
|
||||
|
||||
- name: Test static file collection
|
||||
run: |
|
||||
export DJANGO_SETTINGS_MODULE=django_project.settings.build
|
||||
python manage.py collectstatic --noinput --dry-run
|
||||
|
||||
- name: Check for pending migrations
|
||||
run: |
|
||||
export DJANGO_SETTINGS_MODULE=django_project.settings.production
|
||||
export SECRET_KEY=github-actions-secret-key
|
||||
export DATABASE_URL=sqlite:///temp.db
|
||||
python manage.py makemigrations --check --dry-run
|
||||
|
||||
- name: Test Docker build
|
||||
run: |
|
||||
docker build --target production -t django-template-test .
|
||||
docker rmi django-template-test
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: 📤 Deploy notification
|
||||
needs: pre-deploy-checks
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: ✅ Pre-deployment checks passed
|
||||
run: |
|
||||
echo "🎉 All pre-deployment checks passed!"
|
||||
echo "🚀 Ready for deployment to Dokploy"
|
||||
echo "📋 Manual steps required:"
|
||||
echo " 1. Trigger deployment in Dokploy dashboard"
|
||||
echo " 2. Wait for build completion"
|
||||
echo " 3. Run post-deployment verification"
|
||||
|
||||
- name: 📝 Deployment reminder
|
||||
if: success()
|
||||
run: |
|
||||
echo "::notice::Deployment ready! Trigger deployment in Dokploy manually"
|
||||
162
DEPLOYMENT_CHECKLIST.md
Normal file
162
DEPLOYMENT_CHECKLIST.md
Normal file
@ -0,0 +1,162 @@
|
||||
# 🚀 Django Deployment Checklist - Zero-Fail Guide
|
||||
|
||||
Use this checklist to deploy Django projects smoothly every time.
|
||||
|
||||
## ✅ **Pre-Deployment Checklist**
|
||||
|
||||
### **1. Static Files & Assets**
|
||||
- [ ] **Build Tailwind CSS locally**: `npm run build` in theme/static_src/
|
||||
- [ ] **Verify CSS files exist**: Check `theme/static/css/dist/styles.css` exists
|
||||
- [ ] **Test collectstatic locally**: `python manage.py collectstatic --noinput`
|
||||
- [ ] **Commit static files**: Ensure built assets are in Git
|
||||
|
||||
### **2. Database & Migrations**
|
||||
- [ ] **Create migrations**: `python manage.py makemigrations`
|
||||
- [ ] **Test migrations locally**: `python manage.py migrate`
|
||||
- [ ] **Verify Sites framework**: Check `django.contrib.sites` in INSTALLED_APPS
|
||||
- [ ] **Test site creation**: Run `python manage.py configure_site` locally
|
||||
|
||||
### **3. Environment Configuration**
|
||||
- [ ] **Update .env.example**: Include all required variables
|
||||
- [ ] **Document required vars**: List in README what's needed for production
|
||||
- [ ] **Test with production settings**: `DJANGO_SETTINGS_MODULE=project.settings.production`
|
||||
|
||||
### **4. Docker Configuration**
|
||||
- [ ] **Test Docker build**: `docker build --target production .`
|
||||
- [ ] **Verify startup script**: Ensure `startup.sh` is executable
|
||||
- [ ] **Test compose file**: `docker-compose -f docker-compose.dokploy-simple.yml up`
|
||||
|
||||
## 🔧 **Deployment Steps (Dokploy)**
|
||||
|
||||
### **Step 1: Repository Setup**
|
||||
- [ ] Push all changes to main branch
|
||||
- [ ] Verify compose file path is correct in Dokploy
|
||||
- [ ] Check Dockerfile builds successfully
|
||||
|
||||
### **Step 2: Environment Variables**
|
||||
Set these in Dokploy environment tab:
|
||||
```env
|
||||
SECRET_KEY=your-long-secret-key-here
|
||||
DEBUG=False
|
||||
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
|
||||
CSRF_TRUSTED_ORIGINS=https://yourdomain.com
|
||||
SECURE_SSL_REDIRECT=True
|
||||
DATABASE_URL=postgresql://... (if using external DB)
|
||||
```
|
||||
|
||||
### **Step 3: Domain Configuration**
|
||||
- [ ] DNS: Point domain to Dokploy server IP
|
||||
- [ ] Dokploy Domain: Set service=web, port=8000
|
||||
- [ ] HTTPS: Enable SSL/Auto SSL
|
||||
- [ ] Wait 2-3 minutes for SSL certificate
|
||||
|
||||
### **Step 4: Deploy & Verify**
|
||||
- [ ] Deploy application
|
||||
- [ ] Check deployment logs for errors
|
||||
- [ ] Test endpoints:
|
||||
- [ ] Homepage: `https://yourdomain.com/`
|
||||
- [ ] Health: `https://yourdomain.com/health/`
|
||||
- [ ] Login: `https://yourdomain.com/accounts/login/`
|
||||
- [ ] Admin: `https://yourdomain.com/admin/`
|
||||
|
||||
## 🛡️ **Common Issues Prevention**
|
||||
|
||||
### **Static Files**
|
||||
✅ **DO**: Build static files during Docker build
|
||||
❌ **DON'T**: Rely on runtime collectstatic with volume mounts
|
||||
|
||||
### **Database Migrations**
|
||||
✅ **DO**: Run explicit site migration: `migrate sites`
|
||||
❌ **DON'T**: Assume all migrations run automatically
|
||||
|
||||
### **Environment Variables**
|
||||
✅ **DO**: Use environment variables for all settings
|
||||
❌ **DON'T**: Hardcode production values in settings files
|
||||
|
||||
### **Docker Configuration**
|
||||
✅ **DO**: Use simple port mapping (ports: "8000:8000")
|
||||
❌ **DON'T**: Use complex Traefik labels unless necessary
|
||||
|
||||
## 🚀 **Quick Deploy Templates**
|
||||
|
||||
### **For New Projects** (Copy-Paste Ready)
|
||||
|
||||
**docker-compose.dokploy.yml:**
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DEBUG=${DEBUG:-False}
|
||||
- ALLOWED_HOSTS=${ALLOWED_HOSTS}
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- DATABASE_URL=${DATABASE_URL:-postgresql://django_user:django_password@db:5432/django_db}
|
||||
command: sh -c "chmod +x /app/startup.sh && /app/startup.sh"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
- POSTGRES_DB=django_db
|
||||
- POSTGRES_USER=django_user
|
||||
- POSTGRES_PASSWORD=django_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
**startup.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
python manage.py migrate --noinput
|
||||
python manage.py migrate sites --noinput
|
||||
python manage.py collectstatic --noinput
|
||||
exec gunicorn --bind 0.0.0.0:8000 django_project.wsgi:application
|
||||
```
|
||||
|
||||
## 📋 **Environment Variables Template**
|
||||
```env
|
||||
SECRET_KEY=generate-long-random-key-here
|
||||
DEBUG=False
|
||||
ALLOWED_HOSTS=yourdomain.com
|
||||
CSRF_TRUSTED_ORIGINS=https://yourdomain.com
|
||||
SECURE_SSL_REDIRECT=True
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db
|
||||
```
|
||||
|
||||
## 🎯 **Time to Deploy: 5 Minutes**
|
||||
|
||||
With this checklist, deployment should take:
|
||||
- ⏱️ **2 min**: Environment setup
|
||||
- ⏱️ **2 min**: Docker build & deploy
|
||||
- ⏱️ **1 min**: SSL certificate generation
|
||||
|
||||
**Total: 5 minutes from code to live site!** 🚀
|
||||
|
||||
## 🆘 **Emergency Debugging**
|
||||
|
||||
If deployment fails:
|
||||
1. **Check logs**: Look for specific error messages
|
||||
2. **Test health endpoint**: `/health/` shows Django status
|
||||
3. **Verify environment**: Check env vars are set correctly
|
||||
4. **Database connection**: Ensure DATABASE_URL is correct
|
||||
5. **Static files**: Verify CSS files exist in image
|
||||
|
||||
## 🔄 **Automation Ideas**
|
||||
|
||||
Future improvements:
|
||||
- [ ] GitHub Actions for automated deployments
|
||||
- [ ] Pre-commit hooks for static file builds
|
||||
- [ ] Docker health checks
|
||||
- [ ] Automated backup scripts
|
||||
- [ ] Monitoring and alerting setup
|
||||
|
||||
---
|
||||
|
||||
**Follow this checklist and you'll never have deployment issues again!** ✅
|
||||
158
scripts/post-deploy-verify.sh
Executable file
158
scripts/post-deploy-verify.sh
Executable file
@ -0,0 +1,158 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Post-deployment verification script
|
||||
# Run this after deployment to verify everything is working
|
||||
|
||||
set -e
|
||||
|
||||
# Default domain (can be overridden)
|
||||
DOMAIN=${1:-"dt.netcoptech.com"}
|
||||
PROTOCOL=${2:-"https"}
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo "🔍 Verifying deployment for $PROTOCOL://$DOMAIN"
|
||||
|
||||
# Function to test URL
|
||||
test_url() {
|
||||
local url=$1
|
||||
local expected_status=${2:-200}
|
||||
local description=$3
|
||||
|
||||
echo "Testing: $description"
|
||||
|
||||
# Use curl to test the URL
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
response=$(curl -s -o /dev/null -w "%{http_code}" -L "$url" 2>/dev/null || echo "000")
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ curl not found, skipping URL test${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$response" -eq "$expected_status" ]; then
|
||||
echo -e "${GREEN}✅ $description - HTTP $response${NC}"
|
||||
elif [ "$response" -eq "000" ]; then
|
||||
echo -e "${RED}❌ $description - Connection failed${NC}"
|
||||
return 1
|
||||
else
|
||||
echo -e "${RED}❌ $description - HTTP $response (expected $expected_status)${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to test JSON endpoint
|
||||
test_json_endpoint() {
|
||||
local url=$1
|
||||
local description=$2
|
||||
|
||||
echo "Testing: $description"
|
||||
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
response=$(curl -s -L "$url" 2>/dev/null || echo "")
|
||||
if echo "$response" | python3 -m json.tool > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✅ $description - Valid JSON response${NC}"
|
||||
# Pretty print the response
|
||||
echo "$response" | python3 -m json.tool | head -10
|
||||
else
|
||||
echo -e "${RED}❌ $description - Invalid JSON response${NC}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ curl not found, skipping JSON test${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "🌐 Testing core endpoints..."
|
||||
|
||||
# Test main endpoints
|
||||
test_url "$PROTOCOL://$DOMAIN/" 200 "Homepage"
|
||||
test_json_endpoint "$PROTOCOL://$DOMAIN/health/" "Health check endpoint"
|
||||
test_url "$PROTOCOL://$DOMAIN/accounts/login/" 200 "Login page"
|
||||
test_url "$PROTOCOL://$DOMAIN/admin/" 302 "Admin panel (should redirect to login)"
|
||||
|
||||
echo ""
|
||||
echo "🔒 Testing SSL/HTTPS..."
|
||||
|
||||
if [ "$PROTOCOL" = "https" ]; then
|
||||
# Test SSL certificate
|
||||
if command -v openssl > /dev/null 2>&1; then
|
||||
echo "Checking SSL certificate..."
|
||||
cert_info=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$cert_info" ]; then
|
||||
echo -e "${GREEN}✅ SSL certificate is valid${NC}"
|
||||
echo "$cert_info"
|
||||
else
|
||||
echo -e "${RED}❌ SSL certificate check failed${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ openssl not found, skipping SSL check${NC}"
|
||||
fi
|
||||
|
||||
# Test HTTP to HTTPS redirect
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
redirect_response=$(curl -s -o /dev/null -w "%{redirect_url}" "http://$DOMAIN/" || echo "")
|
||||
if [[ "$redirect_response" == "https://"* ]]; then
|
||||
echo -e "${GREEN}✅ HTTP to HTTPS redirect working${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ HTTP to HTTPS redirect not detected${NC}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Testing HTTP deployment..."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🗄️ Testing database connectivity..."
|
||||
|
||||
# Test if site is configured properly by checking login page
|
||||
login_response=$(curl -s -L "$PROTOCOL://$DOMAIN/accounts/login/" 2>/dev/null || echo "")
|
||||
if echo "$login_response" | grep -q "django_site" 2>/dev/null; then
|
||||
echo -e "${RED}❌ Database error: django_site relation not found${NC}"
|
||||
elif echo "$login_response" | grep -q "Login" 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ Database connectivity working${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Unable to verify database connectivity${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📱 Testing responsive design..."
|
||||
|
||||
# Test if CSS is loading
|
||||
css_test=$(curl -s -L "$PROTOCOL://$DOMAIN/" 2>/dev/null | grep -o "styles\.css" || echo "")
|
||||
if [ -n "$css_test" ]; then
|
||||
echo -e "${GREEN}✅ CSS files are being loaded${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ CSS files may not be loading properly${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎯 Deployment verification summary:"
|
||||
|
||||
# Final health check
|
||||
health_url="$PROTOCOL://$DOMAIN/health/"
|
||||
if curl -s "$health_url" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}🎉 Deployment successful!${NC}"
|
||||
echo -e "${GREEN}✅ Your Django app is live and healthy${NC}"
|
||||
echo ""
|
||||
echo "🔗 Important URLs:"
|
||||
echo " Homepage: $PROTOCOL://$DOMAIN/"
|
||||
echo " Login: $PROTOCOL://$DOMAIN/accounts/login/"
|
||||
echo " Admin: $PROTOCOL://$DOMAIN/admin/"
|
||||
echo " Health: $PROTOCOL://$DOMAIN/health/"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Create your admin user: python manage.py createsuperuser"
|
||||
echo "2. Configure social authentication in admin panel"
|
||||
echo "3. Test all functionality thoroughly"
|
||||
else
|
||||
echo -e "${RED}❌ Deployment verification failed${NC}"
|
||||
echo "Check deployment logs and retry"
|
||||
exit 1
|
||||
fi
|
||||
104
scripts/pre-deploy-check.sh
Executable file
104
scripts/pre-deploy-check.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Pre-deployment validation script
|
||||
# Run this before every deployment to catch issues early
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Running pre-deployment checks..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print status
|
||||
print_status() {
|
||||
if [ $1 -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ $2${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ $2${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
# Check 1: Verify Tailwind CSS is built
|
||||
echo "📦 Checking Tailwind CSS files..."
|
||||
if [ -f "theme/static/css/dist/styles.css" ]; then
|
||||
print_status 0 "Tailwind CSS files exist"
|
||||
else
|
||||
print_status 1 "Tailwind CSS not built. Run: cd theme/static_src && npm run build"
|
||||
fi
|
||||
|
||||
# Check 2: Test collectstatic
|
||||
echo "📁 Testing static file collection..."
|
||||
if python manage.py collectstatic --noinput --dry-run > /dev/null 2>&1; then
|
||||
print_status 0 "Static files collection test passed"
|
||||
else
|
||||
print_status 1 "Static files collection failed. Check STATIC_ROOT and STATICFILES_DIRS"
|
||||
fi
|
||||
|
||||
# Check 3: Check for pending migrations
|
||||
echo "🗄️ Checking for pending migrations..."
|
||||
if python manage.py showmigrations --plan | grep -q "\[ \]"; then
|
||||
print_status 1 "Pending migrations found. Run: python manage.py makemigrations"
|
||||
else
|
||||
print_status 0 "No pending migrations"
|
||||
fi
|
||||
|
||||
# Check 4: Validate Django configuration
|
||||
echo "⚙️ Validating Django settings..."
|
||||
if python manage.py check > /dev/null 2>&1; then
|
||||
print_status 0 "Django configuration valid"
|
||||
else
|
||||
print_status 1 "Django configuration issues found. Run: python manage.py check"
|
||||
fi
|
||||
|
||||
# Check 5: Test Docker build
|
||||
echo "🐳 Testing Docker build..."
|
||||
if docker build --target production -t django-deploy-test . > /dev/null 2>&1; then
|
||||
print_status 0 "Docker build successful"
|
||||
docker rmi django-deploy-test > /dev/null 2>&1 || true
|
||||
else
|
||||
print_status 1 "Docker build failed. Check Dockerfile"
|
||||
fi
|
||||
|
||||
# Check 6: Verify startup script
|
||||
echo "🚀 Checking startup script..."
|
||||
if [ -x "startup.sh" ]; then
|
||||
print_status 0 "Startup script is executable"
|
||||
else
|
||||
print_status 1 "Startup script not executable. Run: chmod +x startup.sh"
|
||||
fi
|
||||
|
||||
# Check 7: Validate environment variables
|
||||
echo "🔐 Checking environment configuration..."
|
||||
if [ -f ".env.example" ]; then
|
||||
print_status 0 "Environment example file exists"
|
||||
else
|
||||
print_warning "No .env.example file found. Consider creating one"
|
||||
fi
|
||||
|
||||
# Check 8: Verify required files
|
||||
required_files=("manage.py" "requirements/production.txt" "docker-compose.dokploy-simple.yml")
|
||||
for file in "${required_files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
print_status 0 "Required file exists: $file"
|
||||
else
|
||||
print_status 1 "Missing required file: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 All pre-deployment checks passed!${NC}"
|
||||
echo -e "${GREEN}✅ Ready for deployment!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. git add . && git commit -m 'Ready for deployment' && git push"
|
||||
echo "2. Deploy in Dokploy"
|
||||
echo "3. Test endpoints after deployment"
|
||||
Loading…
Reference in New Issue
Block a user