Configure GCP Bigtable Garbage Collection Policies

The gcp:bigtable/gCPolicy:GCPolicy resource, part of the Pulumi GCP provider, defines garbage collection policies for Bigtable column families, controlling when cells are automatically deleted based on age or version count. This guide focuses on three capabilities: age-based deletion, version-based deletion, and union/intersection logic for multi-condition policies.

GC policies attach to existing column families within Bigtable tables. The instance, table, and column family must exist before creating the policy. The examples are intentionally small. Combine them with your own table schemas and retention requirements.

Delete cells older than a specific age

Bigtable tables accumulate historical data over time. Teams often need to automatically remove cells that exceed a retention window to control storage costs and comply with data retention policies.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const instance = new gcp.bigtable.Instance("instance", {
    name: "tf-instance",
    clusters: [{
        clusterId: "tf-instance-cluster",
        numNodes: 3,
        storageType: "HDD",
    }],
});
const table = new gcp.bigtable.Table("table", {
    name: "tf-table",
    instanceName: instance.name,
    columnFamilies: [{
        family: "name",
    }],
});
const policy = new gcp.bigtable.GCPolicy("policy", {
    instanceName: instance.name,
    table: table.name,
    columnFamily: "name",
    deletionPolicy: "ABANDON",
    gcRules: `  {
    \\"rules\\": [
      {
        \\"max_age\\": \\"168h\\"
      }
    ]
  }
`,
});
import pulumi
import pulumi_gcp as gcp

instance = gcp.bigtable.Instance("instance",
    name="tf-instance",
    clusters=[{
        "cluster_id": "tf-instance-cluster",
        "num_nodes": 3,
        "storage_type": "HDD",
    }])
table = gcp.bigtable.Table("table",
    name="tf-table",
    instance_name=instance.name,
    column_families=[{
        "family": "name",
    }])
