Why I Built My Own GCP Cleanup Tool Instead of Using Google's?

427 GCP projects. That's what I was staring at when I finally decided something had to change.

Since founding my company in 2014, I'd accumulated over a decade of cloud sprawl. Dev environments. Staging copies. Client demos. Experiments that never went anywhere. Projects named "My First Project" that nobody remembered creating. Sound familiar?

I knew this was a problem. The numbers are staggering:

But knowing and doing are different. What finally pushed me over the edge was realizing I had no idea which of those 427 projects were actually necessary.

Google has tools for this. I built my own instead. Here's why.

The Challenge: A Decade of Cloud Sprawl

How I Got Here

The sprawl didn't happen overnight. It crept up on me.

In 2014, GCP was my playground. Every new feature meant a new project. Every client demo got its own sandbox. We had no governance, no naming conventions, no cleanup process. By default, everyone in a Google Workspace domain gets Project Creator and Billing Account Creator roles. A governance problem hiding in plain sight.

Looking back, the warning signs were clear. If you see lots of "My First Project" or "My Project XXXXX" in your organization, you've got the same problem I had.

The Triple Threat: Cost, Security, Carbon

The damage wasn't just financial. It was a triple threat.

Cost: Zombie projects burning budget I couldn't track. Persistent disks attached to nothing. Unattached disks keep billing at full price even after the VM is gone. I had no visibility into what was actually necessary.

Security: Forgotten projects meant unpatched vulnerabilities. Compliance gaps. 90% of IT leaders manage hybrid cloud environments, and 35% cite securing data across ecosystems as their top challenge. I was part of that statistic.

Carbon: Every idle resource burns energy. Not the biggest concern, but it added up. Cleaning house meant reducing my environmental footprint too.

Why I Didn't Use Google's Tools

Google offers solutions for this exact problem. I evaluated them all. I didn't use them.

The Official Options

Google Remora is the obvious choice. It's a serverless solution that uses the Unattended Project Recommender to identify unused projects, notify owners, and delete after a configurable TTL. Dry-run mode by default. Three-wave notification escalation. Sounds perfect, right?

Here's the catch: Remora carries a disclaimer that it's "not an official Google project" and "not supported by Google." But even setting that aside, the setup is substantial. You need Cloud Scheduler, Cloud Workflows, service accounts with specific IAM bindings, and a dedicated project to run it all. The irony wasn't lost on me: creating another project to manage my project problem.

For small-to-medium portfolios, that overhead might be acceptable. For 400+ projects accumulated over a decade, with complex dependencies and shared resources, I wanted something simpler that I could understand completely.

DoiT SafeScrub solves a different problem entirely: cleaning resources within a single project. It's designed to wipe a dev environment clean at the end of the day. Great for that use case. But I didn't need to clean resources inside projects. I needed to identify which of my 400+ entire projects were obsolete and could be deleted safely. SafeScrub doesn't even look at the organization level. Different tool, different problem.

Cloud Asset Inventory is Google's native visibility tool, but visibility isn't cleanup. It only retains 35 days of history and requires you to build your own automation on top using Cloud Functions and BigQuery exports. It's a foundation, not a solution. At 427 projects, I needed something that would actually do the work, not just show me the problem.

