DEV Community

Cover image for Solved: what’s your process for tracking leftover resources after a project ends?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: what’s your process for tracking leftover resources after a project ends?

🚀 Executive Summary

TL;DR: Unmanaged cloud resources after project completion lead to significant cost overruns, security vulnerabilities, and operational clutter. Proactive solutions involve enforcing mandatory tagging policies, implementing automated janitor scripts for scheduled scans, and leveraging Infrastructure as Code (IaC) for comprehensive “cradle to grave” resource lifecycle management.

🎯 Key Takeaways

  • Mandatory tagging policies, enforced via cloud provider policy-as-code (e.g., AWS SCP, Azure Policy), prevent resource creation without essential tags like project-id, owner, and ttl, embodying the “No Tag, No Launch” philosophy.
  • Automated janitor scripts and tools like Cloud Custodian provide a robust reactive layer, scanning for and reporting on untagged, unattached, or expired resources on a schedule to prevent them from becoming “digital detritus.”
  • Infrastructure as Code (IaC) tools (Terraform, CloudFormation) enable “Cradle to Grave” management, allowing deterministic creation and destruction of all project infrastructure via code, significantly reducing manual effort and risk of error.

Discover proactive and automated strategies for managing cloud resource lifecycle to prevent cost overruns and security risks. This guide details solutions using tagging policies, cleanup scripts, and Infrastructure as Code (IaC) to effectively track and decommission leftover assets after a project concludes.

Symptoms: The Digital Graveyard

When a project wraps up, the development team moves on, but the infrastructure often remains. This digital detritus leads to several critical problems that can haunt an organization long after the project’s ‘go-live’ party has ended.

  • Cloud Bill Shock: The most immediate symptom. Orphaned EC2 instances, unattached EBS volumes, forgotten S3 buckets, and idle RDS databases quietly accumulate charges, leading to significant and unexpected cost overruns. A single high-memory instance left running for a few months can cost thousands of dollars.
  • Security Posture Drift: Unmanaged resources are unpatched resources. An abandoned virtual machine with a public IP is a ticking time bomb, vulnerable to exploits. Forgotten security groups with overly permissive rules (0.0.0.0/0) create permanent backdoors into your network.
  • Operational Clutter: A cluttered cloud account makes management, auditing, and troubleshooting exponentially more difficult. When a real incident occurs, sifting through hundreds of irrelevant, zombie resources to find the root cause wastes precious time.

Solution 1: Proactive Control with Mandatory Tagging Policies

The most effective strategy is to prevent resources from becoming orphaned in the first place. This is achieved by establishing and enforcing a rigorous tagging policy. The core principle is simple: if a resource cannot be tied to a project, owner, or purpose, it shouldn’t exist.

The “No Tag, No Launch” Philosophy

This approach uses the cloud provider’s native policy-as-code services to enforce tagging at the moment of resource creation. If a user or process attempts to create a resource without the required tags, the API call is denied.

Essential Tags for Lifecycle Management

Your policy should mandate a minimum set of tags on all taggable resources. Consider these essential:

  • project-id: A unique identifier for the project or cost center.
  • owner: The email or user ID of the person responsible.
  • environment: (e.g., prod, staging, dev, sandbox).
  • creation-date: An ISO 8601 timestamp of when it was created.
  • ttl (Time To Live): A date after which the resource should be considered for automatic deletion, especially useful for temporary dev/test resources.

Implementation Examples

Here’s how to enforce a policy that requires the project-id tag on new resources.

AWS: Using AWS Config Rules

You can create a custom AWS Config rule to check if EC2 instances have the required tags. While AWS Config is for detection (not prevention), you can pair it with automated remediation via Lambda. For prevention, a Service Control Policy (SCP) at the AWS Organizations level is more powerful.

Here is an SCP that denies EC2 instance creation if the project-id tag is missing:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyEC2CreationWithoutProjectTag",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "Null": {
          "aws:RequestTag/project-id": "true"
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Azure: Using Azure Policy

Azure Policy is the go-to service for enforcing standards. You can assign a built-in policy or create a custom one to require a tag on a resource group or specific resources.

This policy definition denies the creation of any resource that does not have a project-id tag:

{
  "mode": "Indexed",
  "policyRule": {
    "if": {
      "field": "[concat('tags[', 'project-id', ']')]",
      "exists": "false"
    },
    "then": {
      "effect": "deny"
    }
  },
  "parameters": {}
}
Enter fullscreen mode Exit fullscreen mode