policy = gcp.bigtable.GCPolicy("policy",
    instance_name=instance.name,
    table=table.name,
    column_family="name",
    deletion_policy="ABANDON",
    gc_rules="""  {
    \"rules\": [
      {
        \"max_age\": \"168h\"
      }
    ]
  }
""")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/bigtable"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		instance, err := bigtable.NewInstance(ctx, "instance", &bigtable.InstanceArgs{
			Name: pulumi.String("tf-instance"),
			Clusters: bigtable.InstanceClusterArray{
				&bigtable.InstanceClusterArgs{
					ClusterId:   pulumi.String("tf-instance-cluster"),
					NumNodes:    pulumi.Int(3),
					StorageType: pulumi.String("HDD"),
				},
			},
		})
		if err != nil {
			return err
		}
		table, err := bigtable.NewTable(ctx, "table", &bigtable.TableArgs{
			Name:         pulumi.String("tf-table"),
			InstanceName: instance.Name,
			ColumnFamilies: bigtable.TableColumnFamilyArray{
				&bigtable.TableColumnFamilyArgs{
					Family: pulumi.String("name"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = bigtable.NewGCPolicy(ctx, "policy", &bigtable.GCPolicyArgs{
			InstanceName:   instance.Name,
			Table:          table.Name,
			ColumnFamily:   pulumi.String("name"),
			DeletionPolicy: pulumi.String("ABANDON"),
			GcRules: pulumi.String(`  {
    \"rules\": [
      {
        \"max_age\": \"168h\"
      }
    ]
  }
`),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var instance = new Gcp.BigTable.Instance("instance", new()
    {
        Name = "tf-instance",
        Clusters = new[]
        {
            new Gcp.BigTable.Inputs.InstanceClusterArgs
            {
                ClusterId = "tf-instance-cluster",
                NumNodes = 3,
                StorageType = "HDD",
            },
        },
    });

    var table = new Gcp.BigTable.Table("table", new()
    {
        Name = "tf-table",
        InstanceName = instance.Name,
        ColumnFamilies = new[]
        {
            new Gcp.BigTable.Inputs.TableColumnFamilyArgs
            {
                Family = "name",
            },
        },
    });

    var policy = new Gcp.BigTable.GCPolicy("policy", new()
    {
        InstanceName = instance.Name,
        Table = table.Name,
        ColumnFamily = "name",
        DeletionPolicy = "ABANDON",
        GcRules = @"  {
    \""rules\"": [
      {
        \""max_age\"": \""168h\""
      }
    ]
  }
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.bigtable.Instance;
import com.pulumi.gcp.bigtable.InstanceArgs;
import com.pulumi.gcp.bigtable.inputs.InstanceClusterArgs;
import com.pulumi.gcp.bigtable.Table;
import com.pulumi.gcp.bigtable.TableArgs;
import com.pulumi.gcp.bigtable.inputs.TableColumnFamilyArgs;
import com.pulumi.gcp.bigtable.GCPolicy;
import com.pulumi.gcp.bigtable.GCPolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var instance = new Instance("instance", InstanceArgs.builder()
            .name("tf-instance")
            .clusters(InstanceClusterArgs.builder()
                .clusterId("tf-instance-cluster")
                .numNodes(3)
                .storageType("HDD")
                .build())
            .build());

        var table = new Table("table", TableArgs.builder()
            .name("tf-table")
            .instanceName(instance.name())
            .columnFamilies(TableColumnFamilyArgs.builder()
                .family("name")
                .build())
            .build());

        var policy = new GCPolicy("policy", GCPolicyArgs.builder()
            .instanceName(instance.name())
            .table(table.name())
            .columnFamily("name")
            .deletionPolicy("ABANDON")
            .gcRules("""
  {
    \"rules\": [
      {
        \"max_age\": \"168h\"
      }
    ]
  }
            """)
            .build());

    }
}
resources:
  instance:
    type: gcp:bigtable:Instance
    properties:
      name: tf-instance
      clusters:
        - clusterId: tf-instance-cluster
          numNodes: 3
          storageType: HDD
  table:
    type: gcp:bigtable:Table
    properties:
      name: tf-table
      instanceName: ${instance.name}
      columnFamilies:
        - family: name
  policy:
    type: gcp:bigtable:GCPolicy
    properties:
      instanceName: ${instance.name}
      table: ${table.name}
      columnFamily: name
      deletionPolicy: ABANDON
      gcRules: |2
          {
            \"rules\": [
              {
                \"max_age\": \"168h\"
              }
            ]
          }

The gcRules property accepts a JSON string that defines the deletion criteria. Here, max_age specifies that cells older than 168 hours (7 days) are eligible for garbage collection. The deletionPolicy set to “ABANDON” allows the resource to be removed from Pulumi state without deleting the policy from Bigtable, which is necessary for replicated instances where policy deletion is restricted.

Combine age and version limits with union logic

Some workloads need flexible retention where cells are deleted if they meet any of several criteria, such as exceeding either an age threshold or a version count.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const policy = new gcp.bigtable.GCPolicy("policy", {
    instanceName: instance.name,
    table: table.name,
    columnFamily: "name",
    deletionPolicy: "ABANDON",
    gcRules: `  {
    \\"mode\\": \\"union\\",
    \\"rules\\": [
      {
        \\"max_age\\": \\"168h\\"
      },
      {
        \\"max_version\\": 10
      }
    ]
  }
`,
});
import pulumi
import pulumi_gcp as gcp

policy = gcp.bigtable.GCPolicy("policy",
    instance_name=instance["name"],
    table=table["name"],
    column_family="name",
    deletion_policy="ABANDON",
    gc_rules="""  {
    \"mode\": \"union\",
    \"rules\": [
      {
        \"max_age\": \"168h\"
      },
      {
        \"max_version\": 10
      }
    ]
  }
""")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/bigtable"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := bigtable.NewGCPolicy(ctx, "policy", &bigtable.GCPolicyArgs{
			InstanceName:   pulumi.Any(instance.Name),
			Table:          pulumi.Any(table.Name),
			ColumnFamily:   pulumi.String("name"),
			DeletionPolicy: pulumi.String("ABANDON"),
			GcRules: pulumi.String(`  {
    \"mode\": \"union\",
    \"rules\": [
      {
        \"max_age\": \"168h\"
      },
      {
        \"max_version\": 10
      }
    ]
  }
`),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var policy = new Gcp.BigTable.GCPolicy("policy", new()
    {
        InstanceName = instance.Name,
        Table = table.Name,
        ColumnFamily = "name",
        DeletionPolicy = "ABANDON",
        GcRules = @"  {
    \""mode\"": \""union\"",
    \""rules\"": [
      {
        \""max_age\"": \""168h\""
      },
      {
        \""max_version\"": 10
      }
    ]
  }
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.bigtable.GCPolicy;
import com.pulumi.gcp.bigtable.GCPolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var policy = new GCPolicy("policy", GCPolicyArgs.builder()
            .instanceName(instance.name())
            .table(table.name())
            .columnFamily("name")
            .deletionPolicy("ABANDON")
            .gcRules("""
  {
    \"mode\": \"union\",
    \"rules\": [
      {
        \"max_age\": \"168h\"
      },
      {
        \"max_version\": 10
      }
    ]
  }
            """)
            .build());

    }
}
resources:
  policy:
    type: gcp:bigtable:GCPolicy
    properties:
      instanceName: ${instance.name}
      table: ${table.name}
      columnFamily: name
      deletionPolicy: ABANDON
      gcRules: |2
          {
            \"mode\": \"union\",
            \"rules\": [
              {
                \"max_age\": \"168h\"
              },
              {
                \"max_version\": 10
              }
            ]
          }

The mode property set to “union” means cells are deleted when any sub-policy applies (OR logic). In this configuration, cells are removed if they’re older than 7 days OR if more than 10 versions exist. Union mode provides the most aggressive cleanup, applying whichever condition triggers first.

Build complex policies with nested union and intersection

Advanced retention scenarios require combining multiple conditions with both AND and OR logic, such as keeping only recent versions of recent data while allowing older data to persist longer.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const instance = new gcp.bigtable.Instance("instance", {
    name: "instance_name",
    clusters: [{
        clusterId: "cid",
        zone: "us-central1-b",
    }],
    instanceType: "DEVELOPMENT",
    deletionProtection: false,
});
const table = new gcp.bigtable.Table("table", {
    name: "your-table",
    instanceName: instance.id,
    columnFamilies: [{
        family: "cf1",
    }],
});
const policy = new gcp.bigtable.GCPolicy("policy", {
    instanceName: instance.id,
    table: table.name,
    columnFamily: "cf1",
    deletionPolicy: "ABANDON",
    gcRules: `  {
    \\"mode\\": \\"union\\",
    \\"rules\\": [
      {
        \\"max_age\\": \\"10h\\"
      },
      {
        \\"mode\\": \\"intersection\\",
        \\"rules\\": [
          {
            \\"max_age\\": \\"2h\\"
          },
          {
            \\"max_version\\": 2
          }
        ]
      }
    ]
  }
`,
});
import pulumi
import pulumi_gcp as gcp

instance = gcp.bigtable.Instance("instance",
    name="instance_name",
    clusters=[{
        "cluster_id": "cid",
        "zone": "us-central1-b",
    }],
    instance_type="DEVELOPMENT",
    deletion_protection=False)
table = gcp.bigtable.Table("table",
    name="your-table",
    instance_name=instance.id,
    column_families=[{
        "family": "cf1",
    }])
policy = gcp.bigtable.GCPolicy("policy",
    instance_name=instance.id,
    table=table.name,
    column_family="cf1",
    deletion_policy="ABANDON",
    gc_rules="""  {
    \"mode\": \"union\",
    \"rules\": [
      {
        \"max_age\": \"10h\"
      },
      {
        \"mode\": \"intersection\",
        \"rules\": [
          {
            \"max_age\": \"2h\"
          },
          {
            \"max_version\": 2
          }
        ]
      }
    ]
  }
""")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/bigtable"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		instance, err := bigtable.NewInstance(ctx, "instance", &bigtable.InstanceArgs{
			Name: pulumi.String("instance_name"),
			Clusters: bigtable.InstanceClusterArray{
				&bigtable.InstanceClusterArgs{
					ClusterId: pulumi.String("cid"),
					Zone:      pulumi.String("us-central1-b"),
				},
			},
			InstanceType:       pulumi.String("DEVELOPMENT"),
			DeletionProtection: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		table, err := bigtable.NewTable(ctx, "table", &bigtable.TableArgs{
			Name:         pulumi.String("your-table"),
			InstanceName: instance.ID(),
			ColumnFamilies: bigtable.TableColumnFamilyArray{
				&bigtable.TableColumnFamilyArgs{
					Family: pulumi.String("cf1"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = bigtable.NewGCPolicy(ctx, "policy", &bigtable.GCPolicyArgs{
			InstanceName:   instance.ID(),
			Table:          table.Name,
			ColumnFamily:   pulumi.String("cf1"),
			DeletionPolicy: pulumi.String("ABANDON"),
			GcRules: pulumi.String(`  {
    \"mode\": \"union\",
    \"rules\": [
      {
        \"max_age\": \"10h\"
      },
      {
        \"mode\": \"intersection\",
        \"rules\": [
          {
            \"max_age\": \"2h\"
          },
          {
            \"max_version\": 2
          }
        ]
      }
    ]
  }
`),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var instance = new Gcp.BigTable.Instance("instance", new()
    {
        Name = "instance_name",
        Clusters = new[]
        {
            new Gcp.BigTable.Inputs.InstanceClusterArgs
            {
                ClusterId = "cid",
                Zone = "us-central1-b",
            },
        },
        InstanceType = "DEVELOPMENT",
        DeletionProtection = false,
    });

    var table = new Gcp.BigTable.Table("table", new()
    {
        Name = "your-table",
        InstanceName = instance.Id,
        ColumnFamilies = new[]
        {
            new Gcp.BigTable.Inputs.TableColumnFamilyArgs
            {
                Family = "cf1",
            },
        },
    });

    var policy = new Gcp.BigTable.GCPolicy("policy", new()
    {
        InstanceName = instance.Id,
        Table = table.Name,
        ColumnFamily = "cf1",
        DeletionPolicy = "ABANDON",
        GcRules = @"  {
    \""mode\"": \""union\"",
    \""rules\"": [
      {
        \""max_age\"": \""10h\""
      },
      {
        \""mode\"": \""intersection\"",
        \""rules\"": [
          {
            \""max_age\"": \""2h\""
          },
          {
            \""max_version\"": 2
          }
        ]
      }
    ]
  }
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.bigtable.Instance;
import com.pulumi.gcp.bigtable.InstanceArgs;
import com.pulumi.gcp.bigtable.inputs.InstanceClusterArgs;
import com.pulumi.gcp.bigtable.Table;
import com.pulumi.gcp.bigtable.TableArgs;
import com.pulumi.gcp.bigtable.inputs.TableColumnFamilyArgs;
import com.pulumi.gcp.bigtable.GCPolicy;
import com.pulumi.gcp.bigtable.GCPolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var instance = new Instance("instance", InstanceArgs.builder()
            .name("instance_name")
            .clusters(InstanceClusterArgs.builder()
                .clusterId("cid")
                .zone("us-central1-b")
                .build())
            .instanceType("DEVELOPMENT")
            .deletionProtection(false)
            .build());

        var table = new Table("table", TableArgs.builder()
            .name("your-table")
            .instanceName(instance.id())
            .columnFamilies(TableColumnFamilyArgs.builder()
                .family("cf1")
                .build())
            .build());

        var policy = new GCPolicy("policy", GCPolicyArgs.builder()
            .instanceName(instance.id())
            .table(table.name())
            .columnFamily("cf1")
            .deletionPolicy("ABANDON")
            .gcRules("""
  {
    \"mode\": \"union\",
    \"rules\": [
      {
        \"max_age\": \"10h\"
      },
      {
        \"mode\": \"intersection\",
        \"rules\": [
          {
            \"max_age\": \"2h\"
          },
          {
            \"max_version\": 2
          }
        ]
      }
    ]
  }
            """)
            .build());

    }
}
resources:
  instance:
    type: gcp:bigtable:Instance
    properties:
      name: instance_name
      clusters:
        - clusterId: cid
          zone: us-central1-b
      instanceType: DEVELOPMENT
      deletionProtection: false
  table:
    type: gcp:bigtable:Table
    properties:
      name: your-table
      instanceName: ${instance.id}
      columnFamilies:
        - family: cf1
  policy:
    type: gcp:bigtable:GCPolicy
    properties:
      instanceName: ${instance.id}
      table: ${table.name}
      columnFamily: cf1
      deletionPolicy: ABANDON
      gcRules: |2
          {
            \"mode\": \"union\",
            \"rules\": [
              {
                \"max_age\": \"10h\"
              },
              {
                \"mode\": \"intersection\",
                \"rules\": [
                  {
                    \"max_age\": \"2h\"
                  },
                  {
                    \"max_version\": 2
                  }
                ]
              }
            ]
          }

Nested rules allow you to combine union and intersection modes. This policy deletes cells that are either older than 10 hours OR (older than 2 hours AND have more than 2 versions). The outer union creates an OR relationship, while the inner intersection creates an AND relationship. The example shows the equivalent cbt command for validation: cbt setgcpolicy your-table cf1 "(maxage=2d and maxversions=2) or maxage=10h".

Beyond these examples

These snippets focus on specific GC policy features: age-based and version-based garbage collection, and union and intersection logic for multi-condition policies. They’re intentionally minimal rather than full table management solutions.

The examples reference pre-existing infrastructure such as Bigtable instances and tables with defined column families. They focus on configuring garbage collection rather than provisioning the table schema.

To keep things focused, common GC policy patterns are omitted, including:

  • Warning suppression for policy relaxation (ignoreWarnings)
  • Replication-aware policy management
  • Simple mode/maxAge/maxVersion properties (examples use gcRules JSON)

These omissions are intentional: the goal is to illustrate how each GC policy feature is wired, not provide drop-in data lifecycle modules. See the Bigtable GCPolicy resource reference for all available configuration options.

Let's configure GCP Bigtable Garbage Collection Policies

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Replication & Deletion
Why can't I destroy my GC policy on a replicated table?
GC policies on replicated tables cannot be destroyed directly because destroying translates to “never perform garbage collection,” which is considered relaxing the policy and isn’t allowed. The workaround is to unreplicate the instance first by updating it to have one cluster.
When should I use deletionPolicy ABANDON?
Set deletionPolicy to ABANDON when you want to abandon the resource rather than delete it. This is particularly useful for GC policies on replicated instances where direct deletion isn’t allowed.
Can I have multiple GC policies for the same column family?
Multiple GC policies per column family aren’t recommended as they may result in unexpected behavior. Instead, use a single GC policy with UNION or INTERSECTION to combine multiple conditions.
What are the risks of setting ignoreWarnings to true?
Setting ignoreWarnings to true allows relaxing the GC policy for replicated clusters by up to 90 days, but this may increase how long clusters remain inconsistent. Review the risks at https://cloud.google.com/bigtable/docs/garbage-collection#increasing before enabling this option.
Policy Configuration
Should I use gcRules or the simple maxAge/maxVersion properties?
Use gcRules for complex policies with multiple conditions or nested logic. For simple policies, you can use maxAge or maxVersion directly. Note that gcRules conflicts with mode, maxAge, and maxVersion, so you can’t use them together.
What's the difference between UNION and INTERSECTION modes?
UNION mode applies when any of its sub-policies apply (OR logic), while INTERSECTION mode applies when all of its sub-policies apply (AND logic). Use these in gcRules to combine multiple conditions.
How do I create a complex GC policy with nested conditions?
Use gcRules with nested JSON objects. For example, you can combine UNION and INTERSECTION modes: {"mode": "union", "rules": [{"max_age": "10h"}, {"mode": "intersection", "rules": [...]}]}. This is equivalent to the cbt command: (maxage=2d and maxversions=2) or maxage=10h.
Immutability & Constraints
What properties are immutable after creation?
The following properties cannot be changed after creation: columnFamily, instanceName, table, project, maxAge, maxVersions, and mode.
Can I import an existing GC policy?
No, this resource does not support import.
How do I specify the age for a GC policy?
Use the max_age field in gcRules with a duration string like "168h" (7 days) or "10h" (10 hours). The format follows Go’s duration syntax.

Using a different cloud?

Explore database guides for other cloud providers: