This check flags GCP Storage buckets with no lifecycle rules, which means old objects, stale versions, and abandoned multipart uploads pile up forever and quietly inflate your bill. Fix it by adding a lifecycle policy that deletes or transitions objects after a set age.
Cloud storage is cheap until it isn't. A bucket that accumulates years of logs, build artifacts, temporary exports, and forgotten object versions can grow into a real cost line item without anyone noticing. The storage_nolifecycle check looks at every Google Cloud Storage bucket in your project and reports the ones that have no lifecycle management rules configured.
This is one of those findings that rarely triggers an incident but steadily drains money and increases your data footprint. More data sitting around also means more to protect, more to audit, and more to leak if the bucket is ever exposed.
What this check detects
The check inspects the lifecycle configuration of each Cloud Storage bucket. If the lifecycle ruleset is empty, the bucket is flagged.
Lifecycle management in GCS lets you define rules that automatically act on objects when they meet certain conditions. Two actions are supported:
- Delete — permanently removes objects that match a condition (age, version count, creation date, and so on).
- SetStorageClass — transitions objects to a cheaper storage class, for example from Standard to Nearline, Coldline, or Archive.
A bucket with no rules at all keeps every object indefinitely, at the storage class it was written with, until someone deletes it by hand.
Note: Lifecycle rules in GCS are evaluated asynchronously. Google runs the evaluation roughly once per day per bucket, so a newly added rule may take up to 24 hours to act on existing objects. Do not expect instant deletions.
Why it matters
Runaway storage costs
This is the most common impact. Buckets used for logs, CI artifacts, database exports, or analytics intermediates often write far more than they ever read. Without a lifecycle rule, that data lives forever. A pipeline writing a few gigabytes per day adds up to terabytes over a year, and you keep paying for all of it.
The cost gets worse when object versioning is enabled. Every overwrite creates a noncurrent version that also counts toward your bill. A bucket with versioning and no lifecycle rule to prune old versions can store many copies of the same logical object.
A larger attack and compliance surface
Data you no longer need is pure liability. If a bucket is accidentally made public, or an access key leaks, the blast radius includes every old export and log that should have been deleted years ago. Holding personal data longer than necessary also runs against retention requirements in GDPR, CCPA, and most internal data governance policies.
Warning: Lifecycle rules are not a security control on their own. A delete rule reduces how long sensitive data lingers, but it does not stop access while the data exists. Pair retention with proper IAM and public-access prevention.
Operational drag
Buckets with millions of stale objects are slow and expensive to list, scan, or migrate. When you eventually need to move providers, run a security scan, or audit contents, every extra object costs time and API calls.
How to fix it
You configure lifecycle rules at the bucket level. Start by deciding what each bucket actually holds and how long that data is useful, then apply a rule that matches.
Step 1: Inspect the current configuration
gcloud storage buckets describe gs://my-bucket --format="json(lifecycle_config)"
If lifecycle is null or empty, the bucket has no rules.
Step 2: Write a lifecycle policy
Create a JSON file describing the rules. This example deletes objects older than 90 days and moves anything older than 30 days to Nearline first:
{
"rule": [
{
"action": { "type": "SetStorageClass", "storageClass": "NEARLINE" },
"condition": { "age": 30, "matchesStorageClass": ["STANDARD"] }
},
{
"action": { "type": "Delete" },
"condition": { "age": 90 }
}
]
}
If you use versioning, add a rule to clean up noncurrent versions so old copies do not accumulate:
{
"rule": [
{
"action": { "type": "Delete" },
"condition": { "daysSinceNoncurrentTime": 30, "numNewerVersions": 3 }
}
]
}
Step 3: Apply the policy
Danger: A delete rule is destructive and irreversible. Once a lifecycle rule deletes an object, it is gone. Validate the age and condition values against a non-production bucket first, and confirm no live workload depends on objects in the deletion window.
gcloud storage buckets update gs://my-bucket --lifecycle-file=lifecycle.json
Verify it applied:
gcloud storage buckets describe gs://my-bucket --format="json(lifecycle_config)"
Console steps
- Open Cloud Storage in the Google Cloud Console.
- Click the bucket name, then the Lifecycle tab.
- Click Add a rule, choose an action (Delete or Set storage class), and set a condition such as age.
- Save. The rule begins applying within roughly a day.
Tip: Use matchesPrefix and matchesSuffix conditions to scope rules to specific paths. For example, delete logs/ objects after 30 days while keeping exports/ for a year, all in the same bucket.
How to prevent it from happening again
Manual fixes do not scale. Bake lifecycle policies into the way buckets are created so no bucket ships without one.
Terraform
Define the lifecycle rule directly on the bucket resource so it is impossible to provision a bucket without one:
resource "google_storage_bucket" "logs" {
name = "my-app-logs"
location = "US"
storage_class = "STANDARD"
lifecycle_rule {
condition {
age = 30
}
action {
type = "SetStorageClass"
storage_class = "NEARLINE"
}
}
lifecycle_rule {
condition {
age = 365
}
action {
type = "Delete"
}
}
}
Policy-as-code in CI
Add a gate that rejects any Terraform plan introducing a google_storage_bucket without a lifecycle_rule. With Conftest and OPA:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_storage_bucket"
not resource.change.after.lifecycle_rule
msg := sprintf("Bucket '%s' has no lifecycle rule", [resource.address])
}
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json --policy ./policy
Tip: If you want this enforced at the organization level rather than per pipeline, write a Terraform module that all teams must use for buckets, with a sensible default lifecycle rule that callers can override but not remove.
Continuous detection
Even with good guardrails, buckets created by hand, by third-party tools, or before your policy existed will slip through. Run the storage_nolifecycle check on a schedule in Lensix so any new offender is flagged the day it appears rather than the day your bill arrives.
Best practices
- Match retention to purpose. Logs, temp data, and CI artifacts usually need days to weeks. Compliance records may need years. Set the
ageper bucket, not with a single org-wide number. - Tier before you delete. If you must keep data but rarely read it, transition to Coldline or Archive instead of paying Standard rates. Watch retrieval and early-deletion fees on colder classes.
- Prune noncurrent versions. Any bucket with versioning enabled should have a rule limiting how many old versions and how long they live.
- Use Object Versioning or bucket lock where data must not be deleted early. Lifecycle rules and retention policies serve different goals. Do not rely on a delete rule where regulators require immutability.
- Document the why. A 90-day delete rule with no context confuses the next engineer. Record the retention reasoning in your IaC comments or a data catalog.
Warning: Transitioning objects to Nearline, Coldline, or Archive incurs minimum storage durations. Deleting or rewriting an object before that minimum (30, 90, or 365 days) still bills you for the full period. Only tier data you genuinely intend to leave in place.
Lifecycle rules are a low-effort, high-return setting. Adding one takes a few minutes, and it pays back continuously by trimming cost and shrinking the amount of data you have to defend.

