The gcp:compute/regionNetworkEndpointGroup:RegionNetworkEndpointGroup resource, part of the Pulumi GCP provider, defines regional network endpoint groups that serve as load balancer backends for serverless services, Private Service Connect endpoints, or external targets. This guide focuses on three capabilities: serverless backend configuration, Private Service Connect for Google APIs and service attachments, and external endpoint routing.
Regional NEGs reference existing Cloud Run services, Cloud Functions, App Engine versions, VPC networks, or PSC service attachments. The examples are intentionally small. Combine them with your own load balancer configuration and backend services.
Route traffic to Cloud Run services
Load balancers distribute traffic to serverless Cloud Run services through regional NEGs that automatically track service revisions.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const cloudrunNegService = new gcp.cloudrun.Service("cloudrun_neg", {
name: "cloudrun-neg",
location: "us-central1",
template: {
spec: {
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/hello",
}],
},
},
traffics: [{
percent: 100,
latestRevision: true,
}],
});
// Cloud Run Example
const cloudrunNeg = new gcp.compute.RegionNetworkEndpointGroup("cloudrun_neg", {
name: "cloudrun-neg",
networkEndpointType: "SERVERLESS",
region: "us-central1",
cloudRun: {
service: cloudrunNegService.name,
},
});
import pulumi
import pulumi_gcp as gcp
cloudrun_neg_service = gcp.cloudrun.Service("cloudrun_neg",
name="cloudrun-neg",
location="us-central1",
template={
"spec": {
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/hello",
}],
},
},
traffics=[{
"percent": 100,
"latest_revision": True,
}])
# Cloud Run Example
cloudrun_neg = gcp.compute.RegionNetworkEndpointGroup("cloudrun_neg",
name="cloudrun-neg",
network_endpoint_type="SERVERLESS",
region="us-central1",
cloud_run={
"service": cloudrun_neg_service.name,
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrun"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
cloudrunNegService, err := cloudrun.NewService(ctx, "cloudrun_neg", &cloudrun.ServiceArgs{
Name: pulumi.String("cloudrun-neg"),
Location: pulumi.String("us-central1"),
Template: &cloudrun.ServiceTemplateArgs{
Spec: &cloudrun.ServiceTemplateSpecArgs{
Containers: cloudrun.ServiceTemplateSpecContainerArray{
&cloudrun.ServiceTemplateSpecContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/hello"),
},
},
},
},
Traffics: cloudrun.ServiceTrafficArray{
&cloudrun.ServiceTrafficArgs{
Percent: pulumi.Int(100),
LatestRevision: pulumi.Bool(true),
},
},
})
if err != nil {
return err
}
// Cloud Run Example
_, err = compute.NewRegionNetworkEndpointGroup(ctx, "cloudrun_neg", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("cloudrun-neg"),
NetworkEndpointType: pulumi.String("SERVERLESS"),
Region: pulumi.String("us-central1"),
CloudRun: &compute.RegionNetworkEndpointGroupCloudRunArgs{
Service: cloudrunNegService.Name,
},
})
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 cloudrunNegService = new Gcp.CloudRun.Service("cloudrun_neg", new()
{
Name = "cloudrun-neg",
Location = "us-central1",
Template = new Gcp.CloudRun.Inputs.ServiceTemplateArgs
{
Spec = new Gcp.CloudRun.Inputs.ServiceTemplateSpecArgs
{
Containers = new[]
{
new Gcp.CloudRun.Inputs.ServiceTemplateSpecContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/hello",
},
},
},
},
Traffics = new[]
{
new Gcp.CloudRun.Inputs.ServiceTrafficArgs
{
Percent = 100,
LatestRevision = true,
},
},
});
// Cloud Run Example
var cloudrunNeg = new Gcp.Compute.RegionNetworkEndpointGroup("cloudrun_neg", new()
{
Name = "cloudrun-neg",
NetworkEndpointType = "SERVERLESS",
Region = "us-central1",
CloudRun = new Gcp.Compute.Inputs.RegionNetworkEndpointGroupCloudRunArgs
{
Service = cloudrunNegService.Name,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudrun.Service;
import com.pulumi.gcp.cloudrun.ServiceArgs;
import com.pulumi.gcp.cloudrun.inputs.ServiceTemplateArgs;
import com.pulumi.gcp.cloudrun.inputs.ServiceTemplateSpecArgs;
import com.pulumi.gcp.cloudrun.inputs.ServiceTrafficArgs;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
import com.pulumi.gcp.compute.inputs.RegionNetworkEndpointGroupCloudRunArgs;
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 cloudrunNegService = new Service("cloudrunNegService", ServiceArgs.builder()
.name("cloudrun-neg")
.location("us-central1")
.template(ServiceTemplateArgs.builder()
.spec(ServiceTemplateSpecArgs.builder()
.containers(ServiceTemplateSpecContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/hello")
.build())
.build())
.build())
.traffics(ServiceTrafficArgs.builder()
.percent(100)
.latestRevision(true)
.build())
.build());
// Cloud Run Example
var cloudrunNeg = new RegionNetworkEndpointGroup("cloudrunNeg", RegionNetworkEndpointGroupArgs.builder()
.name("cloudrun-neg")
.networkEndpointType("SERVERLESS")
.region("us-central1")
.cloudRun(RegionNetworkEndpointGroupCloudRunArgs.builder()
.service(cloudrunNegService.name())
.build())
.build());
}
}
resources:
# Cloud Run Example
cloudrunNeg:
type: gcp:compute:RegionNetworkEndpointGroup
name: cloudrun_neg
properties:
name: cloudrun-neg
networkEndpointType: SERVERLESS
region: us-central1
cloudRun:
service: ${cloudrunNegService.name}
cloudrunNegService:
type: gcp:cloudrun:Service
name: cloudrun_neg
properties:
name: cloudrun-neg
location: us-central1
template:
spec:
containers:
- image: us-docker.pkg.dev/cloudrun/container/hello
traffics:
- percent: 100
latestRevision: true
The networkEndpointType property set to SERVERLESS indicates this NEG targets a serverless backend. The cloudRun block specifies which Cloud Run service to route to by name. The NEG automatically tracks the service’s active revisions without requiring manual endpoint updates.
Route traffic to Cloud Functions
Cloud Functions serve as load balancer backends through regional NEGs that reference the function by name.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const bucket = new gcp.storage.Bucket("bucket", {
name: "cloudfunctions-function-example-bucket",
location: "US",
});
const archive = new gcp.storage.BucketObject("archive", {
name: "index.zip",
bucket: bucket.name,
source: new pulumi.asset.FileAsset("path/to/index.zip"),
});
const functionNegFunction = new gcp.cloudfunctions.Function("function_neg", {
name: "function-neg",
description: "My function",
runtime: "nodejs20",
availableMemoryMb: 128,
sourceArchiveBucket: bucket.name,
sourceArchiveObject: archive.name,
triggerHttp: true,
timeout: 60,
entryPoint: "helloGET",
});
// Cloud Functions Example
const functionNeg = new gcp.compute.RegionNetworkEndpointGroup("function_neg", {
name: "function-neg",
networkEndpointType: "SERVERLESS",
region: "us-central1",
cloudFunction: {
"function": functionNegFunction.name,
},
});
import pulumi
import pulumi_gcp as gcp
bucket = gcp.storage.Bucket("bucket",
name="cloudfunctions-function-example-bucket",
location="US")
archive = gcp.storage.BucketObject("archive",
name="index.zip",
bucket=bucket.name,
source=pulumi.FileAsset("path/to/index.zip"))
function_neg_function = gcp.cloudfunctions.Function("function_neg",
name="function-neg",
description="My function",
runtime="nodejs20",
available_memory_mb=128,
source_archive_bucket=bucket.name,
source_archive_object=archive.name,
trigger_http=True,
timeout=60,
entry_point="helloGET")
# Cloud Functions Example
function_neg = gcp.compute.RegionNetworkEndpointGroup("function_neg",
name="function-neg",
network_endpoint_type="SERVERLESS",
region="us-central1",
cloud_function={
"function": function_neg_function.name,
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudfunctions"
"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 {
bucket, err := storage.NewBucket(ctx, "bucket", &storage.BucketArgs{
Name: pulumi.String("cloudfunctions-function-example-bucket"),
Location: pulumi.String("US"),
})
if err != nil {
return err
}
archive, err := storage.NewBucketObject(ctx, "archive", &storage.BucketObjectArgs{
Name: pulumi.String("index.zip"),
Bucket: bucket.Name,
Source: pulumi.NewFileAsset("path/to/index.zip"),
})
if err != nil {
return err
}
functionNegFunction, err := cloudfunctions.NewFunction(ctx, "function_neg", &cloudfunctions.FunctionArgs{
Name: pulumi.String("function-neg"),
Description: pulumi.String("My function"),
Runtime: pulumi.String("nodejs20"),
AvailableMemoryMb: pulumi.Int(128),
SourceArchiveBucket: bucket.Name,
SourceArchiveObject: archive.Name,
TriggerHttp: pulumi.Bool(true),
Timeout: pulumi.Int(60),
EntryPoint: pulumi.String("helloGET"),
})
if err != nil {
return err
}
// Cloud Functions Example
_, err = compute.NewRegionNetworkEndpointGroup(ctx, "function_neg", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("function-neg"),
NetworkEndpointType: pulumi.String("SERVERLESS"),
Region: pulumi.String("us-central1"),
CloudFunction: &compute.RegionNetworkEndpointGroupCloudFunctionArgs{
Function: functionNegFunction.Name,
},
})
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 bucket = new Gcp.Storage.Bucket("bucket", new()
{
Name = "cloudfunctions-function-example-bucket",
Location = "US",
});
var archive = new Gcp.Storage.BucketObject("archive", new()
{
Name = "index.zip",
Bucket = bucket.Name,
Source = new FileAsset("path/to/index.zip"),
});
var functionNegFunction = new Gcp.CloudFunctions.Function("function_neg", new()
{
Name = "function-neg",
Description = "My function",
Runtime = "nodejs20",
AvailableMemoryMb = 128,
SourceArchiveBucket = bucket.Name,
SourceArchiveObject = archive.Name,
TriggerHttp = true,
Timeout = 60,
EntryPoint = "helloGET",
});
// Cloud Functions Example
var functionNeg = new Gcp.Compute.RegionNetworkEndpointGroup("function_neg", new()
{
Name = "function-neg",
NetworkEndpointType = "SERVERLESS",
Region = "us-central1",
CloudFunction = new Gcp.Compute.Inputs.RegionNetworkEndpointGroupCloudFunctionArgs
{
Function = functionNegFunction.Name,
},
});
});
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.storage.BucketObject;
import com.pulumi.gcp.storage.BucketObjectArgs;
import com.pulumi.gcp.cloudfunctions.Function;
import com.pulumi.gcp.cloudfunctions.FunctionArgs;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
import com.pulumi.gcp.compute.inputs.RegionNetworkEndpointGroupCloudFunctionArgs;
import com.pulumi.asset.FileAsset;
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 bucket = new Bucket("bucket", BucketArgs.builder()
.name("cloudfunctions-function-example-bucket")
.location("US")
.build());
var archive = new BucketObject("archive", BucketObjectArgs.builder()
.name("index.zip")
.bucket(bucket.name())
.source(new FileAsset("path/to/index.zip"))
.build());
var functionNegFunction = new Function("functionNegFunction", FunctionArgs.builder()
.name("function-neg")
.description("My function")
.runtime("nodejs20")
.availableMemoryMb(128)
.sourceArchiveBucket(bucket.name())
.sourceArchiveObject(archive.name())
.triggerHttp(true)
.timeout(60)
.entryPoint("helloGET")
.build());
// Cloud Functions Example
var functionNeg = new RegionNetworkEndpointGroup("functionNeg", RegionNetworkEndpointGroupArgs.builder()
.name("function-neg")
.networkEndpointType("SERVERLESS")
.region("us-central1")
.cloudFunction(RegionNetworkEndpointGroupCloudFunctionArgs.builder()
.function(functionNegFunction.name())
.build())
.build());
}
}
resources:
# Cloud Functions Example
functionNeg:
type: gcp:compute:RegionNetworkEndpointGroup
name: function_neg
properties:
name: function-neg
networkEndpointType: SERVERLESS
region: us-central1
cloudFunction:
function: ${functionNegFunction.name}
functionNegFunction:
type: gcp:cloudfunctions:Function
name: function_neg
properties:
name: function-neg
description: My function
runtime: nodejs20
availableMemoryMb: 128
sourceArchiveBucket: ${bucket.name}
sourceArchiveObject: ${archive.name}
triggerHttp: true
timeout: 60
entryPoint: helloGET
bucket:
type: gcp:storage:Bucket
properties:
name: cloudfunctions-function-example-bucket
location: US
archive:
type: gcp:storage:BucketObject
properties:
name: index.zip
bucket: ${bucket.name}
source:
fn::FileAsset: path/to/index.zip
Similar to Cloud Run, the cloudFunction block identifies the target function. Only one serverless backend type (cloudRun, cloudFunction, or appEngine) can be specified per NEG.
Route traffic to App Engine services
App Engine applications receive load-balanced traffic by specifying both service name and version ID.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const appengineNegBucket = new gcp.storage.Bucket("appengine_neg", {
name: "appengine-neg",
location: "US",
uniformBucketLevelAccess: true,
});
const appengineNegBucketObject = new gcp.storage.BucketObject("appengine_neg", {
name: "hello-world.zip",
bucket: appengineNegBucket.name,
source: new pulumi.asset.FileAsset("./test-fixtures/hello-world.zip"),
});
const appengineNegFlexibleAppVersion = new gcp.appengine.FlexibleAppVersion("appengine_neg", {
versionId: "v1",
service: "appengine-neg",
runtime: "nodejs",
flexibleRuntimeSettings: {
operatingSystem: "ubuntu22",
runtimeVersion: "20",
},
entrypoint: {
shell: "node ./app.js",
},
deployment: {
zip: {
sourceUrl: pulumi.interpolate`https://storage.googleapis.com/${appengineNegBucket.name}/${appengineNegBucketObject.name}`,
},
},
livenessCheck: {
path: "/",
},
readinessCheck: {
path: "/",
},
envVariables: {
port: "8080",
},
handlers: [{
urlRegex: ".*\\/my-path\\/*",
securityLevel: "SECURE_ALWAYS",
login: "LOGIN_REQUIRED",
authFailAction: "AUTH_FAIL_ACTION_REDIRECT",
staticFiles: {
path: "my-other-path",
uploadPathRegex: ".*\\/my-path\\/*",
},
}],
automaticScaling: {
coolDownPeriod: "120s",
cpuUtilization: {
targetUtilization: 0.5,
},
},
deleteServiceOnDestroy: true,
});
// App Engine Example
const appengineNeg = new gcp.compute.RegionNetworkEndpointGroup("appengine_neg", {
name: "appengine-neg",
networkEndpointType: "SERVERLESS",
region: "us-central1",
appEngine: {
service: appengineNegFlexibleAppVersion.service,
version: appengineNegFlexibleAppVersion.versionId,
},
});
import pulumi
import pulumi_gcp as gcp
appengine_neg_bucket = gcp.storage.Bucket("appengine_neg",
name="appengine-neg",
location="US",
uniform_bucket_level_access=True)
appengine_neg_bucket_object = gcp.storage.BucketObject("appengine_neg",
name="hello-world.zip",
bucket=appengine_neg_bucket.name,
source=pulumi.FileAsset("./test-fixtures/hello-world.zip"))
appengine_neg_flexible_app_version = gcp.appengine.FlexibleAppVersion("appengine_neg",
version_id="v1",
service="appengine-neg",
runtime="nodejs",
flexible_runtime_settings={
"operating_system": "ubuntu22",
"runtime_version": "20",
},
entrypoint={
"shell": "node ./app.js",
},
deployment={
"zip": {
"source_url": pulumi.Output.all(
appengineNegBucketName=appengine_neg_bucket.name,
appengineNegBucketObjectName=appengine_neg_bucket_object.name
).apply(lambda resolved_outputs: f"https://storage.googleapis.com/{resolved_outputs['appengineNegBucketName']}/{resolved_outputs['appengineNegBucketObjectName']}")
,
},
},
liveness_check={
"path": "/",
},
readiness_check={
"path": "/",
},
env_variables={
"port": "8080",
},
handlers=[{
"url_regex": ".*\\/my-path\\/*",
"security_level": "SECURE_ALWAYS",
"login": "LOGIN_REQUIRED",
"auth_fail_action": "AUTH_FAIL_ACTION_REDIRECT",
"static_files": {
"path": "my-other-path",
"upload_path_regex": ".*\\/my-path\\/*",
},
}],
automatic_scaling={
"cool_down_period": "120s",
"cpu_utilization": {
"target_utilization": 0.5,
},
},
delete_service_on_destroy=True)
# App Engine Example
appengine_neg = gcp.compute.RegionNetworkEndpointGroup("appengine_neg",
name="appengine-neg",
network_endpoint_type="SERVERLESS",
region="us-central1",
app_engine={
"service": appengine_neg_flexible_app_version.service,
"version": appengine_neg_flexible_app_version.version_id,
})
package main
import (
"fmt"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/appengine"
"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 {
appengineNegBucket, err := storage.NewBucket(ctx, "appengine_neg", &storage.BucketArgs{
Name: pulumi.String("appengine-neg"),
Location: pulumi.String("US"),
UniformBucketLevelAccess: pulumi.Bool(true),
})
if err != nil {
return err
}
appengineNegBucketObject, err := storage.NewBucketObject(ctx, "appengine_neg", &storage.BucketObjectArgs{
Name: pulumi.String("hello-world.zip"),
Bucket: appengineNegBucket.Name,
Source: pulumi.NewFileAsset("./test-fixtures/hello-world.zip"),
})
if err != nil {
return err
}
appengineNegFlexibleAppVersion, err := appengine.NewFlexibleAppVersion(ctx, "appengine_neg", &appengine.FlexibleAppVersionArgs{
VersionId: pulumi.String("v1"),
Service: pulumi.String("appengine-neg"),
Runtime: pulumi.String("nodejs"),
FlexibleRuntimeSettings: &appengine.FlexibleAppVersionFlexibleRuntimeSettingsArgs{
OperatingSystem: pulumi.String("ubuntu22"),
RuntimeVersion: pulumi.String("20"),
},
Entrypoint: &appengine.FlexibleAppVersionEntrypointArgs{
Shell: pulumi.String("node ./app.js"),
},
Deployment: &appengine.FlexibleAppVersionDeploymentArgs{
Zip: &appengine.FlexibleAppVersionDeploymentZipArgs{
SourceUrl: pulumi.All(appengineNegBucket.Name, appengineNegBucketObject.Name).ApplyT(func(_args []interface{}) (string, error) {
appengineNegBucketName := _args[0].(string)
appengineNegBucketObjectName := _args[1].(string)
return fmt.Sprintf("https://storage.googleapis.com/%v/%v", appengineNegBucketName, appengineNegBucketObjectName), nil
}).(pulumi.StringOutput),
},
},
LivenessCheck: &appengine.FlexibleAppVersionLivenessCheckArgs{
Path: pulumi.String("/"),
},
ReadinessCheck: &appengine.FlexibleAppVersionReadinessCheckArgs{
Path: pulumi.String("/"),
},
EnvVariables: pulumi.StringMap{
"port": pulumi.String("8080"),
},
Handlers: appengine.FlexibleAppVersionHandlerArray{
&appengine.FlexibleAppVersionHandlerArgs{
UrlRegex: pulumi.String(".*\\/my-path\\/*"),
SecurityLevel: pulumi.String("SECURE_ALWAYS"),
Login: pulumi.String("LOGIN_REQUIRED"),
AuthFailAction: pulumi.String("AUTH_FAIL_ACTION_REDIRECT"),
StaticFiles: &appengine.FlexibleAppVersionHandlerStaticFilesArgs{
Path: pulumi.String("my-other-path"),
UploadPathRegex: pulumi.String(".*\\/my-path\\/*"),
},
},
},
AutomaticScaling: &appengine.FlexibleAppVersionAutomaticScalingArgs{
CoolDownPeriod: pulumi.String("120s"),
CpuUtilization: &appengine.FlexibleAppVersionAutomaticScalingCpuUtilizationArgs{
TargetUtilization: pulumi.Float64(0.5),
},
},
DeleteServiceOnDestroy: pulumi.Bool(true),
})
if err != nil {
return err
}
// App Engine Example
_, err = compute.NewRegionNetworkEndpointGroup(ctx, "appengine_neg", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("appengine-neg"),
NetworkEndpointType: pulumi.String("SERVERLESS"),
Region: pulumi.String("us-central1"),
AppEngine: &compute.RegionNetworkEndpointGroupAppEngineArgs{
Service: appengineNegFlexibleAppVersion.Service,
Version: appengineNegFlexibleAppVersion.VersionId,
},
})
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 appengineNegBucket = new Gcp.Storage.Bucket("appengine_neg", new()
{
Name = "appengine-neg",
Location = "US",
UniformBucketLevelAccess = true,
});
var appengineNegBucketObject = new Gcp.Storage.BucketObject("appengine_neg", new()
{
Name = "hello-world.zip",
Bucket = appengineNegBucket.Name,
Source = new FileAsset("./test-fixtures/hello-world.zip"),
});
var appengineNegFlexibleAppVersion = new Gcp.AppEngine.FlexibleAppVersion("appengine_neg", new()
{
VersionId = "v1",
Service = "appengine-neg",
Runtime = "nodejs",
FlexibleRuntimeSettings = new Gcp.AppEngine.Inputs.FlexibleAppVersionFlexibleRuntimeSettingsArgs
{
OperatingSystem = "ubuntu22",
RuntimeVersion = "20",
},
Entrypoint = new Gcp.AppEngine.Inputs.FlexibleAppVersionEntrypointArgs
{
Shell = "node ./app.js",
},
Deployment = new Gcp.AppEngine.Inputs.FlexibleAppVersionDeploymentArgs
{
Zip = new Gcp.AppEngine.Inputs.FlexibleAppVersionDeploymentZipArgs
{
SourceUrl = Output.Tuple(appengineNegBucket.Name, appengineNegBucketObject.Name).Apply(values =>
{
var appengineNegBucketName = values.Item1;
var appengineNegBucketObjectName = values.Item2;
return $"https://storage.googleapis.com/{appengineNegBucketName}/{appengineNegBucketObjectName}";
}),
},
},
LivenessCheck = new Gcp.AppEngine.Inputs.FlexibleAppVersionLivenessCheckArgs
{
Path = "/",
},
ReadinessCheck = new Gcp.AppEngine.Inputs.FlexibleAppVersionReadinessCheckArgs
{
Path = "/",
},
EnvVariables =
{
{ "port", "8080" },
},
Handlers = new[]
{
new Gcp.AppEngine.Inputs.FlexibleAppVersionHandlerArgs
{
UrlRegex = ".*\\/my-path\\/*",
SecurityLevel = "SECURE_ALWAYS",
Login = "LOGIN_REQUIRED",
AuthFailAction = "AUTH_FAIL_ACTION_REDIRECT",
StaticFiles = new Gcp.AppEngine.Inputs.FlexibleAppVersionHandlerStaticFilesArgs
{
Path = "my-other-path",
UploadPathRegex = ".*\\/my-path\\/*",
},
},
},
AutomaticScaling = new Gcp.AppEngine.Inputs.FlexibleAppVersionAutomaticScalingArgs
{
CoolDownPeriod = "120s",
CpuUtilization = new Gcp.AppEngine.Inputs.FlexibleAppVersionAutomaticScalingCpuUtilizationArgs
{
TargetUtilization = 0.5,
},
},
DeleteServiceOnDestroy = true,
});
// App Engine Example
var appengineNeg = new Gcp.Compute.RegionNetworkEndpointGroup("appengine_neg", new()
{
Name = "appengine-neg",
NetworkEndpointType = "SERVERLESS",
Region = "us-central1",
AppEngine = new Gcp.Compute.Inputs.RegionNetworkEndpointGroupAppEngineArgs
{
Service = appengineNegFlexibleAppVersion.Service,
Version = appengineNegFlexibleAppVersion.VersionId,
},
});
});
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.storage.BucketObject;
import com.pulumi.gcp.storage.BucketObjectArgs;
import com.pulumi.gcp.appengine.FlexibleAppVersion;
import com.pulumi.gcp.appengine.FlexibleAppVersionArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionFlexibleRuntimeSettingsArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionEntrypointArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionDeploymentArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionDeploymentZipArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionLivenessCheckArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionReadinessCheckArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionHandlerArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionHandlerStaticFilesArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionAutomaticScalingArgs;
import com.pulumi.gcp.appengine.inputs.FlexibleAppVersionAutomaticScalingCpuUtilizationArgs;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
import com.pulumi.gcp.compute.inputs.RegionNetworkEndpointGroupAppEngineArgs;
import com.pulumi.asset.FileAsset;
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 appengineNegBucket = new Bucket("appengineNegBucket", BucketArgs.builder()
.name("appengine-neg")
.location("US")
.uniformBucketLevelAccess(true)
.build());
var appengineNegBucketObject = new BucketObject("appengineNegBucketObject", BucketObjectArgs.builder()
.name("hello-world.zip")
.bucket(appengineNegBucket.name())
.source(new FileAsset("./test-fixtures/hello-world.zip"))
.build());
var appengineNegFlexibleAppVersion = new FlexibleAppVersion("appengineNegFlexibleAppVersion", FlexibleAppVersionArgs.builder()
.versionId("v1")
.service("appengine-neg")
.runtime("nodejs")
.flexibleRuntimeSettings(FlexibleAppVersionFlexibleRuntimeSettingsArgs.builder()
.operatingSystem("ubuntu22")
.runtimeVersion("20")
.build())
.entrypoint(FlexibleAppVersionEntrypointArgs.builder()
.shell("node ./app.js")
.build())
.deployment(FlexibleAppVersionDeploymentArgs.builder()
.zip(FlexibleAppVersionDeploymentZipArgs.builder()
.sourceUrl(Output.tuple(appengineNegBucket.name(), appengineNegBucketObject.name()).applyValue(values -> {
var appengineNegBucketName = values.t1;
var appengineNegBucketObjectName = values.t2;
return String.format("https://storage.googleapis.com/%s/%s", appengineNegBucketName,appengineNegBucketObjectName);
}))
.build())
.build())
.livenessCheck(FlexibleAppVersionLivenessCheckArgs.builder()
.path("/")
.build())
.readinessCheck(FlexibleAppVersionReadinessCheckArgs.builder()
.path("/")
.build())
.envVariables(Map.of("port", "8080"))
.handlers(FlexibleAppVersionHandlerArgs.builder()
.urlRegex(".*\\/my-path\\/*")
.securityLevel("SECURE_ALWAYS")
.login("LOGIN_REQUIRED")
.authFailAction("AUTH_FAIL_ACTION_REDIRECT")
.staticFiles(FlexibleAppVersionHandlerStaticFilesArgs.builder()
.path("my-other-path")
.uploadPathRegex(".*\\/my-path\\/*")
.build())
.build())
.automaticScaling(FlexibleAppVersionAutomaticScalingArgs.builder()
.coolDownPeriod("120s")
.cpuUtilization(FlexibleAppVersionAutomaticScalingCpuUtilizationArgs.builder()
.targetUtilization(0.5)
.build())
.build())
.deleteServiceOnDestroy(true)
.build());
// App Engine Example
var appengineNeg = new RegionNetworkEndpointGroup("appengineNeg", RegionNetworkEndpointGroupArgs.builder()
.name("appengine-neg")
.networkEndpointType("SERVERLESS")
.region("us-central1")
.appEngine(RegionNetworkEndpointGroupAppEngineArgs.builder()
.service(appengineNegFlexibleAppVersion.service())
.version(appengineNegFlexibleAppVersion.versionId())
.build())
.build());
}
}
resources:
# App Engine Example
appengineNeg:
type: gcp:compute:RegionNetworkEndpointGroup
name: appengine_neg
properties:
name: appengine-neg
networkEndpointType: SERVERLESS
region: us-central1
appEngine:
service: ${appengineNegFlexibleAppVersion.service}
version: ${appengineNegFlexibleAppVersion.versionId}
appengineNegFlexibleAppVersion:
type: gcp:appengine:FlexibleAppVersion
name: appengine_neg
properties:
versionId: v1
service: appengine-neg
runtime: nodejs
flexibleRuntimeSettings:
operatingSystem: ubuntu22
runtimeVersion: '20'
entrypoint:
shell: node ./app.js
deployment:
zip:
sourceUrl: https://storage.googleapis.com/${appengineNegBucket.name}/${appengineNegBucketObject.name}
livenessCheck:
path: /
readinessCheck:
path: /
envVariables:
port: '8080'
handlers:
- urlRegex: .*\/my-path\/*
securityLevel: SECURE_ALWAYS
login: LOGIN_REQUIRED
authFailAction: AUTH_FAIL_ACTION_REDIRECT
staticFiles:
path: my-other-path
uploadPathRegex: .*\/my-path\/*
automaticScaling:
coolDownPeriod: 120s
cpuUtilization:
targetUtilization: 0.5
deleteServiceOnDestroy: true
appengineNegBucket:
type: gcp:storage:Bucket
name: appengine_neg
properties:
name: appengine-neg
location: US
uniformBucketLevelAccess: true
appengineNegBucketObject:
type: gcp:storage:BucketObject
name: appengine_neg
properties:
name: hello-world.zip
bucket: ${appengineNegBucket.name}
source:
fn::FileAsset: ./test-fixtures/hello-world.zip
The appEngine block requires both service and version properties to target a specific App Engine deployment. This provides version-level routing control compared to the service-level routing in Cloud Run and Cloud Functions examples.
Connect to Google APIs via Private Service Connect
Private Service Connect provides private connectivity to Google APIs without traversing the public internet.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const pscNeg = new gcp.compute.RegionNetworkEndpointGroup("psc_neg", {
name: "psc-neg",
region: "asia-northeast3",
networkEndpointType: "PRIVATE_SERVICE_CONNECT",
pscTargetService: "asia-northeast3-cloudkms.googleapis.com",
});
import pulumi
import pulumi_gcp as gcp
psc_neg = gcp.compute.RegionNetworkEndpointGroup("psc_neg",
name="psc-neg",
region="asia-northeast3",
network_endpoint_type="PRIVATE_SERVICE_CONNECT",
psc_target_service="asia-northeast3-cloudkms.googleapis.com")
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := compute.NewRegionNetworkEndpointGroup(ctx, "psc_neg", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("psc-neg"),
Region: pulumi.String("asia-northeast3"),
NetworkEndpointType: pulumi.String("PRIVATE_SERVICE_CONNECT"),
PscTargetService: pulumi.String("asia-northeast3-cloudkms.googleapis.com"),
})
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 pscNeg = new Gcp.Compute.RegionNetworkEndpointGroup("psc_neg", new()
{
Name = "psc-neg",
Region = "asia-northeast3",
NetworkEndpointType = "PRIVATE_SERVICE_CONNECT",
PscTargetService = "asia-northeast3-cloudkms.googleapis.com",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
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 pscNeg = new RegionNetworkEndpointGroup("pscNeg", RegionNetworkEndpointGroupArgs.builder()
.name("psc-neg")
.region("asia-northeast3")
.networkEndpointType("PRIVATE_SERVICE_CONNECT")
.pscTargetService("asia-northeast3-cloudkms.googleapis.com")
.build());
}
}
resources:
pscNeg:
type: gcp:compute:RegionNetworkEndpointGroup
name: psc_neg
properties:
name: psc-neg
region: asia-northeast3
networkEndpointType: PRIVATE_SERVICE_CONNECT
pscTargetService: asia-northeast3-cloudkms.googleapis.com
The networkEndpointType switches to PRIVATE_SERVICE_CONNECT, and pscTargetService specifies the Google API endpoint URL. This configuration routes traffic through private VPC connectivity rather than the public internet.
Connect to PSC service attachments with port mapping
When PSC service attachments expose multiple ports, you can specify which producer port to target and which subnetwork to use.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.compute.Network("default", {name: "psc-network"});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
name: "psc-subnetwork",
ipCidrRange: "10.0.0.0/16",
region: "europe-west4",
network: _default.id,
});
const pscSubnetwork = new gcp.compute.Subnetwork("psc_subnetwork", {
name: "psc-subnetwork-nat",
ipCidrRange: "10.1.0.0/16",
region: "europe-west4",
purpose: "PRIVATE_SERVICE_CONNECT",
network: _default.id,
});
const defaultHealthCheck = new gcp.compute.HealthCheck("default", {
name: "psc-healthcheck",
checkIntervalSec: 1,
timeoutSec: 1,
tcpHealthCheck: {
port: 80,
},
});
const defaultRegionBackendService = new gcp.compute.RegionBackendService("default", {
name: "psc-backend",
region: "europe-west4",
healthChecks: defaultHealthCheck.id,
});
const defaultForwardingRule = new gcp.compute.ForwardingRule("default", {
name: "psc-forwarding-rule",
region: "europe-west4",
loadBalancingScheme: "INTERNAL",
backendService: defaultRegionBackendService.id,
ports: [
"80",
"88",
"443",
],
network: _default.name,
subnetwork: defaultSubnetwork.name,
});
const defaultServiceAttachment = new gcp.compute.ServiceAttachment("default", {
name: "psc-service-attachment",
region: "europe-west4",
description: "A service attachment configured with Terraform",
enableProxyProtocol: false,
connectionPreference: "ACCEPT_AUTOMATIC",
natSubnets: [pscSubnetwork.selfLink],
targetService: defaultForwardingRule.selfLink,
});
const pscNegServiceAttachment = new gcp.compute.RegionNetworkEndpointGroup("psc_neg_service_attachment", {
name: "psc-neg",
region: "europe-west4",
networkEndpointType: "PRIVATE_SERVICE_CONNECT",
pscTargetService: defaultServiceAttachment.selfLink,
pscData: {
producerPort: "88",
},
subnetwork: defaultSubnetwork.selfLink,
});
import pulumi
import pulumi_gcp as gcp
default = gcp.compute.Network("default", name="psc-network")
default_subnetwork = gcp.compute.Subnetwork("default",
name="psc-subnetwork",
ip_cidr_range="10.0.0.0/16",
region="europe-west4",
network=default.id)
psc_subnetwork = gcp.compute.Subnetwork("psc_subnetwork",
name="psc-subnetwork-nat",
ip_cidr_range="10.1.0.0/16",
region="europe-west4",
purpose="PRIVATE_SERVICE_CONNECT",
network=default.id)
default_health_check = gcp.compute.HealthCheck("default",
name="psc-healthcheck",
check_interval_sec=1,
timeout_sec=1,
tcp_health_check={
"port": 80,
})
default_region_backend_service = gcp.compute.RegionBackendService("default",
name="psc-backend",
region="europe-west4",
health_checks=default_health_check.id)
default_forwarding_rule = gcp.compute.ForwardingRule("default",
name="psc-forwarding-rule",
region="europe-west4",
load_balancing_scheme="INTERNAL",
backend_service=default_region_backend_service.id,
ports=[
"80",
"88",
"443",
],
network=default.name,
subnetwork=default_subnetwork.name)
default_service_attachment = gcp.compute.ServiceAttachment("default",
name="psc-service-attachment",
region="europe-west4",
description="A service attachment configured with Terraform",
enable_proxy_protocol=False,
connection_preference="ACCEPT_AUTOMATIC",
nat_subnets=[psc_subnetwork.self_link],
target_service=default_forwarding_rule.self_link)
psc_neg_service_attachment = gcp.compute.RegionNetworkEndpointGroup("psc_neg_service_attachment",
name="psc-neg",
region="europe-west4",
network_endpoint_type="PRIVATE_SERVICE_CONNECT",
psc_target_service=default_service_attachment.self_link,
psc_data={
"producer_port": "88",
},
subnetwork=default_subnetwork.self_link)
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_default, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
Name: pulumi.String("psc-network"),
})
if err != nil {
return err
}
defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
Name: pulumi.String("psc-subnetwork"),
IpCidrRange: pulumi.String("10.0.0.0/16"),
Region: pulumi.String("europe-west4"),
Network: _default.ID(),
})
if err != nil {
return err
}
pscSubnetwork, err := compute.NewSubnetwork(ctx, "psc_subnetwork", &compute.SubnetworkArgs{
Name: pulumi.String("psc-subnetwork-nat"),
IpCidrRange: pulumi.String("10.1.0.0/16"),
Region: pulumi.String("europe-west4"),
Purpose: pulumi.String("PRIVATE_SERVICE_CONNECT"),
Network: _default.ID(),
})
if err != nil {
return err
}
defaultHealthCheck, err := compute.NewHealthCheck(ctx, "default", &compute.HealthCheckArgs{
Name: pulumi.String("psc-healthcheck"),
CheckIntervalSec: pulumi.Int(1),
TimeoutSec: pulumi.Int(1),
TcpHealthCheck: &compute.HealthCheckTcpHealthCheckArgs{
Port: pulumi.Int(80),
},
})
if err != nil {
return err
}
defaultRegionBackendService, err := compute.NewRegionBackendService(ctx, "default", &compute.RegionBackendServiceArgs{
Name: pulumi.String("psc-backend"),
Region: pulumi.String("europe-west4"),
HealthChecks: defaultHealthCheck.ID(),
})
if err != nil {
return err
}
defaultForwardingRule, err := compute.NewForwardingRule(ctx, "default", &compute.ForwardingRuleArgs{
Name: pulumi.String("psc-forwarding-rule"),
Region: pulumi.String("europe-west4"),
LoadBalancingScheme: pulumi.String("INTERNAL"),
BackendService: defaultRegionBackendService.ID(),
Ports: pulumi.StringArray{
pulumi.String("80"),
pulumi.String("88"),
pulumi.String("443"),
},
Network: _default.Name,
Subnetwork: defaultSubnetwork.Name,
})
if err != nil {
return err
}
defaultServiceAttachment, err := compute.NewServiceAttachment(ctx, "default", &compute.ServiceAttachmentArgs{
Name: pulumi.String("psc-service-attachment"),
Region: pulumi.String("europe-west4"),
Description: pulumi.String("A service attachment configured with Terraform"),
EnableProxyProtocol: pulumi.Bool(false),
ConnectionPreference: pulumi.String("ACCEPT_AUTOMATIC"),
NatSubnets: pulumi.StringArray{
pscSubnetwork.SelfLink,
},
TargetService: defaultForwardingRule.SelfLink,
})
if err != nil {
return err
}
_, err = compute.NewRegionNetworkEndpointGroup(ctx, "psc_neg_service_attachment", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("psc-neg"),
Region: pulumi.String("europe-west4"),
NetworkEndpointType: pulumi.String("PRIVATE_SERVICE_CONNECT"),
PscTargetService: defaultServiceAttachment.SelfLink,
PscData: &compute.RegionNetworkEndpointGroupPscDataArgs{
ProducerPort: pulumi.String("88"),
},
Subnetwork: defaultSubnetwork.SelfLink,
})
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 @default = new Gcp.Compute.Network("default", new()
{
Name = "psc-network",
});
var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
{
Name = "psc-subnetwork",
IpCidrRange = "10.0.0.0/16",
Region = "europe-west4",
Network = @default.Id,
});
var pscSubnetwork = new Gcp.Compute.Subnetwork("psc_subnetwork", new()
{
Name = "psc-subnetwork-nat",
IpCidrRange = "10.1.0.0/16",
Region = "europe-west4",
Purpose = "PRIVATE_SERVICE_CONNECT",
Network = @default.Id,
});
var defaultHealthCheck = new Gcp.Compute.HealthCheck("default", new()
{
Name = "psc-healthcheck",
CheckIntervalSec = 1,
TimeoutSec = 1,
TcpHealthCheck = new Gcp.Compute.Inputs.HealthCheckTcpHealthCheckArgs
{
Port = 80,
},
});
var defaultRegionBackendService = new Gcp.Compute.RegionBackendService("default", new()
{
Name = "psc-backend",
Region = "europe-west4",
HealthChecks = defaultHealthCheck.Id,
});
var defaultForwardingRule = new Gcp.Compute.ForwardingRule("default", new()
{
Name = "psc-forwarding-rule",
Region = "europe-west4",
LoadBalancingScheme = "INTERNAL",
BackendService = defaultRegionBackendService.Id,
Ports = new[]
{
"80",
"88",
"443",
},
Network = @default.Name,
Subnetwork = defaultSubnetwork.Name,
});
var defaultServiceAttachment = new Gcp.Compute.ServiceAttachment("default", new()
{
Name = "psc-service-attachment",
Region = "europe-west4",
Description = "A service attachment configured with Terraform",
EnableProxyProtocol = false,
ConnectionPreference = "ACCEPT_AUTOMATIC",
NatSubnets = new[]
{
pscSubnetwork.SelfLink,
},
TargetService = defaultForwardingRule.SelfLink,
});
var pscNegServiceAttachment = new Gcp.Compute.RegionNetworkEndpointGroup("psc_neg_service_attachment", new()
{
Name = "psc-neg",
Region = "europe-west4",
NetworkEndpointType = "PRIVATE_SERVICE_CONNECT",
PscTargetService = defaultServiceAttachment.SelfLink,
PscData = new Gcp.Compute.Inputs.RegionNetworkEndpointGroupPscDataArgs
{
ProducerPort = "88",
},
Subnetwork = defaultSubnetwork.SelfLink,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.HealthCheck;
import com.pulumi.gcp.compute.HealthCheckArgs;
import com.pulumi.gcp.compute.inputs.HealthCheckTcpHealthCheckArgs;
import com.pulumi.gcp.compute.RegionBackendService;
import com.pulumi.gcp.compute.RegionBackendServiceArgs;
import com.pulumi.gcp.compute.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
import com.pulumi.gcp.compute.ServiceAttachment;
import com.pulumi.gcp.compute.ServiceAttachmentArgs;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
import com.pulumi.gcp.compute.inputs.RegionNetworkEndpointGroupPscDataArgs;
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 default_ = new Network("default", NetworkArgs.builder()
.name("psc-network")
.build());
var defaultSubnetwork = new Subnetwork("defaultSubnetwork", SubnetworkArgs.builder()
.name("psc-subnetwork")
.ipCidrRange("10.0.0.0/16")
.region("europe-west4")
.network(default_.id())
.build());
var pscSubnetwork = new Subnetwork("pscSubnetwork", SubnetworkArgs.builder()
.name("psc-subnetwork-nat")
.ipCidrRange("10.1.0.0/16")
.region("europe-west4")
.purpose("PRIVATE_SERVICE_CONNECT")
.network(default_.id())
.build());
var defaultHealthCheck = new HealthCheck("defaultHealthCheck", HealthCheckArgs.builder()
.name("psc-healthcheck")
.checkIntervalSec(1)
.timeoutSec(1)
.tcpHealthCheck(HealthCheckTcpHealthCheckArgs.builder()
.port(80)
.build())
.build());
var defaultRegionBackendService = new RegionBackendService("defaultRegionBackendService", RegionBackendServiceArgs.builder()
.name("psc-backend")
.region("europe-west4")
.healthChecks(defaultHealthCheck.id())
.build());
var defaultForwardingRule = new ForwardingRule("defaultForwardingRule", ForwardingRuleArgs.builder()
.name("psc-forwarding-rule")
.region("europe-west4")
.loadBalancingScheme("INTERNAL")
.backendService(defaultRegionBackendService.id())
.ports(
"80",
"88",
"443")
.network(default_.name())
.subnetwork(defaultSubnetwork.name())
.build());
var defaultServiceAttachment = new ServiceAttachment("defaultServiceAttachment", ServiceAttachmentArgs.builder()
.name("psc-service-attachment")
.region("europe-west4")
.description("A service attachment configured with Terraform")
.enableProxyProtocol(false)
.connectionPreference("ACCEPT_AUTOMATIC")
.natSubnets(pscSubnetwork.selfLink())
.targetService(defaultForwardingRule.selfLink())
.build());
var pscNegServiceAttachment = new RegionNetworkEndpointGroup("pscNegServiceAttachment", RegionNetworkEndpointGroupArgs.builder()
.name("psc-neg")
.region("europe-west4")
.networkEndpointType("PRIVATE_SERVICE_CONNECT")
.pscTargetService(defaultServiceAttachment.selfLink())
.pscData(RegionNetworkEndpointGroupPscDataArgs.builder()
.producerPort("88")
.build())
.subnetwork(defaultSubnetwork.selfLink())
.build());
}
}
resources:
default:
type: gcp:compute:Network
properties:
name: psc-network
defaultSubnetwork:
type: gcp:compute:Subnetwork
name: default
properties:
name: psc-subnetwork
ipCidrRange: 10.0.0.0/16
region: europe-west4
network: ${default.id}
pscSubnetwork:
type: gcp:compute:Subnetwork
name: psc_subnetwork
properties:
name: psc-subnetwork-nat
ipCidrRange: 10.1.0.0/16
region: europe-west4
purpose: PRIVATE_SERVICE_CONNECT
network: ${default.id}
defaultHealthCheck:
type: gcp:compute:HealthCheck
name: default
properties:
name: psc-healthcheck
checkIntervalSec: 1
timeoutSec: 1
tcpHealthCheck:
port: '80'
defaultRegionBackendService:
type: gcp:compute:RegionBackendService
name: default
properties:
name: psc-backend
region: europe-west4
healthChecks: ${defaultHealthCheck.id}
defaultForwardingRule:
type: gcp:compute:ForwardingRule
name: default
properties:
name: psc-forwarding-rule
region: europe-west4
loadBalancingScheme: INTERNAL
backendService: ${defaultRegionBackendService.id}
ports:
- '80'
- '88'
- '443'
network: ${default.name}
subnetwork: ${defaultSubnetwork.name}
defaultServiceAttachment:
type: gcp:compute:ServiceAttachment
name: default
properties:
name: psc-service-attachment
region: europe-west4
description: A service attachment configured with Terraform
enableProxyProtocol: false
connectionPreference: ACCEPT_AUTOMATIC
natSubnets:
- ${pscSubnetwork.selfLink}
targetService: ${defaultForwardingRule.selfLink}
pscNegServiceAttachment:
type: gcp:compute:RegionNetworkEndpointGroup
name: psc_neg_service_attachment
properties:
name: psc-neg
region: europe-west4
networkEndpointType: PRIVATE_SERVICE_CONNECT
pscTargetService: ${defaultServiceAttachment.selfLink}
pscData:
producerPort: '88'
subnetwork: ${defaultSubnetwork.selfLink}
The pscData block adds producerPort to select a specific port from the service attachment’s exposed ports. The subnetwork property controls which VPC subnet the NEG uses for the PSC connection. This extends basic PSC with port selection and network placement control.
Route to external IP addresses and ports
Load balancers can distribute traffic to endpoints outside GCP by IP address and port, enabling hybrid architectures.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.compute.Network("default", {name: "network"});
const regionNetworkEndpointGroupInternetIpPort = new gcp.compute.RegionNetworkEndpointGroup("region_network_endpoint_group_internet_ip_port", {
name: "ip-port-neg",
region: "us-central1",
network: _default.id,
networkEndpointType: "INTERNET_IP_PORT",
});
import pulumi
import pulumi_gcp as gcp
default = gcp.compute.Network("default", name="network")
region_network_endpoint_group_internet_ip_port = gcp.compute.RegionNetworkEndpointGroup("region_network_endpoint_group_internet_ip_port",
name="ip-port-neg",
region="us-central1",
network=default.id,
network_endpoint_type="INTERNET_IP_PORT")
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_default, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
Name: pulumi.String("network"),
})
if err != nil {
return err
}
_, err = compute.NewRegionNetworkEndpointGroup(ctx, "region_network_endpoint_group_internet_ip_port", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("ip-port-neg"),
Region: pulumi.String("us-central1"),
Network: _default.ID(),
NetworkEndpointType: pulumi.String("INTERNET_IP_PORT"),
})
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 @default = new Gcp.Compute.Network("default", new()
{
Name = "network",
});
var regionNetworkEndpointGroupInternetIpPort = new Gcp.Compute.RegionNetworkEndpointGroup("region_network_endpoint_group_internet_ip_port", new()
{
Name = "ip-port-neg",
Region = "us-central1",
Network = @default.Id,
NetworkEndpointType = "INTERNET_IP_PORT",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
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 default_ = new Network("default", NetworkArgs.builder()
.name("network")
.build());
var regionNetworkEndpointGroupInternetIpPort = new RegionNetworkEndpointGroup("regionNetworkEndpointGroupInternetIpPort", RegionNetworkEndpointGroupArgs.builder()
.name("ip-port-neg")
.region("us-central1")
.network(default_.id())
.networkEndpointType("INTERNET_IP_PORT")
.build());
}
}
resources:
regionNetworkEndpointGroupInternetIpPort:
type: gcp:compute:RegionNetworkEndpointGroup
name: region_network_endpoint_group_internet_ip_port
properties:
name: ip-port-neg
region: us-central1
network: ${default.id}
networkEndpointType: INTERNET_IP_PORT
default:
type: gcp:compute:Network
properties:
name: network
The networkEndpointType INTERNET_IP_PORT indicates external IP-based endpoints. The network property specifies which VPC network the NEG belongs to. Individual endpoints (IP:port pairs) are added separately after NEG creation.
Route to external FQDNs and ports
When external endpoints use DNS names rather than static IPs, FQDN-based NEGs allow the load balancer to resolve and route to those hostnames.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.compute.Network("default", {name: "network"});
const regionNetworkEndpointGroupInternetFqdnPort = new gcp.compute.RegionNetworkEndpointGroup("region_network_endpoint_group_internet_fqdn_port", {
name: "ip-port-neg",
region: "us-central1",
network: _default.id,
networkEndpointType: "INTERNET_FQDN_PORT",
});
import pulumi
import pulumi_gcp as gcp
default = gcp.compute.Network("default", name="network")
region_network_endpoint_group_internet_fqdn_port = gcp.compute.RegionNetworkEndpointGroup("region_network_endpoint_group_internet_fqdn_port",
name="ip-port-neg",
region="us-central1",
network=default.id,
network_endpoint_type="INTERNET_FQDN_PORT")
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_default, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
Name: pulumi.String("network"),
})
if err != nil {
return err
}
_, err = compute.NewRegionNetworkEndpointGroup(ctx, "region_network_endpoint_group_internet_fqdn_port", &compute.RegionNetworkEndpointGroupArgs{
Name: pulumi.String("ip-port-neg"),
Region: pulumi.String("us-central1"),
Network: _default.ID(),
NetworkEndpointType: pulumi.String("INTERNET_FQDN_PORT"),
})
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 @default = new Gcp.Compute.Network("default", new()
{
Name = "network",
});
var regionNetworkEndpointGroupInternetFqdnPort = new Gcp.Compute.RegionNetworkEndpointGroup("region_network_endpoint_group_internet_fqdn_port", new()
{
Name = "ip-port-neg",
Region = "us-central1",
Network = @default.Id,
NetworkEndpointType = "INTERNET_FQDN_PORT",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroup;
import com.pulumi.gcp.compute.RegionNetworkEndpointGroupArgs;
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 default_ = new Network("default", NetworkArgs.builder()
.name("network")
.build());
var regionNetworkEndpointGroupInternetFqdnPort = new RegionNetworkEndpointGroup("regionNetworkEndpointGroupInternetFqdnPort", RegionNetworkEndpointGroupArgs.builder()
.name("ip-port-neg")
.region("us-central1")
.network(default_.id())
.networkEndpointType("INTERNET_FQDN_PORT")
.build());
}
}
resources:
regionNetworkEndpointGroupInternetFqdnPort:
type: gcp:compute:RegionNetworkEndpointGroup
name: region_network_endpoint_group_internet_fqdn_port
properties:
name: ip-port-neg
region: us-central1
network: ${default.id}
networkEndpointType: INTERNET_FQDN_PORT
default:
type: gcp:compute:Network
properties:
name: network
The networkEndpointType INTERNET_FQDN_PORT enables DNS-based routing. The load balancer resolves the FQDN at request time, allowing endpoints to change IPs without NEG reconfiguration.
Beyond these examples
These snippets focus on specific regional NEG features: serverless backends (Cloud Run, Cloud Functions, App Engine), Private Service Connect for Google APIs and service attachments, and external endpoints via IP or FQDN. They’re intentionally minimal rather than full load balancing configurations.
The examples may reference pre-existing infrastructure such as Cloud Run services, Cloud Functions, App Engine versions, VPC networks and subnets, and PSC service attachments, forwarding rules, backend services. They focus on configuring the NEG rather than provisioning the complete load balancing stack.
To keep things focused, common NEG patterns are omitted, including:
- GCE_VM_IP_PORTMAP for VM instance groups
- Network endpoint attachment and management
- Load balancer backend service configuration
- Health checks and traffic distribution
These omissions are intentional: the goal is to illustrate how each NEG type is wired, not provide drop-in load balancing modules. See the RegionNetworkEndpointGroup resource reference for all available configuration options.
Let's configure GCP Regional Network Endpoint Groups
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Immutability
[a-z][-a-z0-9]*[a-z0-9]?.SERVERLESS (default), PRIVATE_SERVICE_CONNECT, INTERNET_IP_PORT, INTERNET_FQDN_PORT, and GCE_VM_IP_PORTMAP.Serverless Backends
cloudRun, appEngine, cloudFunction, or serverlessDeployment may be set for SERVERLESS NEGs.appEngine to an empty object for SERVERLESS NEGs, as shown in the App Engine empty example.PSC & Network Configuration
network field is required for PSC and INTERNET NEGs. If unspecified, it defaults to the “default” project network.pscData is required for PRIVATE_SERVICE_CONNECT NEGs and contains PSC-specific configuration like producerPort.subnetwork is optional for PSC NEGs and required for GCE_VM_IP_PORTMAP NEGs.pscTargetService specifies the target service URL for connecting to a Google API or PSC Producer Service Attachment.Using a different cloud?
Explore networking guides for other cloud providers: