---
tags:
  - GIT
  - CI/CD
  - CI-CD
---
If your `docker compose up -d` command encounters broken network routing, bad volume mappings, or unparseable image tags, the deployment step will fail. By utilizing standard shell behaviors and Gitea's `if: failure()` conditions, you can instruct your runner to automatically execute a fallback deployment command using the previous stable version.

1. Setup the Backup Directory

To safely fall back to a working state, the runner needs access to the _previous_ commit's configuration. The cleanest way to do this without messing up your live directories is to copy your files into a temporary backup workspace on the live machine before running the fresh upgrade.

2. The Complete Automated Rollback Workflow File

Here is the deployment and rollback code pattern. Replace your existing deployment job block with this comprehensive configuration:

  deploy-with-rollback:
    runs-on: ubuntu-latest
    needs: build-and-push # Ensures we only deploy if the container image successfully builds
    
    steps:
      - name: Checkout Repository Code
        uses: actions/checkout@v4

      - name: Set up SSH Routing
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts

      - name: Create Remote Docker Context
        run: |
          docker context create homelab-target \
            --docker "host=ssh://${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}"
          docker context use homelab-target

      # --- STEP A: BACKUP PREVIOUS LIVE ENVIRONMENT STATE ---
      - name: Stash Current Working Configuration
        run: |
          # Use SSH to quickly copy the current running directory configuration to a backup spot
          ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
            "mkdir -p /tmp/homelab_backup && cp -r ~/homelab_stack/* /tmp/homelab_backup/ || true"

      # --- STEP B: FRESH ATTEMPTED DEPLOYMENT ---
      - name: Sync New Config Files and Deploy
        id: deploy_attempt
        run: |
          # Copy your freshly fetched git workspace configs to your server deployment path
          scp -r ./* ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:~/homelab_stack/
          
          # Attempt the live container stack upgrade
          docker compose --project-directory ~/homelab_stack up -d --remove-orphans

      # --- STEP C: AUTOMATIC FALLBACK ROLLBACK (Triggers only if Step B crashes) ---
      - name: Execute Emergency Rollback
        if: failure() && steps.deploy_attempt.outcome == 'failure'
        run: |
          echo "⚠️ Deployment crashed! Restoring last known good stack..."
          
          # Restore the stashed backup file configuration over the broken files
          ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
            "cp -r /tmp/homelab_backup/* ~/homelab_stack/ && rm -rf /tmp/homelab_backup"
            
          # Rerun Docker Compose using the restored working configurations
          docker compose --project-directory ~/homelab_stack up -d --remove-orphans
          echo "✅ Rollback completed successfully."


🧠 Advanced Breakdown for Homelabs

- **`steps.deploy_attempt.outcome == 'failure'`**: This condition specifically targets the core deployment run. If your build or your SSH setups break, the rollback logic ignores it. It _only_ swings into action if the `docker compose up` execution itself returns an error code on your target server.
- **Why Separate Config & Images?**: Because you are using the Gitea Container Registry, images are tagged with the specific commit hash (`:${{ gitea.sha }}`). If you need to roll back, moving back to the old `docker-compose.yml` config will point Docker back to the _previous_ commit's image tag. This safely pulls and starts the old container version even if you pushed a broken image to `latest`.