Custom CI/CD

GitHub Actions: Automatic Cleanup

Delete Zuplo environments automatically when branches are deleted. This keeps your environment list clean and avoids accumulating unused preview environments.

YAML.github/workflows/cleanup-on-branch-delete.yaml
name: Cleanup on Branch Delete on: 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: 24 - 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:

  1. Triggers when any branch is deleted
  2. Converts the branch name to the deployment-name branch segment format
  3. Looks up the matching deployment and deletes it by URL
  4. 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:

YAML.github/workflows/scheduled-cleanup.yaml
name: Scheduled Cleanup on: 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: 24 - 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

Next Steps

Last modified on