name: Production Deployment Pipeline on: push: branches: - main paths-ignore: - 'docs/**' - '**.md' - '.github/**' workflow_dispatch: inputs: skip_tests: description: 'Skip tests (emergency only)' required: false default: false type: boolean env: REGISTRY: git.michaelschiemer.de:5000 IMAGE_NAME: framework DEPLOYMENT_HOST: 94.16.110.151 jobs: # Job 1: Run Tests test: name: Run Tests & Quality Checks runs-on: ubuntu-latest if: ${{ !inputs.skip_tests }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.3' extensions: mbstring, xml, pdo, pdo_mysql, zip, gd, intl, sodium, bcmath, redis coverage: none - name: Cache Composer dependencies uses: actions/cache@v3 with: path: vendor key: composer-${{ hashFiles('composer.lock') }} restore-keys: composer- - name: Install dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - name: Run Pest tests run: ./vendor/bin/pest --colors=always - name: Run PHPStan run: make phpstan - name: Code style check run: composer cs # Job 2: Build & Push Docker Image build: name: Build Docker Image needs: test if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') runs-on: ubuntu-latest outputs: image_tag: ${{ steps.meta.outputs.tag }} commit_sha: ${{ github.sha }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate image metadata id: meta run: | SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) TAG="${SHORT_SHA}-$(date +%s)" echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - name: Login to Registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.REGISTRY_USER }} --password-stdin - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.production push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:git-${{ steps.meta.outputs.short_sha }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max build-args: | BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') GIT_COMMIT=${{ github.sha }} GIT_BRANCH=${{ github.ref_name }} - name: Image scan for vulnerabilities run: | # Optional: Add Trivy or similar vulnerability scanning echo "✅ Image built successfully: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}" # Job 3: Deploy to Production deploy: name: Deploy to Production Server needs: build runs-on: ubuntu-latest environment: name: production url: https://michaelschiemer.de steps: - name: Checkout deployment scripts uses: actions/checkout@v4 with: sparse-checkout: | deployment/ansible sparse-checkout-cone-mode: false - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production chmod 600 ~/.ssh/production ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts - name: Install Ansible run: | sudo apt-get update sudo apt-get install -y ansible - name: Deploy via Ansible run: | cd deployment/ansible ansible-playbook -i inventory/production.yml \ playbooks/deploy-update.yml \ -e "image_tag=${{ needs.build.outputs.image_tag }}" \ -e "git_commit_sha=${{ needs.build.outputs.commit_sha }}" \ -e "deployment_timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ -e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \ -e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}" - name: Wait for deployment to stabilize run: sleep 30 - name: Health check id: health run: | for i in {1..10}; do if curl -f -k https://michaelschiemer.de/health; then echo "✅ Health check passed" exit 0 fi echo "⏳ Waiting for service... (attempt $i/10)" sleep 10 done echo "❌ Health check failed" exit 1 - name: Rollback on failure if: failure() && steps.health.outcome == 'failure' run: | cd deployment/ansible ansible-playbook -i inventory/production.yml \ playbooks/rollback.yml - name: Notify deployment success if: success() run: | echo "🚀 Deployment successful!" echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" echo "Commit: ${{ needs.build.outputs.commit_sha }}" - name: Notify deployment failure if: failure() run: | echo "❌ Deployment failed and was rolled back" # TODO: Add Slack/Email notification