A lightweight Python application that scans Kubernetes namespaces, marks empty ones, and deletes them if they stay empty. Run it in whichever context fits your workflow:
- as a Kubernetes CronJob (manifests available in
kustomize/) - as a standalone command-line job (e.g., from your laptop or CI runner)
It is the successor to remove-empty-ns-operator, a Python/kopf operator that solved the same problem. That project started as an operator-development experiment, but periodic namespace cleanup turned out to fit an app + CronJob better than a continuously reconciling controller — so the operator is no longer developed and this app takes its place.
- Discover all namespaced API resources that allow
list. - Filter namespaces, API resources, and concrete objects via regex-based allow/deny lists.
- Optionally apply a "double check" label so a namespace must remain empty for two consecutive runs.
- Delete or skip namespaces based on the configured rules and
dryRunflag.
-
Adjust
kustomize/settings.yamlto match your environment (namespaces, resource filters, age thresholds, etc.). -
(Optional) Edit
kustomize/base/cronjob.yamlto tune schedule, image, or pod resources. -
Apply the manifests:
kubectl apply -k kustomize/
This creates the
remove-empty-nsnamespace, a ConfigMap with your settings, and a CronJob that uses the published container image. -
Update the ConfigMap (
kustomize/settings.yaml) and re-apply to roll out new rules. The next CronJob run picks them up automatically.
-
Install the required tools and dependencies with mise:
mise install mise run venv
-
Provide a settings file (you can reuse
kustomize/settings.yamlor craft your own). -
Run the app:
poetry run -- python ./src/app.py -c kustomize/settings.yaml
- The app uses in-cluster credentials when running inside Kubernetes; otherwise it falls back to your local
~/.kube/config. - Use
-v/-vvfor more verbose logging. - Use
-qto suppress all logging output.
- The app uses in-cluster credentials when running inside Kubernetes; otherwise it falls back to your local
Configuration is plain YAML. See kustomize/settings.yaml for a complete working example. Key options:
namespacesConsidered/namespacesIgnored: regex lists for target namespaces.namespacesIgnoredalways runs first; ifnamespacesConsideredis empty, every namespace that survived the ignore step is eligible.ageGreaterThan: minimum namespace age (seconds) before it is even inspected.doubleCheck.enabledanddoubleCheck.label: add/remove the specified label to ensure a namespace is still empty on the next run before deleting it.resourcesConsidered/resourcesIgnored: regex triplets (apiGroup,kind,name) to force consideration or exclusion of specific objects. Objects are checked againstresourcesIgnoredfirst, thenresourcesConsidered; ignored matches never count as present even if also listed as considered.apiResourcesConsidered/apiResourcesIgnored: regex pairs (apiGroup,kind) to whitelist/blacklist entire API kinds before listing objects.apiResourcesIgnoredtrims the universe first, thenapiResourcesConsideredoptionally whitelists the remainder.dryRun: whentrue, nothing is deleted; the app only logs what it would do.
Tuning these lists lets you focus on particular workloads (for example, only clean up preview environments) while ignoring shared infra components.
- Install toolchain with
mise install. - Create the virtual environment:
mise run venv. - Install git hooks:
pre-commit install. - Explore helper commands via
mise tasks. - Run unit tests (outdated but kept for reference) with
mise run test.