I also evaluated community alternatives: gcp-cleaner (a self-described "non-comprehensive cleanup script"), gcloud-cleanup (Travis CI's internal tool for cleaning test instances), and bazooka (a gcloud wrapper for bulk deletions by name filter). All useful for their specific purposes, but none designed for organization-wide project lifecycle management.

What I Actually Needed

My requirements were specific:

  • Lightweight automation without enterprise complexity. I'm a small team, not a Fortune 500.

  • Two-tier classification to avoid accidentally flagging active projects. Binary delete/keep isn't nuanced enough when projects share IAM bindings, VPCs, or service accounts.

  • Human-in-the-loop approval before any deletions. GCP's 30-day recovery window is a safety net, but paranoia is healthy when you're deleting infrastructure.

  • Integration with existing workflows. I already had monitoring and alerting. I didn't want another disconnected tool.

  • Free and open source. I'd already spent enough on cloud bills.

  • Hesitation to share my credentials with 3rd party solutions. Mine worked with GCLOUD CLI’s existing login, and I kept the script open on Github for anyone to scan what its doing in a quick glimpse.

No existing tool checked all those boxes. So I built one.

Building the Solution

The Discovery Phase

I started with the Cloud Asset Inventory API. The key insight: instead of querying each service individually (which would take 4-8 API calls per project), I could get all resources in a single call per project. At 427 projects, that's the difference between 1,708-3,416 API calls and just 427.

The API's searchAllResources endpoint returns every resource in a project with one request:

gcloud asset search-all-resources --scope=projects/my-project-id --format=json

The response is a JSON array containing every resource in the project, regardless of service type. Here's what a typical response looks like (truncated for clarity):

[
  {
    "name": "//compute.googleapis.com/projects/my-project/zones/us-central1-a/instances/web-server",
    "assetType": "compute.googleapis.com/Instance",
    "updateTime": "2025-03-15T10:30:00Z",
    "createTime": "2024-01-10T08:00:00Z"
  },
  {
    "name": "//storage.googleapis.com/projects/_/buckets/my-backup-bucket",
    "assetType": "storage.googleapis.com/Bucket",
    "updateTime": "2024-06-01T14:00:00Z",
    "createTime": "2023-02-20T09:00:00Z"
  },
  {
    "name": "//sqladmin.googleapis.com/projects/my-project/instances/prod-db",
    "assetType": "sqladmin.googleapis.com/Instance",
    "updateTime": "2025-01-10T08:45:00Z",
    "createTime": "2022-11-15T16:30:00Z"
  }
]


My script iterates through all 427 projects in parallel (10 workers by default), makes this single call per project, and categorizes resources by their assetType:

  • Computecompute.googleapis.com/Instance, /Disk, /Snapshot, /Image

  • Storagestorage.googleapis.com/Bucket

  • Databasessqladmin.googleapis.com/Instance

  • Serverlessappengine.googleapis.com/Application, cloudfunctions.googleapis.com/CloudFunction

  • Other → Everything else the API returns

For each project, the script finds the most recent updateTime across all resources. That becomes the project's "last activity" date. The classification logic:

  • No resources at all → Obsolete (empty project)

  • 180+ days since last update → Obsolete (abandoned)

  • 90-180 days → Review required (potentially obsolete)

  • Project state not ACTIVE → Obsolete (already marked for deletion)

My prototype used Python and the gcloud CLI. Nothing fancy. The goal was understanding what existed before taking any action.

Key Implementation Decisions

The architecture was deliberately simple: a CLI tool that runs locally. No Cloud Functions. No Cloud Scheduler. Just Python and the APIs I already had access to.

  • Dry-run by default. JSON reports of what would be deleted. No deletions until you confirm.

  • Auto-resume. Interrupted scans pick up where they left off.

  • Parallel scanning. 10 worker threads by default.

The output is two JSON files: obsolete_projects_report.json with the full categorized analysis, and projects_for_deletion.json with the deletion-ready candidates. Review them. Edit them. Then run the delete phase only on what you've approved.

# The core classification logic
def classify_project(project, last_activity_days):
    if last_activity_days > 180:
        return "safe_to_delete"
    elif last_activity_days > 90:
        return "review_required"
    return "keep"

Results and Impact

Gmail Screenshot after deleting 393 GCP Projects

You might be bombarded with emails from Google after running the final deletion script

Some Stats

  • 393 zombie projects identified and safely removed

  • Cost reduction: Decrease in monthly GCP spend

  • Security: 12 projects with deprecated APIs discovered and addressed

  • Time savings: What would have taken weeks of manual audit happened automatically

The ROI math is simple. Systematic zombie elimination can achieve 20-64% cost reductions with 300-500% ROI in the first year. I'm at the lower end of that range, but I'll take it.

Unexpected Benefits

Beyond the obvious metrics, some surprises:

Organizational visibility. For the first time, I actually knew what we were running. Every project had a purpose or got deleted.

Compliance confidence. Auditors love documentation. Now I had an audit trail for project lifecycle.

Team clarity. Owners assigned. Responsibility clear. No more "I thought someone else was handling that" conversations.

Knowledge preservation. Documenting why each project existed forced conversations that should have happened years ago.

Lessons Learned: What Worked and What Didn't

What Worked

Start small. Testing on non-production first caught edge cases that would have caused real damage.

Two-tier classification. The 90-180 day "review required" bucket saved me from deleting several projects I would have regretted.

Manual approval gates. Fully automated deletion sounds efficient. It's also terrifying. Human review saved me twice.

Weekly cadence. Monthly was too slow. Weekly scans catch drift before it becomes sprawl.

What I'd Do Differently

Enforce tagging from day one. Labels indicating owner, environment, and purpose would have made cleanup trivial. Retrofitting tags to 400+ projects was painful.

Set per-project budgets earlier. Cost alerts at the project level, not just the billing account. Would have caught zombie projects years sooner.

Document project purpose on creation. A simple README requirement for every new project. Future you will thank present you.

Establish org policies sooner. Disable project creation at the org level except for specific roles. Prevention beats cleanup every time.

FAQs

Is this safe for production environments?

Yes, with proper testing. The two-tier classification and dry-run mode are non-negotiable. Always review the "needs review" category manually, and never run automated deletion without understanding what will be affected.

How long does cleanup take?

Initial audit took about three weeks of part-time work. Ongoing maintenance is maybe two hours per month reviewing reports and approving deletions.

Can I use this with fewer than 400 projects?

Absolutely. Even 10-20 projects benefit from automation. The principles scale down. You don't need enterprise-grade chaos to justify better governance.

What about Google Cloud Remora?

Great tool for smaller portfolios. If you're under 50 projects and don't have complex dependencies, try Remora first. My situation required something more customized.

Try it yourself (safely)

The tool is available on GitHub. Star it if it's useful. Open an issue if something doesn't work. I built this for my needs, but maybe it helps with yours.

You don't need 400 projects to benefit from better cloud governance. The best time to start was ten years ago. The second best time is now.

What's your project cleanup strategy? Drop a comment.

References

Official Documentation:

Cloud Governance:

Cost Optimization:

Community Tools:

Abhinav Gupta

1st Indian Salesforce MVP, rewarded 8 times in a row, has been blogging about Salesforce, Cloud, AI, & Web3 since 2011.

Founded India’s 1st Salesforce Dreamin event in India, called “Jaipur Dev Fest”. A seasoned speaker at Dreamforce, Dreamin events, & local meets. Author of many popular GitHub repos featured in official Salesforce blogs, newsletters, and books.

Next
Next

When to Replace SFMC, & When to Fix Your Implementation?