Automatically finds and cleans up ZAD deployments for closed or merged PRs. Intended to run on a schedule (e.g., weekly) to catch environments that were missed by the normal PR-close cleanup.
| Name | Required | Default | Description |
|---|---|---|---|
api-key |
Yes | - | ZAD API key (ZAD_API_KEY secret) |
project-id |
Yes | - | ZAD project identifier |
environment-pattern |
No | ^pr-?[0-9]+$ |
Regex pattern to match PR environments |
pr-number-pattern |
No | s/^pr-\{0,1\}// |
Sed expression to extract PR number from environment name |
max-age-days |
No | 0 |
Also cleanup environments older than N days regardless of PR state (0 to disable) |
dry-run |
No | false |
List stale environments without deleting |
delete-github-env |
No | true |
Delete the GitHub environment |
delete-github-deployments |
No | true |
Delete GitHub deployments for each environment |
delete-container |
No | false |
Delete the container image from GHCR |
container-org |
No | '' |
Organization owning the container |
container-name |
No | '' |
Container package name |
github-token |
No | github.token |
GitHub token for API operations |
github-admin-token |
No | '' |
GitHub token for environment deletion (needs repo admin permission) |
api-base-url |
No | https://operations-manager.rig.prd1.gn2.quattro.rijksapps.nl/api |
ZAD Operations Manager API base URL |
max-retries |
No | 3 |
Maximum number of retries for transient ZAD API errors (0 to disable) |
retry-delay |
No | 2 |
Initial retry delay in seconds (doubles each retry) |
| Name | Description |
|---|---|
stale-count |
Number of stale environments found |
cleaned-count |
Number of environments successfully cleaned (always 0 when skipped or no stale envs) |
stale-environments |
JSON array of stale environment names |
name: Cleanup Stale Preview Environments
on:
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 02:00
workflow_dispatch:
# Prevent concurrent cleanup runs
concurrency:
group: scheduled-cleanup
cancel-in-progress: false
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
deployments: write
packages: write
pull-requests: read
steps:
- uses: RijksICTGilde/zad-actions/scheduled-cleanup@v2
with:
api-key: ${{ secrets.ZAD_API_KEY }}
project-id: my-project
environment-pattern: '^pr-?[0-9]+$'
delete-container: true
container-org: my-org
container-name: my-app
github-admin-token: ${{ secrets.GITHUB_ADMIN_TOKEN }}Test what would be cleaned up without actually deleting anything:
- uses: RijksICTGilde/zad-actions/scheduled-cleanup@v2
with:
api-key: ${{ secrets.ZAD_API_KEY }}
project-id: my-project
dry-run: trueClean up environments older than 30 days, even if the PR is still open:
- uses: RijksICTGilde/zad-actions/scheduled-cleanup@v2
with:
api-key: ${{ secrets.ZAD_API_KEY }}
project-id: my-project
max-age-days: 30If your environments use a different naming convention (e.g., preview-123):
- uses: RijksICTGilde/zad-actions/scheduled-cleanup@v2
with:
api-key: ${{ secrets.ZAD_API_KEY }}
project-id: my-project
environment-pattern: '^preview-[0-9]+$'
pr-number-pattern: 's/^preview-//'- Scan: Fetches all GitHub environments for the repository (paginated)
- Filter: Matches environment names against
environment-pattern - Check: For each match, extracts the PR number and checks if the PR is still open
- Age check: If
max-age-daysis set, also marks environments older than N days as stale - Cleanup: For each stale environment (unless
dry-run):- Deletes the ZAD deployment (with retry on transient errors)
- Deletes GitHub deployments (marks inactive, then deletes)
- Deletes the GitHub environment (if
github-admin-tokenprovided) - Deletes the container image (if
delete-containerenabled)
Only the ZAD API delete call is retried on transient errors. GitHub API calls (deployments, environments, containers) are not retried — they use best-effort error handling.
| HTTP Code | Retries? | Reason |
|---|---|---|
| 000 | Yes | Network error / timeout |
| 429 | Yes | Rate limit |
| 500-504 | Yes | Server errors |
| 401, 403 | No | Authentication / authorization |
| 404 | No | Already deleted |
Backoff is exponential: 2s -> 4s -> 8s (default). Set max-retries: '0' to disable.
Use a concurrency group in your workflow to prevent overlapping cleanup runs (e.g., if a manual trigger overlaps with a scheduled run):
concurrency:
group: scheduled-cleanup
cancel-in-progress: falsepermissions:
deployments: write # For deleting GitHub deployments
packages: write # For deleting container images
pull-requests: read # For checking PR stateFor environment deletion, a github-admin-token with repo admin access is required (the default GITHUB_TOKEN cannot delete environments).