Some checks failed
Deploy Application / deploy (push) Has been cancelled
313 lines
11 KiB
YAML
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"
|