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 FREEFrequently Asked Questions
CDN & Caching
loadBalancingScheme is set to INTERNAL_MANAGED. Use classic global external or global application external load balancers if you need CDN functionality.cdnPolicy.cacheKeyPolicy.queryStringWhitelists with an array of query parameter names to include in cache keys (e.g., ["image-version"]).cdnPolicy.cacheKeyPolicy.includeHttpHeaders with header names to include in cache key generation (e.g., ["X-My-Header-Field"]).compressionMode to compress text responses with Brotli or gzip based on the client’s Accept-Encoding header. Options are AUTOMATIC or DISABLED.Security & Headers
gcp.compute.SecurityPolicy (such as type CLOUD_ARMOR_EDGE) and reference its ID in the edgeSecurityPolicy property.customResponseHeaders to specify an array of headers that the HTTP/S load balancer will add to proxied responses.Configuration & Constraints
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.name, project, and params. Plan these values carefully before creating the resource.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: