The gcp:cloudrunv2/job:Job resource, part of the Pulumi GCP provider, defines a Cloud Run Job that runs containerized tasks to completion on demand or on schedule. This guide focuses on three capabilities: container image deployment, resource limits and VPC connectivity, and secrets and volume management.
Jobs reference container images from registries and may connect to Cloud SQL, Secret Manager, or VPC resources that must exist separately. The examples are intentionally small. Combine them with your own container images, IAM policies, and networking infrastructure.
Deploy a job with a container image
Most deployments start by specifying a container image and location. The job resource defines what to run but doesn’t execute until triggered.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
}],
},
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
}],
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
},
},
},
},
})
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.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
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 Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.build())
.build())
.build())
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
The template property defines the execution environment. Inside template.template.containers, the image property points to your container in Artifact Registry or Container Registry. The job remains idle until you trigger an execution manually or via Cloud Scheduler.
Set CPU and memory resource limits
Jobs that process large datasets or perform compute-intensive work need explicit resource allocation for predictable performance.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
resources: {
limits: {
cpu: "2",
memory: "1024Mi",
},
},
}],
},
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
"resources": {
"limits": {
"cpu": "2",
"memory": "1024Mi",
},
},
}],
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
Resources: &cloudrunv2.JobTemplateTemplateContainerResourcesArgs{
Limits: pulumi.StringMap{
"cpu": pulumi.String("2"),
"memory": pulumi.String("1024Mi"),
},
},
},
},
},
},
})
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.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
Resources = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerResourcesArgs
{
Limits =
{
{ "cpu", "2" },
{ "memory", "1024Mi" },
},
},
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
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 Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.resources(JobTemplateTemplateContainerResourcesArgs.builder()
.limits(Map.ofEntries(
Map.entry("cpu", "2"),
Map.entry("memory", "1024Mi")
))
.build())
.build())
.build())
.build())
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
resources:
limits:
cpu: '2'
memory: 1024Mi
The resources.limits block controls CPU cores and memory allocated to each task execution. CPU is specified as a string (e.g., “2” for 2 cores), and memory uses units like “1024Mi” for 1 GiB. These limits affect both performance and cost per execution.
Connect to Cloud SQL with secrets
Jobs that query databases need Cloud SQL connectivity and credentials passed securely through Secret Manager.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const secret = new gcp.secretmanager.Secret("secret", {
secretId: "secret",
replication: {
auto: {},
},
});
const instance = new gcp.sql.DatabaseInstance("instance", {
name: "cloudrun-sql",
region: "us-central1",
databaseVersion: "MYSQL_5_7",
settings: {
tier: "db-f1-micro",
},
deletionProtection: true,
});
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
volumes: [{
name: "cloudsql",
cloudSqlInstance: {
instances: [instance.connectionName],
},
}],
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
envs: [
{
name: "FOO",
value: "bar",
},
{
name: "latestdclsecret",
valueSource: {
secretKeyRef: {
secret: secret.secretId,
version: "1",
},
},
},
],
volumeMounts: [{
name: "cloudsql",
mountPath: "/cloudsql",
}],
}],
},
},
});
const project = gcp.organizations.getProject({});
const secret_version_data = new gcp.secretmanager.SecretVersion("secret-version-data", {
secret: secret.name,
secretData: "secret-data",
});
const secret_access = new gcp.secretmanager.SecretIamMember("secret-access", {
secretId: secret.id,
role: "roles/secretmanager.secretAccessor",
member: project.then(project => `serviceAccount:${project.number}-compute@developer.gserviceaccount.com`),
}, {
dependsOn: [secret],
});
import pulumi
import pulumi_gcp as gcp
secret = gcp.secretmanager.Secret("secret",
secret_id="secret",
replication={
"auto": {},
})
instance = gcp.sql.DatabaseInstance("instance",
name="cloudrun-sql",
region="us-central1",
database_version="MYSQL_5_7",
settings={
"tier": "db-f1-micro",
},
deletion_protection=True)
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"volumes": [{
"name": "cloudsql",
"cloud_sql_instance": {
"instances": [instance.connection_name],
},
}],
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
"envs": [
{
"name": "FOO",
"value": "bar",
},
{
"name": "latestdclsecret",
"value_source": {
"secret_key_ref": {
"secret": secret.secret_id,
"version": "1",
},
},
},
],
"volume_mounts": [{
"name": "cloudsql",
"mount_path": "/cloudsql",
}],
}],
},
})
project = gcp.organizations.get_project()
secret_version_data = gcp.secretmanager.SecretVersion("secret-version-data",
secret=secret.name,
secret_data="secret-data")
secret_access = gcp.secretmanager.SecretIamMember("secret-access",
secret_id=secret.id,
role="roles/secretmanager.secretAccessor",
member=f"serviceAccount:{project.number}-compute@developer.gserviceaccount.com",
opts = pulumi.ResourceOptions(depends_on=[secret]))
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/secretmanager"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/sql"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
secret, err := secretmanager.NewSecret(ctx, "secret", &secretmanager.SecretArgs{
SecretId: pulumi.String("secret"),
Replication: &secretmanager.SecretReplicationArgs{
Auto: &secretmanager.SecretReplicationAutoArgs{},
},
})
if err != nil {
return err
}
instance, err := sql.NewDatabaseInstance(ctx, "instance", &sql.DatabaseInstanceArgs{
Name: pulumi.String("cloudrun-sql"),
Region: pulumi.String("us-central1"),
DatabaseVersion: pulumi.String("MYSQL_5_7"),
Settings: &sql.DatabaseInstanceSettingsArgs{
Tier: pulumi.String("db-f1-micro"),
},
DeletionProtection: pulumi.Bool(true),
})
if err != nil {
return err
}
_, err = cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Volumes: cloudrunv2.JobTemplateTemplateVolumeArray{
&cloudrunv2.JobTemplateTemplateVolumeArgs{
Name: pulumi.String("cloudsql"),
CloudSqlInstance: &cloudrunv2.JobTemplateTemplateVolumeCloudSqlInstanceArgs{
Instances: pulumi.StringArray{
instance.ConnectionName,
},
},
},
},
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
Envs: cloudrunv2.JobTemplateTemplateContainerEnvArray{
&cloudrunv2.JobTemplateTemplateContainerEnvArgs{
Name: pulumi.String("FOO"),
Value: pulumi.String("bar"),
},
&cloudrunv2.JobTemplateTemplateContainerEnvArgs{
Name: pulumi.String("latestdclsecret"),
ValueSource: &cloudrunv2.JobTemplateTemplateContainerEnvValueSourceArgs{
SecretKeyRef: &cloudrunv2.JobTemplateTemplateContainerEnvValueSourceSecretKeyRefArgs{
Secret: secret.SecretId,
Version: pulumi.String("1"),
},
},
},
},
VolumeMounts: cloudrunv2.JobTemplateTemplateContainerVolumeMountArray{
&cloudrunv2.JobTemplateTemplateContainerVolumeMountArgs{
Name: pulumi.String("cloudsql"),
MountPath: pulumi.String("/cloudsql"),
},
},
},
},
},
},
})
if err != nil {
return err
}
project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
if err != nil {
return err
}
_, err = secretmanager.NewSecretVersion(ctx, "secret-version-data", &secretmanager.SecretVersionArgs{
Secret: secret.Name,
SecretData: pulumi.String("secret-data"),
})
if err != nil {
return err
}
_, err = secretmanager.NewSecretIamMember(ctx, "secret-access", &secretmanager.SecretIamMemberArgs{
SecretId: secret.ID(),
Role: pulumi.String("roles/secretmanager.secretAccessor"),
Member: pulumi.Sprintf("serviceAccount:%v-compute@developer.gserviceaccount.com", project.Number),
}, pulumi.DependsOn([]pulumi.Resource{
secret,
}))
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 secret = new Gcp.SecretManager.Secret("secret", new()
{
SecretId = "secret",
Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
{
Auto = null,
},
});
var instance = new Gcp.Sql.DatabaseInstance("instance", new()
{
Name = "cloudrun-sql",
Region = "us-central1",
DatabaseVersion = "MYSQL_5_7",
Settings = new Gcp.Sql.Inputs.DatabaseInstanceSettingsArgs
{
Tier = "db-f1-micro",
},
DeletionProtection = true,
});
var @default = new Gcp.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Volumes = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeArgs
{
Name = "cloudsql",
CloudSqlInstance = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeCloudSqlInstanceArgs
{
Instances = new[]
{
instance.ConnectionName,
},
},
},
},
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
Envs = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerEnvArgs
{
Name = "FOO",
Value = "bar",
},
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerEnvArgs
{
Name = "latestdclsecret",
ValueSource = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerEnvValueSourceArgs
{
SecretKeyRef = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerEnvValueSourceSecretKeyRefArgs
{
Secret = secret.SecretId,
Version = "1",
},
},
},
},
VolumeMounts = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerVolumeMountArgs
{
Name = "cloudsql",
MountPath = "/cloudsql",
},
},
},
},
},
},
});
var project = Gcp.Organizations.GetProject.Invoke();
var secret_version_data = new Gcp.SecretManager.SecretVersion("secret-version-data", new()
{
Secret = secret.Name,
SecretData = "secret-data",
});
var secret_access = new Gcp.SecretManager.SecretIamMember("secret-access", new()
{
SecretId = secret.Id,
Role = "roles/secretmanager.secretAccessor",
Member = $"serviceAccount:{project.Apply(getProjectResult => getProjectResult.Number)}-compute@developer.gserviceaccount.com",
}, new CustomResourceOptions
{
DependsOn =
{
secret,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.secretmanager.Secret;
import com.pulumi.gcp.secretmanager.SecretArgs;
import com.pulumi.gcp.secretmanager.inputs.SecretReplicationArgs;
import com.pulumi.gcp.secretmanager.inputs.SecretReplicationAutoArgs;
import com.pulumi.gcp.sql.DatabaseInstance;
import com.pulumi.gcp.sql.DatabaseInstanceArgs;
import com.pulumi.gcp.sql.inputs.DatabaseInstanceSettingsArgs;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.secretmanager.SecretVersion;
import com.pulumi.gcp.secretmanager.SecretVersionArgs;
import com.pulumi.gcp.secretmanager.SecretIamMember;
import com.pulumi.gcp.secretmanager.SecretIamMemberArgs;
import com.pulumi.resources.CustomResourceOptions;
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 secret = new Secret("secret", SecretArgs.builder()
.secretId("secret")
.replication(SecretReplicationArgs.builder()
.auto(SecretReplicationAutoArgs.builder()
.build())
.build())
.build());
var instance = new DatabaseInstance("instance", DatabaseInstanceArgs.builder()
.name("cloudrun-sql")
.region("us-central1")
.databaseVersion("MYSQL_5_7")
.settings(DatabaseInstanceSettingsArgs.builder()
.tier("db-f1-micro")
.build())
.deletionProtection(true)
.build());
var default_ = new Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.volumes(JobTemplateTemplateVolumeArgs.builder()
.name("cloudsql")
.cloudSqlInstance(JobTemplateTemplateVolumeCloudSqlInstanceArgs.builder()
.instances(instance.connectionName())
.build())
.build())
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.envs(
JobTemplateTemplateContainerEnvArgs.builder()
.name("FOO")
.value("bar")
.build(),
JobTemplateTemplateContainerEnvArgs.builder()
.name("latestdclsecret")
.valueSource(JobTemplateTemplateContainerEnvValueSourceArgs.builder()
.secretKeyRef(JobTemplateTemplateContainerEnvValueSourceSecretKeyRefArgs.builder()
.secret(secret.secretId())
.version("1")
.build())
.build())
.build())
.volumeMounts(JobTemplateTemplateContainerVolumeMountArgs.builder()
.name("cloudsql")
.mountPath("/cloudsql")
.build())
.build())
.build())
.build())
.build());
final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
.build());
var secret_version_data = new SecretVersion("secret-version-data", SecretVersionArgs.builder()
.secret(secret.name())
.secretData("secret-data")
.build());
var secret_access = new SecretIamMember("secret-access", SecretIamMemberArgs.builder()
.secretId(secret.id())
.role("roles/secretmanager.secretAccessor")
.member(String.format("serviceAccount:%s-compute@developer.gserviceaccount.com", project.number()))
.build(), CustomResourceOptions.builder()
.dependsOn(secret)
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
volumes:
- name: cloudsql
cloudSqlInstance:
instances:
- ${instance.connectionName}
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
envs:
- name: FOO
value: bar
- name: latestdclsecret
valueSource:
secretKeyRef:
secret: ${secret.secretId}
version: '1'
volumeMounts:
- name: cloudsql
mountPath: /cloudsql
secret:
type: gcp:secretmanager:Secret
properties:
secretId: secret
replication:
auto: {}
secret-version-data:
type: gcp:secretmanager:SecretVersion
properties:
secret: ${secret.name}
secretData: secret-data
secret-access:
type: gcp:secretmanager:SecretIamMember
properties:
secretId: ${secret.id}
role: roles/secretmanager.secretAccessor
member: serviceAccount:${project.number}-compute@developer.gserviceaccount.com
options:
dependsOn:
- ${secret}
instance:
type: gcp:sql:DatabaseInstance
properties:
name: cloudrun-sql
region: us-central1
databaseVersion: MYSQL_5_7
settings:
tier: db-f1-micro
deletionProtection: true
variables:
project:
fn::invoke:
function: gcp:organizations:getProject
arguments: {}
Cloud SQL instances are mounted as Unix sockets via volumes.cloudSqlInstance. The volumeMounts property attaches the socket at /cloudsql, where your application connects using the instance connection name. Secrets are injected as environment variables through envs.valueSource.secretKeyRef, which references a Secret Manager secret by ID and version. The compute service account needs the secretmanager.secretAccessor role to read secrets.
Route traffic through VPC Access Connector
Jobs that need to reach internal services or databases in a VPC use Serverless VPC Access connectors.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const customTestNetwork = new gcp.compute.Network("custom_test", {
name: "run-network",
autoCreateSubnetworks: false,
});
const customTest = new gcp.compute.Subnetwork("custom_test", {
name: "run-subnetwork",
ipCidrRange: "10.2.0.0/28",
region: "us-central1",
network: customTestNetwork.id,
});
const connector = new gcp.vpcaccess.Connector("connector", {
name: "run-vpc",
subnet: {
name: customTest.name,
},
machineType: "e2-standard-4",
minInstances: 2,
maxInstances: 3,
region: "us-central1",
});
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
}],
vpcAccess: {
connector: connector.id,
egress: "ALL_TRAFFIC",
},
},
},
});
import pulumi
import pulumi_gcp as gcp
custom_test_network = gcp.compute.Network("custom_test",
name="run-network",
auto_create_subnetworks=False)
custom_test = gcp.compute.Subnetwork("custom_test",
name="run-subnetwork",
ip_cidr_range="10.2.0.0/28",
region="us-central1",
network=custom_test_network.id)
connector = gcp.vpcaccess.Connector("connector",
name="run-vpc",
subnet={
"name": custom_test.name,
},
machine_type="e2-standard-4",
min_instances=2,
max_instances=3,
region="us-central1")
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
}],
"vpc_access": {
"connector": connector.id,
"egress": "ALL_TRAFFIC",
},
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/vpcaccess"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
customTestNetwork, err := compute.NewNetwork(ctx, "custom_test", &compute.NetworkArgs{
Name: pulumi.String("run-network"),
AutoCreateSubnetworks: pulumi.Bool(false),
})
if err != nil {
return err
}
customTest, err := compute.NewSubnetwork(ctx, "custom_test", &compute.SubnetworkArgs{
Name: pulumi.String("run-subnetwork"),
IpCidrRange: pulumi.String("10.2.0.0/28"),
Region: pulumi.String("us-central1"),
Network: customTestNetwork.ID(),
})
if err != nil {
return err
}
connector, err := vpcaccess.NewConnector(ctx, "connector", &vpcaccess.ConnectorArgs{
Name: pulumi.String("run-vpc"),
Subnet: &vpcaccess.ConnectorSubnetArgs{
Name: customTest.Name,
},
MachineType: pulumi.String("e2-standard-4"),
MinInstances: pulumi.Int(2),
MaxInstances: pulumi.Int(3),
Region: pulumi.String("us-central1"),
})
if err != nil {
return err
}
_, err = cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
},
},
VpcAccess: &cloudrunv2.JobTemplateTemplateVpcAccessArgs{
Connector: connector.ID(),
Egress: pulumi.String("ALL_TRAFFIC"),
},
},
},
})
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 customTestNetwork = new Gcp.Compute.Network("custom_test", new()
{
Name = "run-network",
AutoCreateSubnetworks = false,
});
var customTest = new Gcp.Compute.Subnetwork("custom_test", new()
{
Name = "run-subnetwork",
IpCidrRange = "10.2.0.0/28",
Region = "us-central1",
Network = customTestNetwork.Id,
});
var connector = new Gcp.VpcAccess.Connector("connector", new()
{
Name = "run-vpc",
Subnet = new Gcp.VpcAccess.Inputs.ConnectorSubnetArgs
{
Name = customTest.Name,
},
MachineType = "e2-standard-4",
MinInstances = 2,
MaxInstances = 3,
Region = "us-central1",
});
var @default = new Gcp.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
},
},
VpcAccess = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVpcAccessArgs
{
Connector = connector.Id,
Egress = "ALL_TRAFFIC",
},
},
},
});
});
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.vpcaccess.Connector;
import com.pulumi.gcp.vpcaccess.ConnectorArgs;
import com.pulumi.gcp.vpcaccess.inputs.ConnectorSubnetArgs;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateVpcAccessArgs;
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 customTestNetwork = new Network("customTestNetwork", NetworkArgs.builder()
.name("run-network")
.autoCreateSubnetworks(false)
.build());
var customTest = new Subnetwork("customTest", SubnetworkArgs.builder()
.name("run-subnetwork")
.ipCidrRange("10.2.0.0/28")
.region("us-central1")
.network(customTestNetwork.id())
.build());
var connector = new Connector("connector", ConnectorArgs.builder()
.name("run-vpc")
.subnet(ConnectorSubnetArgs.builder()
.name(customTest.name())
.build())
.machineType("e2-standard-4")
.minInstances(2)
.maxInstances(3)
.region("us-central1")
.build());
var default_ = new Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.build())
.vpcAccess(JobTemplateTemplateVpcAccessArgs.builder()
.connector(connector.id())
.egress("ALL_TRAFFIC")
.build())
.build())
.build())
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
vpcAccess:
connector: ${connector.id}
egress: ALL_TRAFFIC
connector:
type: gcp:vpcaccess:Connector
properties:
name: run-vpc
subnet:
name: ${customTest.name}
machineType: e2-standard-4
minInstances: 2
maxInstances: 3
region: us-central1
customTest:
type: gcp:compute:Subnetwork
name: custom_test
properties:
name: run-subnetwork
ipCidrRange: 10.2.0.0/28
region: us-central1
network: ${customTestNetwork.id}
customTestNetwork:
type: gcp:compute:Network
name: custom_test
properties:
name: run-network
autoCreateSubnetworks: false
The vpcAccess.connector property references a VPC Access Connector by ID. The egress property controls which traffic routes through the connector: “ALL_TRAFFIC” sends all outbound requests through the VPC, while “PRIVATE_RANGES_ONLY” routes only RFC 1918 addresses. This enables access to private Cloud SQL instances, internal load balancers, and other VPC resources.
Connect directly to VPC with network interfaces
Direct VPC egress bypasses VPC Access connectors by attaching network interfaces directly to the job, reducing latency and cost.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
launchStage: "GA",
template: {
template: {
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
}],
vpcAccess: {
networkInterfaces: [{
network: "default",
subnetwork: "default",
tags: [
"tag1",
"tag2",
"tag3",
],
}],
},
},
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
launch_stage="GA",
template={
"template": {
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
}],
"vpc_access": {
"network_interfaces": [{
"network": "default",
"subnetwork": "default",
"tags": [
"tag1",
"tag2",
"tag3",
],
}],
},
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
LaunchStage: pulumi.String("GA"),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
},
},
VpcAccess: &cloudrunv2.JobTemplateTemplateVpcAccessArgs{
NetworkInterfaces: cloudrunv2.JobTemplateTemplateVpcAccessNetworkInterfaceArray{
&cloudrunv2.JobTemplateTemplateVpcAccessNetworkInterfaceArgs{
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
Tags: pulumi.StringArray{
pulumi.String("tag1"),
pulumi.String("tag2"),
pulumi.String("tag3"),
},
},
},
},
},
},
})
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.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
LaunchStage = "GA",
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
},
},
VpcAccess = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVpcAccessArgs
{
NetworkInterfaces = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVpcAccessNetworkInterfaceArgs
{
Network = "default",
Subnetwork = "default",
Tags = new[]
{
"tag1",
"tag2",
"tag3",
},
},
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateVpcAccessArgs;
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 Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.launchStage("GA")
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.build())
.vpcAccess(JobTemplateTemplateVpcAccessArgs.builder()
.networkInterfaces(JobTemplateTemplateVpcAccessNetworkInterfaceArgs.builder()
.network("default")
.subnetwork("default")
.tags(
"tag1",
"tag2",
"tag3")
.build())
.build())
.build())
.build())
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
launchStage: GA
template:
template:
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
vpcAccess:
networkInterfaces:
- network: default
subnetwork: default
tags:
- tag1
- tag2
- tag3
The vpcAccess.networkInterfaces property attaches the job directly to a VPC network and subnet. The tags property applies network tags for firewall rules. Direct VPC is generally preferred over VPC Access connectors for lower latency and simpler configuration, but requires the job to be in GA launch stage.
Mount secrets as files in the container
Applications that read configuration from files can consume Secret Manager secrets mounted as volumes.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const secret = new gcp.secretmanager.Secret("secret", {
secretId: "secret",
replication: {
auto: {},
},
});
const secret_version_data = new gcp.secretmanager.SecretVersion("secret-version-data", {
secret: secret.name,
secretData: "secret-data",
});
const project = gcp.organizations.getProject({});
const secret_access = new gcp.secretmanager.SecretIamMember("secret-access", {
secretId: secret.id,
role: "roles/secretmanager.secretAccessor",
member: project.then(project => `serviceAccount:${project.number}-compute@developer.gserviceaccount.com`),
}, {
dependsOn: [secret],
});
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
volumes: [{
name: "a-volume",
secret: {
secret: secret.secretId,
defaultMode: 292,
items: [{
version: "1",
path: "my-secret",
mode: 256,
}],
},
}],
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
volumeMounts: [{
name: "a-volume",
mountPath: "/secrets",
}],
}],
},
},
}, {
dependsOn: [
secret_version_data,
secret_access,
],
});
import pulumi
import pulumi_gcp as gcp
secret = gcp.secretmanager.Secret("secret",
secret_id="secret",
replication={
"auto": {},
})
secret_version_data = gcp.secretmanager.SecretVersion("secret-version-data",
secret=secret.name,
secret_data="secret-data")
project = gcp.organizations.get_project()
secret_access = gcp.secretmanager.SecretIamMember("secret-access",
secret_id=secret.id,
role="roles/secretmanager.secretAccessor",
member=f"serviceAccount:{project.number}-compute@developer.gserviceaccount.com",
opts = pulumi.ResourceOptions(depends_on=[secret]))
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"volumes": [{
"name": "a-volume",
"secret": {
"secret": secret.secret_id,
"default_mode": 292,
"items": [{
"version": "1",
"path": "my-secret",
"mode": 256,
}],
},
}],
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
"volume_mounts": [{
"name": "a-volume",
"mount_path": "/secrets",
}],
}],
},
},
opts = pulumi.ResourceOptions(depends_on=[
secret_version_data,
secret_access,
]))
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/secretmanager"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
secret, err := secretmanager.NewSecret(ctx, "secret", &secretmanager.SecretArgs{
SecretId: pulumi.String("secret"),
Replication: &secretmanager.SecretReplicationArgs{
Auto: &secretmanager.SecretReplicationAutoArgs{},
},
})
if err != nil {
return err
}
secret_version_data, err := secretmanager.NewSecretVersion(ctx, "secret-version-data", &secretmanager.SecretVersionArgs{
Secret: secret.Name,
SecretData: pulumi.String("secret-data"),
})
if err != nil {
return err
}
project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
if err != nil {
return err
}
secret_access, err := secretmanager.NewSecretIamMember(ctx, "secret-access", &secretmanager.SecretIamMemberArgs{
SecretId: secret.ID(),
Role: pulumi.String("roles/secretmanager.secretAccessor"),
Member: pulumi.Sprintf("serviceAccount:%v-compute@developer.gserviceaccount.com", project.Number),
}, pulumi.DependsOn([]pulumi.Resource{
secret,
}))
if err != nil {
return err
}
_, err = cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Volumes: cloudrunv2.JobTemplateTemplateVolumeArray{
&cloudrunv2.JobTemplateTemplateVolumeArgs{
Name: pulumi.String("a-volume"),
Secret: &cloudrunv2.JobTemplateTemplateVolumeSecretArgs{
Secret: secret.SecretId,
DefaultMode: pulumi.Int(292),
Items: cloudrunv2.JobTemplateTemplateVolumeSecretItemArray{
&cloudrunv2.JobTemplateTemplateVolumeSecretItemArgs{
Version: pulumi.String("1"),
Path: pulumi.String("my-secret"),
Mode: pulumi.Int(256),
},
},
},
},
},
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
VolumeMounts: cloudrunv2.JobTemplateTemplateContainerVolumeMountArray{
&cloudrunv2.JobTemplateTemplateContainerVolumeMountArgs{
Name: pulumi.String("a-volume"),
MountPath: pulumi.String("/secrets"),
},
},
},
},
},
},
}, pulumi.DependsOn([]pulumi.Resource{
secret_version_data,
secret_access,
}))
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 secret = new Gcp.SecretManager.Secret("secret", new()
{
SecretId = "secret",
Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
{
Auto = null,
},
});
var secret_version_data = new Gcp.SecretManager.SecretVersion("secret-version-data", new()
{
Secret = secret.Name,
SecretData = "secret-data",
});
var project = Gcp.Organizations.GetProject.Invoke();
var secret_access = new Gcp.SecretManager.SecretIamMember("secret-access", new()
{
SecretId = secret.Id,
Role = "roles/secretmanager.secretAccessor",
Member = $"serviceAccount:{project.Apply(getProjectResult => getProjectResult.Number)}-compute@developer.gserviceaccount.com",
}, new CustomResourceOptions
{
DependsOn =
{
secret,
},
});
var @default = new Gcp.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Volumes = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeArgs
{
Name = "a-volume",
Secret = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeSecretArgs
{
Secret = secret.SecretId,
DefaultMode = 292,
Items = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeSecretItemArgs
{
Version = "1",
Path = "my-secret",
Mode = 256,
},
},
},
},
},
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
VolumeMounts = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerVolumeMountArgs
{
Name = "a-volume",
MountPath = "/secrets",
},
},
},
},
},
},
}, new CustomResourceOptions
{
DependsOn =
{
secret_version_data,
secret_access,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.secretmanager.Secret;
import com.pulumi.gcp.secretmanager.SecretArgs;
import com.pulumi.gcp.secretmanager.inputs.SecretReplicationArgs;
import com.pulumi.gcp.secretmanager.inputs.SecretReplicationAutoArgs;
import com.pulumi.gcp.secretmanager.SecretVersion;
import com.pulumi.gcp.secretmanager.SecretVersionArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.secretmanager.SecretIamMember;
import com.pulumi.gcp.secretmanager.SecretIamMemberArgs;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
import com.pulumi.resources.CustomResourceOptions;
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 secret = new Secret("secret", SecretArgs.builder()
.secretId("secret")
.replication(SecretReplicationArgs.builder()
.auto(SecretReplicationAutoArgs.builder()
.build())
.build())
.build());
var secret_version_data = new SecretVersion("secret-version-data", SecretVersionArgs.builder()
.secret(secret.name())
.secretData("secret-data")
.build());
final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
.build());
var secret_access = new SecretIamMember("secret-access", SecretIamMemberArgs.builder()
.secretId(secret.id())
.role("roles/secretmanager.secretAccessor")
.member(String.format("serviceAccount:%s-compute@developer.gserviceaccount.com", project.number()))
.build(), CustomResourceOptions.builder()
.dependsOn(secret)
.build());
var default_ = new Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.volumes(JobTemplateTemplateVolumeArgs.builder()
.name("a-volume")
.secret(JobTemplateTemplateVolumeSecretArgs.builder()
.secret(secret.secretId())
.defaultMode(292)
.items(JobTemplateTemplateVolumeSecretItemArgs.builder()
.version("1")
.path("my-secret")
.mode(256)
.build())
.build())
.build())
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.volumeMounts(JobTemplateTemplateContainerVolumeMountArgs.builder()
.name("a-volume")
.mountPath("/secrets")
.build())
.build())
.build())
.build())
.build(), CustomResourceOptions.builder()
.dependsOn(
secret_version_data,
secret_access)
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
volumes:
- name: a-volume
secret:
secret: ${secret.secretId}
defaultMode: 292
items:
- version: '1'
path: my-secret
mode: 256
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
volumeMounts:
- name: a-volume
mountPath: /secrets
options:
dependsOn:
- ${["secret-version-data"]}
- ${["secret-access"]}
secret:
type: gcp:secretmanager:Secret
properties:
secretId: secret
replication:
auto: {}
secret-version-data:
type: gcp:secretmanager:SecretVersion
properties:
secret: ${secret.name}
secretData: secret-data
secret-access:
type: gcp:secretmanager:SecretIamMember
properties:
secretId: ${secret.id}
role: roles/secretmanager.secretAccessor
member: serviceAccount:${project.number}-compute@developer.gserviceaccount.com
options:
dependsOn:
- ${secret}
variables:
project:
fn::invoke:
function: gcp:organizations:getProject
arguments: {}
The volumes.secret block mounts a Secret Manager secret as files. The defaultMode sets Unix permissions (292 is octal 0444, read-only). The items array maps secret versions to file paths with custom permissions. The volumeMounts property attaches the volume at /secrets, where your application reads the files. This approach works well for applications that expect configuration files rather than environment variables.
Use ephemeral storage for temporary data
Jobs that need fast temporary storage for intermediate processing can use emptyDir volumes.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
containers: [{
image: "us-docker.pkg.dev/cloudrun/container/job",
volumeMounts: [{
name: "empty-dir-volume",
mountPath: "/mnt",
}],
}],
volumes: [{
name: "empty-dir-volume",
emptyDir: {
medium: "MEMORY",
sizeLimit: "128Mi",
},
}],
},
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"containers": [{
"image": "us-docker.pkg.dev/cloudrun/container/job",
"volume_mounts": [{
"name": "empty-dir-volume",
"mount_path": "/mnt",
}],
}],
"volumes": [{
"name": "empty-dir-volume",
"empty_dir": {
"medium": "MEMORY",
"size_limit": "128Mi",
},
}],
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
VolumeMounts: cloudrunv2.JobTemplateTemplateContainerVolumeMountArray{
&cloudrunv2.JobTemplateTemplateContainerVolumeMountArgs{
Name: pulumi.String("empty-dir-volume"),
MountPath: pulumi.String("/mnt"),
},
},
},
},
Volumes: cloudrunv2.JobTemplateTemplateVolumeArray{
&cloudrunv2.JobTemplateTemplateVolumeArgs{
Name: pulumi.String("empty-dir-volume"),
EmptyDir: &cloudrunv2.JobTemplateTemplateVolumeEmptyDirArgs{
Medium: pulumi.String("MEMORY"),
SizeLimit: pulumi.String("128Mi"),
},
},
},
},
},
})
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.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/job",
VolumeMounts = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerVolumeMountArgs
{
Name = "empty-dir-volume",
MountPath = "/mnt",
},
},
},
},
Volumes = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeArgs
{
Name = "empty-dir-volume",
EmptyDir = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateVolumeEmptyDirArgs
{
Medium = "MEMORY",
SizeLimit = "128Mi",
},
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
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 Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.containers(JobTemplateTemplateContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/job")
.volumeMounts(JobTemplateTemplateContainerVolumeMountArgs.builder()
.name("empty-dir-volume")
.mountPath("/mnt")
.build())
.build())
.volumes(JobTemplateTemplateVolumeArgs.builder()
.name("empty-dir-volume")
.emptyDir(JobTemplateTemplateVolumeEmptyDirArgs.builder()
.medium("MEMORY")
.sizeLimit("128Mi")
.build())
.build())
.build())
.build())
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
containers:
- image: us-docker.pkg.dev/cloudrun/container/job
volumeMounts:
- name: empty-dir-volume
mountPath: /mnt
volumes:
- name: empty-dir-volume
emptyDir:
medium: MEMORY
sizeLimit: 128Mi
The volumes.emptyDir block creates ephemeral storage that exists only during job execution. The medium property controls backing storage: “MEMORY” uses RAM for fastest access, while “DISK” uses local SSD. The sizeLimit property caps storage size (e.g., “128Mi” for 128 MiB). Data in emptyDir volumes is lost when the job completes.
Run multiple containers in a single job
Complex workflows can split responsibilities across multiple containers that run in parallel.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.cloudrunv2.Job("default", {
name: "cloudrun-job",
location: "us-central1",
deletionProtection: false,
template: {
template: {
containers: [
{
name: "job-1",
image: "us-docker.pkg.dev/cloudrun/container/job",
},
{
name: "job-2",
image: "us-docker.pkg.dev/cloudrun/container/job",
},
],
},
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.cloudrunv2.Job("default",
name="cloudrun-job",
location="us-central1",
deletion_protection=False,
template={
"template": {
"containers": [
{
"name": "job-1",
"image": "us-docker.pkg.dev/cloudrun/container/job",
},
{
"name": "job-2",
"image": "us-docker.pkg.dev/cloudrun/container/job",
},
],
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudrunv2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := cloudrunv2.NewJob(ctx, "default", &cloudrunv2.JobArgs{
Name: pulumi.String("cloudrun-job"),
Location: pulumi.String("us-central1"),
DeletionProtection: pulumi.Bool(false),
Template: &cloudrunv2.JobTemplateArgs{
Template: &cloudrunv2.JobTemplateTemplateArgs{
Containers: cloudrunv2.JobTemplateTemplateContainerArray{
&cloudrunv2.JobTemplateTemplateContainerArgs{
Name: pulumi.String("job-1"),
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
},
&cloudrunv2.JobTemplateTemplateContainerArgs{
Name: pulumi.String("job-2"),
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/job"),
},
},
},
},
})
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.CloudRunV2.Job("default", new()
{
Name = "cloudrun-job",
Location = "us-central1",
DeletionProtection = false,
Template = new Gcp.CloudRunV2.Inputs.JobTemplateArgs
{
Template = new Gcp.CloudRunV2.Inputs.JobTemplateTemplateArgs
{
Containers = new[]
{
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Name = "job-1",
Image = "us-docker.pkg.dev/cloudrun/container/job",
},
new Gcp.CloudRunV2.Inputs.JobTemplateTemplateContainerArgs
{
Name = "job-2",
Image = "us-docker.pkg.dev/cloudrun/container/job",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudrunv2.Job;
import com.pulumi.gcp.cloudrunv2.JobArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateArgs;
import com.pulumi.gcp.cloudrunv2.inputs.JobTemplateTemplateArgs;
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 Job("default", JobArgs.builder()
.name("cloudrun-job")
.location("us-central1")
.deletionProtection(false)
.template(JobTemplateArgs.builder()
.template(JobTemplateTemplateArgs.builder()
.containers(
JobTemplateTemplateContainerArgs.builder()
.name("job-1")
.image("us-docker.pkg.dev/cloudrun/container/job")
.build(),
JobTemplateTemplateContainerArgs.builder()
.name("job-2")
.image("us-docker.pkg.dev/cloudrun/container/job")
.build())
.build())
.build())
.build());
}
}
resources:
default:
type: gcp:cloudrunv2:Job
properties:
name: cloudrun-job
location: us-central1
deletionProtection: false
template:
template:
containers:
- name: job-1
image: us-docker.pkg.dev/cloudrun/container/job
- name: job-2
image: us-docker.pkg.dev/cloudrun/container/job
The containers array defines multiple containers that execute simultaneously within the same job execution. Each container needs a unique name property. Containers share the same network namespace and can communicate via localhost, but run separate processes. This pattern works well for sidecar containers that handle logging, monitoring, or data transformation alongside the main application.
Beyond these examples
These snippets focus on specific job-level features: container deployment and resource limits, VPC connectivity (connectors and direct interfaces), and secrets and volume management. They’re intentionally minimal rather than full batch processing systems.
The examples may reference pre-existing infrastructure such as Cloud SQL instances, Secret Manager secrets with IAM bindings, and VPC networks, subnets, and VPC Access Connectors. They focus on configuring the job rather than provisioning everything around it.
To keep things focused, common job patterns are omitted, including:
- Job execution triggers (startExecutionToken, runExecutionToken)
- Binary Authorization policies (binaryAuthorization)
- GPU node selection (nodeSelector, gpuZonalRedundancyDisabled)
- Annotations and labels for metadata
- Parallelism and retry configuration
These omissions are intentional: the goal is to illustrate how each job feature is wired, not provide drop-in batch processing modules. See the Cloud Run Job resource reference for all available configuration options.
Let's deploy GCP Cloud Run Jobs
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Deletion & Lifecycle
deletionProtection enabled by default. You must explicitly set deletionProtection: false in your configuration before running pulumi up to delete the job.location, name, and project properties are immutable. Changing any of these requires destroying and recreating the job.Metadata & Labels
labels and annotations fields are non-authoritative, meaning they only manage values present in your Pulumi configuration. To see all labels and annotations (including those added by GCP or other tools), use the effectiveLabels and effectiveAnnotations output properties.run.googleapis.com, cloud.googleapis.com, serving.knative.dev, or autoscaling.knative.dev namespaces in custom labels or annotations. These are reserved for system use and will be rejected.Execution & Triggers
startExecutionToken marks the job ready when execution starts, while runExecutionToken waits until execution completes successfully. Both are Beta features.startExecutionToken or runExecutionToken) must be fewer than 63 characters.launchStage to ALPHA or BETA to enable preview features. If not specified, launchStage defaults to GA (generally available features only).Networking & VPC
You have two options:
- Serverless VPC Access - Use
vpcAccess.connectorwith a VPC Access connector ID - Direct VPC egress - Use
vpcAccess.networkInterfacesto specify network, subnetwork, and tags directly
cloudSqlInstance.instances pointing to your database’s connection name, then mount it in your container using volumeMounts. You’ll also need to grant the service account roles/secretmanager.secretAccessor if using secrets for credentials.Secrets & Configuration
secret configuration referencing your Secret Manager secret ID, then mount it using volumeMounts in your container. Ensure the service account has roles/secretmanager.secretAccessor permission.envs array in your container configuration. You can set static values with name and value, or reference secrets with valueSource.secretKeyRef.Resources & Performance
resources.limits within your container specification. For example: cpu: "2" and memory: "1024Mi".nodeSelector.accelerator to your desired GPU type (e.g., nvidia-l4) and optionally set gpuZonalRedundancyDisabled: true to disable zonal redundancy.emptyDir configuration specifying medium (e.g., MEMORY) and sizeLimit (e.g., 128Mi), then mount it using volumeMounts.Using a different cloud?
Explore serverless guides for other cloud providers: