Configure GCP Backend Buckets for Load Balancing

The gcp:compute/backendBucket:BackendBucket resource, part of the Pulumi GCP provider, connects Cloud Storage buckets to HTTP(S) load balancers for serving static content. This guide focuses on three capabilities: CDN enablement and cache control, Cloud Armor edge security, and cache key customization.

Backend buckets reference existing Cloud Storage buckets and must be connected to load balancer URL maps to route traffic. The examples are intentionally small. Combine them with your own URL maps and routing rules.

Serve static content from Cloud Storage

HTTP(S) load balancers often serve static assets like images, CSS, and JavaScript from Cloud Storage rather than compute instances, reducing costs and improving performance.

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

const imageBucket = new gcp.storage.Bucket("image_bucket", {
    name: "image-store-bucket",
    location: "EU",
});
const imageBackend = new gcp.compute.BackendBucket("image_backend", {
    name: "image-backend-bucket",
    description: "Contains beautiful images",
    bucketName: imageBucket.name,
    enableCdn: true,
});
import pulumi
import pulumi_gcp as gcp

image_bucket = gcp.storage.Bucket("image_bucket",
    name="image-store-bucket",
    location="EU")
image_backend = gcp.compute.BackendBucket("image_backend",
    name="image-backend-bucket",
    description="Contains beautiful images",
    bucket_name=image_bucket.name,
    enable_cdn=True)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		imageBucket, err := storage.NewBucket(ctx, "image_bucket", &storage.BucketArgs{
			Name:     pulumi.String("image-store-bucket"),
			Location: pulumi.String("EU"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewBackendBucket(ctx, "image_backend", &compute.BackendBucketArgs{
			Name:        pulumi.String("image-backend-bucket"),
			Description: pulumi.String("Contains beautiful images"),
			BucketName:  imageBucket.Name,
			EnableCdn:   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 imageBucket = new Gcp.Storage.Bucket("image_bucket", new()
    {
        Name = "image-store-bucket",
        Location = "EU",
    });

    var imageBackend = new Gcp.Compute.BackendBucket("image_backend", new()
    {
        Name = "image-backend-bucket",
        Description = "Contains beautiful images",
        BucketName = imageBucket.Name,
        EnableCdn = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.storage.Bucket;
import com.pulumi.gcp.storage.BucketArgs;
import com.pulumi.gcp.compute.BackendBucket;
import com.pulumi.gcp.compute.BackendBucketArgs;
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 imageBucket = new Bucket("imageBucket", BucketArgs.builder()
            .name("image-store-bucket")
            .location("EU")
            .build());

        var imageBackend = new BackendBucket("imageBackend", BackendBucketArgs.builder()
            .name("image-backend-bucket")
            .description("Contains beautiful images")
            .bucketName(imageBucket.name())
            .enableCdn(true)
            .build());

    }
}
resources:
  imageBackend:
    type: gcp:compute:BackendBucket
    name: image_backend
    properties:
      name: image-backend-bucket
      description: Contains beautiful images
      bucketName: ${imageBucket.name}
      enableCdn: true
  imageBucket:
    type: gcp:storage:Bucket
    name: image_bucket
    properties:
      name: image-store-bucket
      location: EU

The bucketName property connects the backend bucket to your Cloud Storage bucket. The enableCdn property activates Cloud CDN, caching content at Google’s edge locations worldwide. Without a URL map referencing this backend bucket, no traffic will reach the storage bucket.

Apply Cloud Armor edge security policies

Applications serving content globally may need DDoS protection and access controls at the edge before requests reach storage.

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

const imageBackendBucket = new gcp.storage.Bucket("image_backend", {
    name: "image-store-bucket",
    location: "EU",
});
const policy = new gcp.compute.SecurityPolicy("policy", {
    name: "image-store-bucket",
    description: "basic security policy",
    type: "CLOUD_ARMOR_EDGE",
});
const imageBackend = new gcp.compute.BackendBucket("image_backend", {
    name: "image-backend-bucket",
    description: "Contains beautiful images",
    bucketName: imageBackendBucket.name,
    enableCdn: true,
    edgeSecurityPolicy: policy.id,
});
import pulumi
import pulumi_gcp as gcp

image_backend_bucket = gcp.storage.Bucket("image_backend",
    name="image-store-bucket",
    location="EU")
policy = gcp.compute.SecurityPolicy("policy",
    name="image-store-bucket",
    description="basic security policy",
    type="CLOUD_ARMOR_EDGE")
image_backend = gcp.compute.BackendBucket("image_backend",
    name="image-backend-bucket",
    description="Contains beautiful images",
    bucket_name=image_backend_bucket.name,
    enable_cdn=True,
    edge_security_policy=policy.id)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		imageBackendBucket, err := storage.NewBucket(ctx, "image_backend", &storage.BucketArgs{
			Name:     pulumi.String("image-store-bucket"),
			Location: pulumi.String("EU"),
		})
		if err != nil {
			return err
		}
		policy, err := compute.NewSecurityPolicy(ctx, "policy", &compute.SecurityPolicyArgs{
			Name:        pulumi.String("image-store-bucket"),
			Description: pulumi.String("basic security policy"),
			Type:        pulumi.String("CLOUD_ARMOR_EDGE"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewBackendBucket(ctx, "image_backend", &compute.BackendBucketArgs{
			Name:               pulumi.String("image-backend-bucket"),
			Description:        pulumi.String("Contains beautiful images"),
			BucketName:         imageBackendBucket.Name,
			EnableCdn:          pulumi.Bool(true),
			EdgeSecurityPolicy: policy.ID(),
		})
		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 imageBackendBucket = new Gcp.Storage.Bucket("image_backend", new()
    {
        Name = "image-store-bucket",
        Location = "EU",
    });

    var policy = new Gcp.Compute.SecurityPolicy("policy", new()
    {
        Name = "image-store-bucket",
        Description = "basic security policy",
        Type = "CLOUD_ARMOR_EDGE",
    });

    var imageBackend = new Gcp.Compute.BackendBucket("image_backend", new()
    {
        Name = "image-backend-bucket",
        Description = "Contains beautiful images",
        BucketName = imageBackendBucket.Name,
        EnableCdn = true,
        EdgeSecurityPolicy = policy.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.storage.Bucket;
import com.pulumi.gcp.storage.BucketArgs;
import com.pulumi.gcp.compute.SecurityPolicy;
import com.pulumi.gcp.compute.SecurityPolicyArgs;
import com.pulumi.gcp.compute.BackendBucket;
import com.pulumi.gcp.compute.BackendBucketArgs;
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 imageBackendBucket = new Bucket("imageBackendBucket", BucketArgs.builder()
            .name("image-store-bucket")
            .location("EU")
            .build());

        var policy = new SecurityPolicy("policy", SecurityPolicyArgs.builder()
            .name("image-store-bucket")
            .description("basic security policy")
            .type("CLOUD_ARMOR_EDGE")
            .build());

        var imageBackend = new BackendBucket("imageBackend", BackendBucketArgs.builder()
            .name("image-backend-bucket")
            .description("Contains beautiful images")
            .bucketName(imageBackendBucket.name())
            .enableCdn(true)
            .edgeSecurityPolicy(policy.id())
            .build());

    }
}
resources:
  imageBackend:
    type: gcp:compute:BackendBucket
    name: image_backend
    properties:
      name: image-backend-bucket
      description: Contains beautiful images
      bucketName: ${imageBackendBucket.name}
      enableCdn: true
      edgeSecurityPolicy: ${policy.id}
  imageBackendBucket:
    type: gcp:storage:Bucket
    name: image_backend
    properties:
      name: image-store-bucket
      location: EU
  policy:
    type: gcp:compute:SecurityPolicy
    properties:
      name: image-store-bucket
      description: basic security policy
      type: CLOUD_ARMOR_EDGE

The edgeSecurityPolicy property attaches a Cloud Armor policy to the backend bucket. The policy type must be CLOUD_ARMOR_EDGE for backend buckets. Security rules evaluate at Google’s edge, blocking malicious traffic before it consumes bandwidth or storage egress.

Control CDN cache keys with query parameters

CDN caching becomes more effective when you control which query parameters affect cache keys. Whitelisting specific parameters ensures different versions of content are cached separately.

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

const imageBucket = new gcp.storage.Bucket("image_bucket", {
    name: "image-backend-bucket",
    location: "EU",
});
const imageBackend = new gcp.compute.BackendBucket("image_backend", {
    name: "image-backend-bucket",
    description: "Contains beautiful images",
    bucketName: imageBucket.name,
    enableCdn: true,
    cdnPolicy: {
        cacheKeyPolicy: {
            queryStringWhitelists: ["image-version"],
        },
    },
});
import pulumi
import pulumi_gcp as gcp

image_bucket = gcp.storage.Bucket("image_bucket",
    name="image-backend-bucket",
    location="EU")
image_backend = gcp.compute.BackendBucket("image_backend",
    name="image-backend-bucket",
    description="Contains beautiful images",
    bucket_name=image_bucket.name,
    enable_cdn=True,
    cdn_policy={
        "cache_key_policy": {
            "query_string_whitelists": ["image-version"],
        },
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		imageBucket, err := storage.NewBucket(ctx, "image_bucket", &storage.BucketArgs{
			Name:     pulumi.String("image-backend-bucket"),
			Location: pulumi.String("EU"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewBackendBucket(ctx, "image_backend", &compute.BackendBucketArgs{
			Name:        pulumi.String("image-backend-bucket"),
			Description: pulumi.String("Contains beautiful images"),
			BucketName:  imageBucket.Name,
			EnableCdn:   pulumi.Bool(true),
			CdnPolicy: &compute.BackendBucketCdnPolicyArgs{
				CacheKeyPolicy: &compute.BackendBucketCdnPolicyCacheKeyPolicyArgs{
					QueryStringWhitelists: pulumi.StringArray{
						pulumi.String("image-version"),
					},
				},
			},
		})
		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 imageBucket = new Gcp.Storage.Bucket("image_bucket", new()
    {
        Name = "image-backend-bucket",
        Location = "EU",
    });

    var imageBackend = new Gcp.Compute.BackendBucket("image_backend", new()
    {
        Name = "image-backend-bucket",
        Description = "Contains beautiful images",
        BucketName = imageBucket.Name,
        EnableCdn = true,
        CdnPolicy = new Gcp.Compute.Inputs.BackendBucketCdnPolicyArgs
        {
            CacheKeyPolicy = new Gcp.Compute.Inputs.BackendBucketCdnPolicyCacheKeyPolicyArgs
            {
                QueryStringWhitelists = new[]
                {
                    "image-version",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.storage.Bucket;
import com.pulumi.gcp.storage.BucketArgs;
import com.pulumi.gcp.compute.BackendBucket;
import com.pulumi.gcp.compute.BackendBucketArgs;
import com.pulumi.gcp.compute.inputs.BackendBucketCdnPolicyArgs;
import com.pulumi.gcp.compute.inputs.BackendBucketCdnPolicyCacheKeyPolicyArgs;
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 imageBucket = new Bucket("imageBucket", BucketArgs.builder()
            .name("image-backend-bucket")
            .location("EU")
            .build());

        var imageBackend = new BackendBucket("imageBackend", BackendBucketArgs.builder()
            .name("image-backend-bucket")
            .description("Contains beautiful images")
            .bucketName(imageBucket.name())
            .enableCdn(true)
            .cdnPolicy(BackendBucketCdnPolicyArgs.builder()
                .cacheKeyPolicy(BackendBucketCdnPolicyCacheKeyPolicyArgs.builder()
                    .queryStringWhitelists("image-version")
                    .build())
                .build())
            .build());

    }
}
resources:
  imageBackend:
    type: gcp:compute:BackendBucket
    name: image_backend
    properties:
      name: image-backend-bucket
      description: Contains beautiful images
      bucketName: ${imageBucket.name}
      enableCdn: true
      cdnPolicy:
        cacheKeyPolicy:
          queryStringWhitelists:
            - image-version
  imageBucket:
    type: gcp:storage:Bucket
    name: image_bucket
    properties:
      name: image-backend-bucket
      location: EU

The cdnPolicy.cacheKeyPolicy.queryStringWhitelists property specifies which query parameters influence cache keys. Here, requests with different “image-version” values create separate cache entries, while other query parameters are ignored. This prevents cache fragmentation from tracking parameters or session IDs.

Include custom headers in cache keys

Some applications serve different content based on custom headers like API versions or feature flags. Including these headers in cache keys ensures correct content delivery.

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

const imageBucket = new gcp.storage.Bucket("image_bucket", {
    name: "image-backend-bucket",
    location: "EU",
});
const imageBackend = new gcp.compute.BackendBucket("image_backend", {
    name: "image-backend-bucket",
    description: "Contains beautiful images",
    bucketName: imageBucket.name,
    enableCdn: true,
    cdnPolicy: {
        cacheKeyPolicy: {
            includeHttpHeaders: ["X-My-Header-Field"],
        },
    },
});
import pulumi
import pulumi_gcp as gcp

image_bucket = gcp.storage.Bucket("image_bucket",
    name="image-backend-bucket",
    location="EU")
image_backend = gcp.compute.BackendBucket("image_backend",
    name="image-backend-bucket",
    description="Contains beautiful images",
    bucket_name=image_bucket.name,
    enable_cdn=True,
    cdn_policy={
        "cache_key_policy": {
            "include_http_headers": ["X-My-Header-Field"],
        },
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		imageBucket, err := storage.NewBucket(ctx, "image_bucket", &storage.BucketArgs{
			Name:     pulumi.String("image-backend-bucket"),
			Location: pulumi.String("EU"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewBackendBucket(ctx, "image_backend", &compute.BackendBucketArgs{
			Name:        pulumi.String("image-backend-bucket"),
			Description: pulumi.String("Contains beautiful images"),
			BucketName:  imageBucket.Name,
			EnableCdn:   pulumi.Bool(true),
			CdnPolicy: &compute.BackendBucketCdnPolicyArgs{
				CacheKeyPolicy: &compute.BackendBucketCdnPolicyCacheKeyPolicyArgs{
					IncludeHttpHeaders: pulumi.StringArray{
						pulumi.String("X-My-Header-Field"),
					},
				},
			},
		})
		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 imageBucket = new Gcp.Storage.Bucket("image_bucket", new()
    {
        Name = "image-backend-bucket",
        Location = "EU",
    });

    var imageBackend = new Gcp.Compute.BackendBucket("image_backend", new()
    {
        Name = "image-backend-bucket",
        Description = "Contains beautiful images",
        BucketName = imageBucket.Name,
        EnableCdn = true,
        CdnPolicy = new Gcp.Compute.Inputs.BackendBucketCdnPolicyArgs
        {
            CacheKeyPolicy = new Gcp.Compute.Inputs.BackendBucketCdnPolicyCacheKeyPolicyArgs
            {
                IncludeHttpHeaders = new[]
                {
                    "X-My-Header-Field",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.storage.Bucket;
import com.pulumi.gcp.storage.BucketArgs;
import com.pulumi.gcp.compute.BackendBucket;
import com.pulumi.gcp.compute.BackendBucketArgs;
import com.pulumi.gcp.compute.inputs.BackendBucketCdnPolicyArgs;
import com.pulumi.gcp.compute.inputs.BackendBucketCdnPolicyCacheKeyPolicyArgs;
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 imageBucket = new Bucket("imageBucket", BucketArgs.builder()
            .name("image-backend-bucket")
            .location("EU")
            .build());

        var imageBackend = new BackendBucket("imageBackend", BackendBucketArgs.builder()
            .name("image-backend-bucket")
            .description("Contains beautiful images")
            .bucketName(imageBucket.name())
            .enableCdn(true)
            .cdnPolicy(BackendBucketCdnPolicyArgs.builder()
                .cacheKeyPolicy(BackendBucketCdnPolicyCacheKeyPolicyArgs.builder()
                    .includeHttpHeaders("X-My-Header-Field")
                    .build())
                .build())
            .build());

    }
}
resources:
  imageBackend:
    type: gcp:compute:BackendBucket
    name: image_backend
    properties:
      name: image-backend-bucket
      description: Contains beautiful images
      bucketName: ${imageBucket.name}
      enableCdn: true
      cdnPolicy:
        cacheKeyPolicy:
          includeHttpHeaders:
            - X-My-Header-Field
  imageBucket:
    type: gcp:storage:Bucket
    name: image_bucket
    properties:
      name: image-backend-bucket
      location: EU

The cdnPolicy.cacheKeyPolicy.includeHttpHeaders property adds custom headers to cache keys. Requests with different “X-My-Header-Field” values create separate cache entries. This approach works well for API versioning or A/B testing where query strings aren’t practical.

Beyond these examples

These snippets focus on specific backend bucket features: CDN configuration and cache control, and Cloud Armor edge security. They’re intentionally minimal rather than full load balancing configurations.

The examples reference pre-existing infrastructure such as Cloud Storage buckets, and HTTP(S) load balancer URL maps that must reference the backend bucket. They focus on backend bucket configuration rather than complete load balancing setups.

To keep things focused, common backend bucket patterns are omitted, including:

  • Custom response headers (customResponseHeaders)
  • Compression settings (compressionMode)
  • Internal load balancing (loadBalancingScheme)
  • URL map integration and routing rules

These omissions are intentional: the goal is to illustrate how each backend bucket feature is wired, not provide drop-in load balancing modules. See the Backend Bucket resource reference for all available configuration options.

Let's configure GCP Backend Buckets for Load Balancing

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

CDN & Caching
Why can't I enable CDN with INTERNAL_MANAGED load balancing?
CDN cannot be enabled when loadBalancingScheme is set to INTERNAL_MANAGED. Use classic global external or global application external load balancers if you need CDN functionality.
How do I control which query parameters affect caching?
Configure cdnPolicy.cacheKeyPolicy.queryStringWhitelists with an array of query parameter names to include in cache keys (e.g., ["image-version"]).
How do I include custom HTTP headers in cache keys?
Set cdnPolicy.cacheKeyPolicy.includeHttpHeaders with header names to include in cache key generation (e.g., ["X-My-Header-Field"]).
What compression options are available?
Use compressionMode to compress text responses with Brotli or gzip based on the client’s Accept-Encoding header. Options are AUTOMATIC or DISABLED.
Security & Headers
How do I add security to my backend bucket?
Create a gcp.compute.SecurityPolicy (such as type CLOUD_ARMOR_EDGE) and reference its ID in the edgeSecurityPolicy property.
Can I add custom response headers to proxied requests?
Yes, use customResponseHeaders to specify an array of headers that the HTTP/S load balancer will add to proxied responses.
Configuration & Constraints
What are the naming requirements for backend buckets?
The name must be 1-63 characters long and match the regex [a-z](?:[-a-z0-9]{0,61}[a-z0-9])?. It must start with a lowercase letter, contain only lowercase letters, digits, or dashes, and cannot end with a dash. The name is immutable after creation.
What properties can't be changed after creation?
Three properties are immutable: name, project, and params. Plan these values carefully before creating the resource.
What happens to the params property?
The params property passes additional parameters with the request but is not persisted as part of the resource payload. It’s immutable and won’t appear in the resource state.

Using a different cloud?

Explore networking guides for other cloud providers: