The gcp:compute/backendBucket:BackendBucket resource, part of the Pulumi GCP provider, connects Cloud Storage buckets to HTTP(S) load balancers, enabling static content delivery with optional CDN acceleration and security policies. This guide focuses on three capabilities: basic bucket attachment with CDN, Cloud Armor edge security, and CDN cache key customization.
Backend buckets reference existing Cloud Storage buckets and optionally Cloud Armor security policies. The examples are intentionally small. Combine them with your own load balancer configuration and URL maps.
Serve static content from Cloud Storage
HTTP(S) load balancers often serve static assets like images or JavaScript files directly from Cloud Storage without routing through compute instances.
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. When enableCdn is true, Google’s CDN caches content at edge locations globally, reducing latency and origin load for static assets.
Protect content with Cloud Armor edge policies
Applications serving content globally need protection against DDoS attacks and malicious traffic at the edge.
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 to apply rules at CDN edge locations before requests reach your bucket.
Control CDN caching with query string filters
CDN caching behavior often needs to vary based on query parameters, allowing different versions of the same resource to be 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 affect cache keys. Here, requests with different “image-version” values are cached separately, while other query parameters are ignored for caching purposes.
Vary cache keys by custom HTTP headers
Some applications need to cache different content based on custom headers, such as API versions or feature flags.
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 the cache key. Requests with different “X-My-Header-Field” values are cached separately, enabling header-based content variation.
Beyond these examples
These snippets focus on specific backend bucket features: CDN enablement and cache key policies, 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 Cloud Armor security policies for the security example. They focus on backend bucket configuration rather than provisioning the complete load balancing stack.
To keep things focused, common backend bucket patterns are omitted, including:
- Response compression (compressionMode)
- Custom response headers (customResponseHeaders)
- Internal load balancing (loadBalancingScheme)
- Cache mode and TTL configuration
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 BackendBucket 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
Common Issues & Limitations
loadBalancingScheme is set to INTERNAL_MANAGED. Use external load balancing schemes if you need CDN, or disable CDN for internal managed load balancers.name, project, and params properties are immutable. Changing these requires recreating the backend bucket, so plan your configuration carefully.[a-z][-a-z0-9]*[a-z0-9]?.CDN & Caching Configuration
cdnPolicy.cacheKeyPolicy.queryStringWhitelists with an array of query parameter names to include in the cache key (e.g., ["image-version"]).cdnPolicy.cacheKeyPolicy.includeHttpHeaders with the header names you want to include (e.g., ["X-My-Header-Field"]).compressionMode to enable Brotli or gzip compression based on the client’s Accept-Encoding header. Options are AUTOMATIC (enabled) or DISABLED.Security & Custom Headers
edgeSecurityPolicy to the ID of a gcp.compute.SecurityPolicy resource. For edge security, use type CLOUD_ARMOR_EDGE.customResponseHeaders with an array of header strings that the HTTP/S load balancer will add to proxied responses.Architecture & Use Cases
Using a different cloud?
Explore networking guides for other cloud providers: