Create GCP Vertex AI Feature Stores

The gcp:vertex/aiFeatureStore:AiFeatureStore resource, part of the Pulumi GCP provider, provisions a Vertex AI Feature Store: a managed repository for storing, serving, and managing ML features. This guide focuses on three capabilities: fixed and autoscaling serving capacity, feature value TTL configuration, and encryption setup.

Feature stores require KMS keys for encryption and run within a GCP project with Vertex AI enabled. The examples are intentionally small. Combine them with your own EntityType and Feature definitions to build a complete feature management system.

Create a feature store with fixed serving capacity

Machine learning teams start by provisioning a named store with fixed online serving capacity for predictable real-time feature lookups during model inference.

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

const featurestore = new gcp.vertex.AiFeatureStore("featurestore", {
    name: "terraform",
    labels: {
        foo: "bar",
    },
    region: "us-central1",
    onlineServingConfig: {
        fixedNodeCount: 2,
    },
    encryptionSpec: {
        kmsKeyName: "kms-name",
    },
    forceDestroy: true,
});
import pulumi
import pulumi_gcp as gcp

featurestore = gcp.vertex.AiFeatureStore("featurestore",
    name="terraform",
    labels={
        "foo": "bar",
    },
    region="us-central1",
    online_serving_config={
        "fixed_node_count": 2,
    },
    encryption_spec={
        "kms_key_name": "kms-name",
    },
    force_destroy=True)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := vertex.NewAiFeatureStore(ctx, "featurestore", &vertex.AiFeatureStoreArgs{
			Name: pulumi.String("terraform"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			Region: pulumi.String("us-central1"),
			OnlineServingConfig: &vertex.AiFeatureStoreOnlineServingConfigArgs{
				FixedNodeCount: pulumi.Int(2),
			},
			EncryptionSpec: &vertex.AiFeatureStoreEncryptionSpecArgs{
				KmsKeyName: pulumi.String("kms-name"),
			},
			ForceDestroy: pulumi.Bool(true),
		})
		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 featurestore = new Gcp.Vertex.AiFeatureStore("featurestore", new()
    {
        Name = "terraform",
        Labels = 
        {
            { "foo", "bar" },
        },
        Region = "us-central1",
        OnlineServingConfig = new Gcp.Vertex.Inputs.AiFeatureStoreOnlineServingConfigArgs
        {
            FixedNodeCount = 2,
        },
        EncryptionSpec = new Gcp.Vertex.Inputs.AiFeatureStoreEncryptionSpecArgs
        {
            KmsKeyName = "kms-name",
        },
        ForceDestroy = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.vertex.AiFeatureStore;
import com.pulumi.gcp.vertex.AiFeatureStoreArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreOnlineServingConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreEncryptionSpecArgs;
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 featurestore = new AiFeatureStore("featurestore", AiFeatureStoreArgs.builder()
            .name("terraform")
            .labels(Map.of("foo", "bar"))
            .region("us-central1")
            .onlineServingConfig(AiFeatureStoreOnlineServingConfigArgs.builder()
                .fixedNodeCount(2)
                .build())
            .encryptionSpec(AiFeatureStoreEncryptionSpecArgs.builder()
                .kmsKeyName("kms-name")
                .build())
            .forceDestroy(true)
            .build());

    }
}
resources:
  featurestore:
    type: gcp:vertex:AiFeatureStore
    properties:
      name: terraform
      labels:
        foo: bar
      region: us-central1
      onlineServingConfig:
        fixedNodeCount: 2
      encryptionSpec:
        kmsKeyName: kms-name
      forceDestroy: true

The onlineServingConfig property with fixedNodeCount allocates dedicated serving nodes for feature retrieval. The encryptionSpec secures both online and offline storage using your KMS key. The forceDestroy property controls whether dependent EntityTypes and Features are deleted when the store is removed.

Configure feature value retention with TTL

Feature stores often need to expire old values to manage storage costs and maintain data freshness.

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

const featurestore = new gcp.vertex.AiFeatureStore("featurestore", {
    name: "terraform2",
    labels: {
        foo: "bar",
    },
    region: "us-central1",
    onlineServingConfig: {
        fixedNodeCount: 2,
    },
    encryptionSpec: {
        kmsKeyName: "kms-name",
    },
    onlineStorageTtlDays: 30,
    forceDestroy: true,
});
import pulumi
import pulumi_gcp as gcp

featurestore = gcp.vertex.AiFeatureStore("featurestore",
    name="terraform2",
    labels={
        "foo": "bar",
    },
    region="us-central1",
    online_serving_config={
        "fixed_node_count": 2,
    },
    encryption_spec={
        "kms_key_name": "kms-name",
    },
    online_storage_ttl_days=30,
    force_destroy=True)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := vertex.NewAiFeatureStore(ctx, "featurestore", &vertex.AiFeatureStoreArgs{
			Name: pulumi.String("terraform2"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			Region: pulumi.String("us-central1"),
			OnlineServingConfig: &vertex.AiFeatureStoreOnlineServingConfigArgs{
				FixedNodeCount: pulumi.Int(2),
			},
			EncryptionSpec: &vertex.AiFeatureStoreEncryptionSpecArgs{
				KmsKeyName: pulumi.String("kms-name"),
			},
			OnlineStorageTtlDays: pulumi.Int(30),
			ForceDestroy:         pulumi.Bool(true),
		})
		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 featurestore = new Gcp.Vertex.AiFeatureStore("featurestore", new()
    {
        Name = "terraform2",
        Labels = 
        {
            { "foo", "bar" },
        },
        Region = "us-central1",
        OnlineServingConfig = new Gcp.Vertex.Inputs.AiFeatureStoreOnlineServingConfigArgs
        {
            FixedNodeCount = 2,
        },
        EncryptionSpec = new Gcp.Vertex.Inputs.AiFeatureStoreEncryptionSpecArgs
        {
            KmsKeyName = "kms-name",
        },
        OnlineStorageTtlDays = 30,
        ForceDestroy = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.vertex.AiFeatureStore;
import com.pulumi.gcp.vertex.AiFeatureStoreArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreOnlineServingConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreEncryptionSpecArgs;
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 featurestore = new AiFeatureStore("featurestore", AiFeatureStoreArgs.builder()
            .name("terraform2")
            .labels(Map.of("foo", "bar"))
            .region("us-central1")
            .onlineServingConfig(AiFeatureStoreOnlineServingConfigArgs.builder()
                .fixedNodeCount(2)
                .build())
            .encryptionSpec(AiFeatureStoreEncryptionSpecArgs.builder()
                .kmsKeyName("kms-name")
                .build())
            .onlineStorageTtlDays(30)
            .forceDestroy(true)
            .build());

    }
}
resources:
  featurestore:
    type: gcp:vertex:AiFeatureStore
    properties:
      name: terraform2
      labels:
        foo: bar
      region: us-central1
      onlineServingConfig:
        fixedNodeCount: 2
      encryptionSpec:
        kmsKeyName: kms-name
      onlineStorageTtlDays: 30
      forceDestroy: true

The onlineStorageTtlDays property sets how long feature values remain in online serving storage before automatic removal. Vertex AI periodically cleans up values older than this threshold based on their generation time. This extends the basic configuration by adding lifecycle management without changing the serving capacity model.

Enable autoscaling for variable workloads

Production workloads with variable traffic benefit from autoscaling to balance cost and performance automatically.

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

const featurestore = new gcp.vertex.AiFeatureStore("featurestore", {
    name: "terraform3",
    labels: {
        foo: "bar",
    },
    region: "us-central1",
    onlineServingConfig: {
        scaling: {
            minNodeCount: 2,
            maxNodeCount: 10,
        },
    },
    encryptionSpec: {
        kmsKeyName: "kms-name",
    },
    forceDestroy: true,
});
import pulumi
import pulumi_gcp as gcp

featurestore = gcp.vertex.AiFeatureStore("featurestore",
    name="terraform3",
    labels={
        "foo": "bar",
    },
    region="us-central1",
    online_serving_config={
        "scaling": {
            "min_node_count": 2,
            "max_node_count": 10,
        },
    },
    encryption_spec={
        "kms_key_name": "kms-name",
    },
    force_destroy=True)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := vertex.NewAiFeatureStore(ctx, "featurestore", &vertex.AiFeatureStoreArgs{
			Name: pulumi.String("terraform3"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			Region: pulumi.String("us-central1"),
			OnlineServingConfig: &vertex.AiFeatureStoreOnlineServingConfigArgs{
				Scaling: &vertex.AiFeatureStoreOnlineServingConfigScalingArgs{
					MinNodeCount: pulumi.Int(2),
					MaxNodeCount: pulumi.Int(10),
				},
			},
			EncryptionSpec: &vertex.AiFeatureStoreEncryptionSpecArgs{
				KmsKeyName: pulumi.String("kms-name"),
			},
			ForceDestroy: pulumi.Bool(true),
		})
		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 featurestore = new Gcp.Vertex.AiFeatureStore("featurestore", new()
    {
        Name = "terraform3",
        Labels = 
        {
            { "foo", "bar" },
        },
        Region = "us-central1",
        OnlineServingConfig = new Gcp.Vertex.Inputs.AiFeatureStoreOnlineServingConfigArgs
        {
            Scaling = new Gcp.Vertex.Inputs.AiFeatureStoreOnlineServingConfigScalingArgs
            {
                MinNodeCount = 2,
                MaxNodeCount = 10,
            },
        },
        EncryptionSpec = new Gcp.Vertex.Inputs.AiFeatureStoreEncryptionSpecArgs
        {
            KmsKeyName = "kms-name",
        },
        ForceDestroy = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.vertex.AiFeatureStore;
import com.pulumi.gcp.vertex.AiFeatureStoreArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreOnlineServingConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreOnlineServingConfigScalingArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureStoreEncryptionSpecArgs;
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 featurestore = new AiFeatureStore("featurestore", AiFeatureStoreArgs.builder()
            .name("terraform3")
            .labels(Map.of("foo", "bar"))
            .region("us-central1")
            .onlineServingConfig(AiFeatureStoreOnlineServingConfigArgs.builder()
                .scaling(AiFeatureStoreOnlineServingConfigScalingArgs.builder()
                    .minNodeCount(2)
                    .maxNodeCount(10)
                    .build())
                .build())
            .encryptionSpec(AiFeatureStoreEncryptionSpecArgs.builder()
                .kmsKeyName("kms-name")
                .build())
            .forceDestroy(true)
            .build());

    }
}
resources:
  featurestore:
    type: gcp:vertex:AiFeatureStore
    properties:
      name: terraform3
      labels:
        foo: bar
      region: us-central1
      onlineServingConfig:
        scaling:
          minNodeCount: 2
          maxNodeCount: 10
      encryptionSpec:
        kmsKeyName: kms-name
      forceDestroy: true

The scaling property replaces fixedNodeCount with minNodeCount and maxNodeCount, allowing Vertex AI to adjust serving capacity based on demand. This provides an alternative to fixed capacity when traffic patterns are unpredictable.

Beyond these examples

These snippets focus on specific feature store-level features: fixed and autoscaling serving capacity, and encryption and TTL configuration. They’re intentionally minimal rather than full ML feature management systems.

The examples reference pre-existing infrastructure such as KMS encryption keys and a GCP project with Vertex AI API enabled. They focus on configuring the feature store container rather than the features themselves.

To keep things focused, common feature store patterns are omitted, including:

  • EntityType and Feature definitions (managed separately)
  • Offline storage configuration and batch serving
  • Monitoring and alerting setup
  • IAM permissions for feature store access

These omissions are intentional: the goal is to illustrate how each feature store capability is wired, not provide drop-in ML infrastructure modules. See the Vertex AI Feature Store resource reference for all available configuration options.

Let's create GCP Vertex AI Feature Stores

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Serving
What's the difference between fixedNodeCount and scaling for online serving?
Use fixedNodeCount to set a static number of nodes, or use scaling with minNodeCount and maxNodeCount for autoscaling. These options are mutually exclusive.
How do I enable encryption for my featurestore?
Configure encryptionSpec with a kmsKeyName to secure both online and offline data storage.
Data Lifecycle & Storage
What's the default TTL for online storage?
The default onlineStorageTtlDays is 4000 days if not specified.
What happens if I set onlineStorageTtlDays higher than offlineStorageTtlDays?
This will cause an error. The onlineStorageTtlDays must be less than or equal to offlineStorageTtlDays for each EntityType.
What happens when I delete a featurestore with forceDestroy enabled?
Setting forceDestroy to true will delete all EntityTypes and Features associated with the featurestore when it’s destroyed.
Labels & Metadata
Why are labels I didn't configure appearing on my featurestore?
The labels field is non-authoritative and only manages labels in your configuration. Use effectiveLabels to see all labels present on the resource, including those set by other clients or services.
Immutability & Updates
What properties can't I change after creating a featurestore?
The name, project, and region properties are immutable and cannot be changed after creation.
What are the naming requirements for a featurestore?
Names can be up to 60 characters using [a-z0-9_], and the first character cannot be a number.
Can I import an existing featurestore into Pulumi?
Yes, you can import using formats like projects/{{project}}/locations/{{region}}/featurestores/{{name}}, {{region}}/{{name}}, or just {{name}}.

Using a different cloud?

Explore analytics guides for other cloud providers: