Delete Zuplo environments automatically when branches are deleted. This keeps
your environment list clean and avoids accumulating unused preview environments.
.github/workflows/cleanup-on-branch-delete.yaml
name: Cleanup on Branch Deleteon: delete:jobs: cleanup: # Only run for branch deletions, not tag deletions if: github.event.ref_type == 'branch' runs-on: ubuntu-latest env: ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm install - name: Delete environment run: | # The deleted branch name BRANCH_NAME="${{ github.event.ref }}" # Deployment names use the format {project}-{branch}-{hash}, where # the branch segment is normalized (lowercase, special characters # replaced by hyphens) and truncated to 10 characters ENV_NAME=$(echo "$BRANCH_NAME" | tr '/_' '--' | tr '[:upper:]' '[:lower:]' | cut -c1-10 | sed 's/-*$//') # Find the deployment for the deleted branch and delete it by URL npx zuplo list --api-key "$ZUPLO_API_KEY" \ --output json --show-details | jq -r --arg env "$ENV_NAME" \ '.[] | select(.name | test("-" + $env + "-[a-z0-9]+$")) | .url' | while read -r URL; do echo "Deleting environment: $URL" npx zuplo delete --url "$URL" --api-key "$ZUPLO_API_KEY" --wait || true done
This workflow:
Triggers when any branch is deleted
Converts the branch name to the deployment-name branch segment format
Looks up the matching deployment and deletes it by URL
Continues without error if no matching environment exists
Combining with PR Cleanup
Use this as a backup for
PR preview environments. The PR workflow
handles cleanup when PRs close, but this catches cases where:
Someone deletes a branch without closing the PR first
A branch was pushed but never had a PR opened
The PR cleanup job failed
Scheduled Cleanup
For additional safety, run periodic cleanup to catch any orphaned environments:
.github/workflows/scheduled-cleanup.yaml
name: Scheduled Cleanupon: schedule: # Run daily at midnight UTC - cron: "0 0 * * *"jobs: cleanup: runs-on: ubuntu-latest env: ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all branches - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm install - name: Cleanup stale environments run: | # Get all remote branches, converted to the deployment-name branch # segment format: lowercase, special characters replaced by hyphens, # truncated to 10 characters BRANCHES=$(git branch -r | sed 's|origin/||' | tr -d ' ' | tr '/_' '--' | tr '[:upper:]' '[:lower:]' | cut -c1-10 | sed 's/-*$//') # List deployed environments as JSON. Each entry has the shape # {"projectName": "...", "name": "...", "url": "..."} npx zuplo list --api-key "$ZUPLO_API_KEY" \ --output json --show-details > environments.json # Deployment names follow the format {project}-{branch}-{hash} jq -r '.[] | "\(.name) \(.url)"' environments.json | while read -r NAME URL; do # Skip protected environments case "$NAME" in *-main-* | *-production-* | *-staging-*) continue ;; esac # Delete only if no branch matches the deployment name STALE=true for BRANCH in $BRANCHES; do if [[ "$NAME" == *"-$BRANCH-"* ]]; then STALE=false break fi done if [[ "$STALE" == "true" ]]; then echo "Deleting stale environment: $NAME" npx zuplo delete --url "$URL" --api-key "$ZUPLO_API_KEY" --wait || true fi done