Solution 2: Automated Janitors and Scheduled Scans

Even with strict policies, resources can slip through the cracks. An automated, scheduled process to scan for and report on non-compliant or stale resources is a vital second layer of defense.

The Reactive-but-Robust Approach

This involves running a script or tool on a schedule (e.g., nightly via a cron job or a Lambda/Function App) that queries the cloud provider’s API. It hunts for resources that are untagged, unattached, or have an expired ttl tag.

Example: A Python Boto3 Script for Untagged AWS Resources

This Python script uses the AWS SDK (Boto3) to find EC2 instances in a specific region that are missing the project-id tag. In a real-world scenario, you would expand this to notify an owner, add it to a deletion queue, or terminate it directly.

import boto3

def find_untagged_instances(region):
    ec2 = boto3.client('ec2', region_name=region)
    untagged_instances = []

    response = ec2.describe_instances(
        Filters=[
            {
                'Name': 'instance-state-name',
                'Values': ['pending', 'running', 'stopping', 'stopped']
            }
        ]
    )

    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            instance_id = instance['InstanceId']
            tags = instance.get('Tags', [])

            # Check if the 'project-id' tag exists
            if not any(tag['Key'] == 'project-id' for tag in tags):
                untagged_instances.append(instance_id)

    return untagged_instances

if __name__ == "__main__":
    aws_region = 'us-east-1'
    violators = find_untagged_instances(aws_region)
    if violators:
        print(f"Found untagged EC2 instances in {aws_region}:")
        for instance_id in violators:
            print(f"- {instance_id}")
    else:
        print(f"No untagged EC2 instances found in {aws_region}.")
Enter fullscreen mode Exit fullscreen mode

Leveling Up with Open Source: Cloud Custodian

Writing and maintaining custom scripts for every resource type is tedious. Tools like Cloud Custodian (an open-source CNCF project) allow you to define policies in simple YAML to find and act on non-compliant resources across multiple cloud providers.

This Cloud Custodian policy finds all S3 buckets that are missing the project-id tag and marks them for deletion in 7 days, notifying a Slack channel.

policies:
  - name: s3-mark-untagged-for-deletion
    resource: s3
    comment: |
      Find any S3 bucket that does not have the 'project-id' tag.
      Mark it for deletion in 7 days and notify the security team.
    filters:
      - "tag:project-id": absent
    actions:
      - type: mark-for-op
        op: delete
        days: 7
      - type: notify
        template: default.html
        to:
          - slack://webhook-url-here
        transport:
          type: sqs
          queue: https://sqs.us-east-1.amazonaws.com/123456789012/cloud-custodian-mailer
Enter fullscreen mode Exit fullscreen mode

Solution 3: Declarative Destruction with Infrastructure as Code (IaC)

The most mature approach treats infrastructure as ephemeral. Resources are not pets to be cared for; they are cattle, managed as a herd. Infrastructure as Code (IaC) tools like Terraform, CloudFormation, Bicep, or Pulumi are the key enablers of this model.

The “Cradle to Grave” Management Model

When all infrastructure for a project is defined in code and stored in a version control system (like Git), the entire lifecycle is managed declaratively.

  • Creation: terraform apply or aws cloudformation deploy.
  • Modification: Change the code, commit, and re-apply.
  • Destruction: When the project ends, the cleanup is a single, deterministic command.

The command terraform destroy reads the state file and systematically destroys every resource it manages, in the correct dependency order. This is infinitely more reliable and comprehensive than manual clicking or ad-hoc scripts.

Comparing Resource Cleanup Approaches

This table contrasts the different methods for managing resource cleanup.

Approach Reliability Manual Effort Risk of Error
Manual Console Cleanup Low High Very High (missed resources, wrong deletions)
Automated Scanning & Scripts Medium Medium (script maintenance) Medium (script bugs, logic errors)
IaC Lifecycle Management High Low (after initial setup) Low (deterministic, state-managed)

Handling “Out-of-Band” Resources

The reality is that even in a mature IaC environment, some resources may be created manually for quick tests or debugging. This is where the solutions combine. Your IaC workflow should be your primary method, while the mandatory tagging policies (Solution 1) and automated janitor scripts (Solution 2) act as a safety net to catch these “out-of-band” resources that were not provisioned via the standard, code-driven process.


Darian Vance

👉 Read the original article on TechResolve.blog


Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)