Files
michaelschiemer/deployment/legacy/gitea-workflows/deploy-production.yml
2025-11-24 21:28:25 +01:00

313 lines
11 KiB
YAML

name: Deploy to Production
on:
push:
branches:
- main
- production
workflow_dispatch:
inputs:
force_rebuild:
description: 'Force rebuild Docker image'
required: false
default: 'false'
skip_backup:
description: 'Skip database backup (not recommended)'
required: false
default: 'false'
env:
REGISTRY: localhost:5000
IMAGE_NAME: framework
IMAGE_TAG: latest
COMPOSE_PROJECT_NAME: framework-production
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker image
run: |
echo "Building Docker image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"
docker build \
--file docker/php/Dockerfile \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
--build-arg ENV=production \
--build-arg COMPOSER_INSTALL_FLAGS="--no-dev --optimize-autoloader --no-interaction" \
.
- name: Push image to private registry
run: |
echo "Pushing image to registry..."
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
- name: Prepare deployment files
run: |
echo "Preparing deployment files..."
mkdir -p deployment-production
cp docker-compose.base.yml deployment-production/
cp docker-compose.prod.yml deployment-production/
cp -r docker deployment-production/
# Create deployment script
cat > deployment-production/deploy.sh << 'EOF'
#!/bin/bash
set -e
echo "=================================================="
echo "Starting Production Deployment"
echo "=================================================="
echo ""
# Database backup (unless explicitly skipped)
if [ "${SKIP_BACKUP}" != "true" ]; then
echo "[0/6] Creating database backup..."
BACKUP_FILE="backup_$(date +%Y%m%d_%H%M%S).sql"
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
php console.php db:backup --output="/var/www/html/storage/backups/${BACKUP_FILE}" || {
echo "⚠️ Database backup failed - deployment aborted"
exit 1
}
echo "✅ Database backup created: ${BACKUP_FILE}"
else
echo "⚠️ Database backup skipped (not recommended for production)"
fi
# Pull latest images
echo "[1/6] Pulling latest Docker images..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml pull
# Stop existing containers gracefully
echo "[2/6] Stopping existing containers (graceful shutdown)..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml stop
# Start new containers
echo "[3/6] Starting new containers..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
# Wait for services to be healthy (longer timeout for production)
echo "[4/6] Waiting for services to be healthy..."
sleep 30
# Run database migrations
echo "[5/6] Running database migrations..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
php console.php db:migrate --force || {
echo "⚠️ Database migration failed"
exit 1
}
# Verify deployment
echo "[6/6] Verifying deployment..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
# Cleanup old containers
echo "Cleaning up old containers..."
docker system prune -f
echo ""
echo "=================================================="
echo "Production Deployment Complete"
echo "=================================================="
EOF
chmod +x deployment-production/deploy.sh
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
script: |
# Create deployment directory
mkdir -p /opt/framework-production
cd /opt/framework-production
# Backup current deployment
if [ -d "current" ]; then
echo "Backing up current deployment..."
timestamp=$(date +%Y%m%d_%H%M%S)
mv current "backup_${timestamp}"
# Keep only last 10 backups for production
ls -dt backup_* | tail -n +11 | xargs rm -rf
fi
# Create new deployment directory
mkdir -p current
cd current
- name: Copy deployment files
uses: appleboy/scp-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
source: "deployment-production/*"
target: "/opt/framework-production/current/"
strip_components: 1
- name: Execute deployment
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
script: |
cd /opt/framework-production/current
# Set skip backup flag if provided
export SKIP_BACKUP="${{ github.event.inputs.skip_backup || 'false' }}"
# Execute deployment script
./deploy.sh
- name: Health check
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
script: |
cd /opt/framework-production/current
# Wait for services to be fully ready (longer for production)
echo "Waiting 60 seconds for services to initialize..."
sleep 60
# Check container status
echo "Checking container status..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
# Check service health
echo "Checking service health..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app php -v
# Check PHP-FPM is running
echo "Checking PHP-FPM process..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app pgrep php-fpm
# Test HTTP endpoint (via Traefik)
echo "Testing production endpoint..."
curl -f -k https://michaelschiemer.de/health || {
echo "⚠️ Health check endpoint failed"
exit 1
}
# Check Redis connection
echo "Checking Redis connection..."
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-redis redis-cli ping
echo ""
echo "✅ All health checks passed!"
- name: Smoke tests
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
script: |
echo "Running production smoke tests..."
# Test main page
curl -f -k https://michaelschiemer.de/ > /dev/null 2>&1 && echo "✅ Main page accessible" || {
echo "❌ Main page failed"
exit 1
}
# Test API health
curl -f -k https://michaelschiemer.de/api/health > /dev/null 2>&1 && echo "✅ API health check passed" || {
echo "❌ API health check failed"
exit 1
}
echo "✅ Smoke tests completed successfully"
- name: Rollback on failure
if: failure()
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
script: |
cd /opt/framework-production
if [ -d "$(ls -dt backup_* 2>/dev/null | head -n1)" ]; then
echo "🚨 Rolling back to previous deployment..."
latest_backup=$(ls -dt backup_* | head -n1)
# Stop current broken deployment
cd current
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml down
cd ..
# Restore backup
rm -rf current
cp -r "$latest_backup" current
# Start restored deployment
cd current
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
# Wait for services
sleep 30
# Verify rollback
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
echo "✅ Rollback complete - previous version restored"
else
echo "❌ No backup available for rollback"
echo "⚠️ MANUAL INTERVENTION REQUIRED"
exit 1
fi
- name: Notify deployment status
if: always()
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
script: |
if [ "${{ job.status }}" == "success" ]; then
echo "✅ Production deployment successful"
echo "URL: https://michaelschiemer.de"
echo "Deployed at: $(date)"
# Log deployment
echo "$(date) - Deployment SUCCESS - Commit: ${{ github.sha }}" >> /opt/framework-production/deployment.log
else
echo "❌ Production deployment failed - rollback executed"
# Log deployment failure
echo "$(date) - Deployment FAILED - Commit: ${{ github.sha }}" >> /opt/framework-production/deployment.log
# Send alert (placeholder - implement actual alerting)
echo "⚠️ ALERT: Production deployment failed. Manual intervention may be required."
fi
- name: Clean up build artifacts
if: always()
run: |
echo "Cleaning up deployment artifacts..."
rm -rf deployment-production
echo "✅ Cleanup complete"