The gcp:clouddeploy/customTargetType:CustomTargetType resource, part of the Pulumi GCP provider, defines a reusable custom target type that extends Cloud Deploy to support deployment systems beyond GKE and Cloud Run. This guide focuses on three capabilities: custom action configuration, Skaffold module sourcing from Git, GCS, and Cloud Build, and metadata organization.
Custom target types reference Skaffold configuration stored in external sources and require Cloud Deploy service accounts with appropriate permissions. The examples are intentionally small. Combine them with your own Target resources, delivery pipelines, and IAM configuration.
Define a custom target type with annotations and labels
Teams extending Cloud Deploy to non-standard deployment targets start by defining a custom target type that specifies render and deploy actions.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const custom_target_type = new gcp.clouddeploy.CustomTargetType("custom-target-type", {
location: "us-central1",
name: "my-custom-target-type",
description: "My custom target type",
annotations: {
my_first_annotation: "example-annotation-1",
my_second_annotation: "example-annotation-2",
},
labels: {
my_first_label: "example-label-1",
my_second_label: "example-label-2",
},
customActions: {
renderAction: "renderAction",
deployAction: "deployAction",
},
});
import pulumi
import pulumi_gcp as gcp
custom_target_type = gcp.clouddeploy.CustomTargetType("custom-target-type",
location="us-central1",
name="my-custom-target-type",
description="My custom target type",
annotations={
"my_first_annotation": "example-annotation-1",
"my_second_annotation": "example-annotation-2",
},
labels={
"my_first_label": "example-label-1",
"my_second_label": "example-label-2",
},
custom_actions={
"render_action": "renderAction",
"deploy_action": "deployAction",
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/clouddeploy"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := clouddeploy.NewCustomTargetType(ctx, "custom-target-type", &clouddeploy.CustomTargetTypeArgs{
Location: pulumi.String("us-central1"),
Name: pulumi.String("my-custom-target-type"),
Description: pulumi.String("My custom target type"),
Annotations: pulumi.StringMap{
"my_first_annotation": pulumi.String("example-annotation-1"),
"my_second_annotation": pulumi.String("example-annotation-2"),
},
Labels: pulumi.StringMap{
"my_first_label": pulumi.String("example-label-1"),
"my_second_label": pulumi.String("example-label-2"),
},
CustomActions: &clouddeploy.CustomTargetTypeCustomActionsArgs{
RenderAction: pulumi.String("renderAction"),
DeployAction: pulumi.String("deployAction"),
},
})
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 custom_target_type = new Gcp.CloudDeploy.CustomTargetType("custom-target-type", new()
{
Location = "us-central1",
Name = "my-custom-target-type",
Description = "My custom target type",
Annotations =
{
{ "my_first_annotation", "example-annotation-1" },
{ "my_second_annotation", "example-annotation-2" },
},
Labels =
{
{ "my_first_label", "example-label-1" },
{ "my_second_label", "example-label-2" },
},
CustomActions = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsArgs
{
RenderAction = "renderAction",
DeployAction = "deployAction",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.clouddeploy.CustomTargetType;
import com.pulumi.gcp.clouddeploy.CustomTargetTypeArgs;
import com.pulumi.gcp.clouddeploy.inputs.CustomTargetTypeCustomActionsArgs;
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 custom_target_type = new CustomTargetType("custom-target-type", CustomTargetTypeArgs.builder()
.location("us-central1")
.name("my-custom-target-type")
.description("My custom target type")
.annotations(Map.ofEntries(
Map.entry("my_first_annotation", "example-annotation-1"),
Map.entry("my_second_annotation", "example-annotation-2")
))
.labels(Map.ofEntries(
Map.entry("my_first_label", "example-label-1"),
Map.entry("my_second_label", "example-label-2")
))
.customActions(CustomTargetTypeCustomActionsArgs.builder()
.renderAction("renderAction")
.deployAction("deployAction")
.build())
.build());
}
}
resources:
custom-target-type:
type: gcp:clouddeploy:CustomTargetType
properties:
location: us-central1
name: my-custom-target-type
description: My custom target type
annotations:
my_first_annotation: example-annotation-1
my_second_annotation: example-annotation-2
labels:
my_first_label: example-label-1
my_second_label: example-label-2
customActions:
renderAction: renderAction
deployAction: deployAction
The customActions property defines the contract between Cloud Deploy and your deployment system. The renderAction and deployAction properties are string references to Skaffold custom actions that implement your deployment logic. Annotations and labels provide metadata for organization and filtering; annotations are user-only, while labels are visible to both users and Cloud Deploy.
Reference Skaffold configuration from a Git repository
When custom deployment logic lives in version control, you can point Cloud Deploy to Skaffold modules stored in Git repositories.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const custom_target_type = new gcp.clouddeploy.CustomTargetType("custom-target-type", {
location: "us-central1",
name: "my-custom-target-type",
description: "My custom target type",
customActions: {
renderAction: "renderAction",
deployAction: "deployAction",
includeSkaffoldModules: [{
configs: ["my-config"],
git: {
repo: "http://github.com/example/example-repo.git",
path: "configs/skaffold.yaml",
ref: "main",
},
}],
},
});
import pulumi
import pulumi_gcp as gcp
custom_target_type = gcp.clouddeploy.CustomTargetType("custom-target-type",
location="us-central1",
name="my-custom-target-type",
description="My custom target type",
custom_actions={
"render_action": "renderAction",
"deploy_action": "deployAction",
"include_skaffold_modules": [{
"configs": ["my-config"],
"git": {
"repo": "http://github.com/example/example-repo.git",
"path": "configs/skaffold.yaml",
"ref": "main",
},
}],
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/clouddeploy"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := clouddeploy.NewCustomTargetType(ctx, "custom-target-type", &clouddeploy.CustomTargetTypeArgs{
Location: pulumi.String("us-central1"),
Name: pulumi.String("my-custom-target-type"),
Description: pulumi.String("My custom target type"),
CustomActions: &clouddeploy.CustomTargetTypeCustomActionsArgs{
RenderAction: pulumi.String("renderAction"),
DeployAction: pulumi.String("deployAction"),
IncludeSkaffoldModules: clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArray{
&clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs{
Configs: pulumi.StringArray{
pulumi.String("my-config"),
},
Git: &clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleGitArgs{
Repo: pulumi.String("http://github.com/example/example-repo.git"),
Path: pulumi.String("configs/skaffold.yaml"),
Ref: pulumi.String("main"),
},
},
},
},
})
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 custom_target_type = new Gcp.CloudDeploy.CustomTargetType("custom-target-type", new()
{
Location = "us-central1",
Name = "my-custom-target-type",
Description = "My custom target type",
CustomActions = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsArgs
{
RenderAction = "renderAction",
DeployAction = "deployAction",
IncludeSkaffoldModules = new[]
{
new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs
{
Configs = new[]
{
"my-config",
},
Git = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsIncludeSkaffoldModuleGitArgs
{
Repo = "http://github.com/example/example-repo.git",
Path = "configs/skaffold.yaml",
Ref = "main",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.clouddeploy.CustomTargetType;
import com.pulumi.gcp.clouddeploy.CustomTargetTypeArgs;
import com.pulumi.gcp.clouddeploy.inputs.CustomTargetTypeCustomActionsArgs;
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 custom_target_type = new CustomTargetType("custom-target-type", CustomTargetTypeArgs.builder()
.location("us-central1")
.name("my-custom-target-type")
.description("My custom target type")
.customActions(CustomTargetTypeCustomActionsArgs.builder()
.renderAction("renderAction")
.deployAction("deployAction")
.includeSkaffoldModules(CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs.builder()
.configs("my-config")
.git(CustomTargetTypeCustomActionsIncludeSkaffoldModuleGitArgs.builder()
.repo("http://github.com/example/example-repo.git")
.path("configs/skaffold.yaml")
.ref("main")
.build())
.build())
.build())
.build());
}
}
resources:
custom-target-type:
type: gcp:clouddeploy:CustomTargetType
properties:
location: us-central1
name: my-custom-target-type
description: My custom target type
customActions:
renderAction: renderAction
deployAction: deployAction
includeSkaffoldModules:
- configs:
- my-config
git:
repo: http://github.com/example/example-repo.git
path: configs/skaffold.yaml
ref: main
The includeSkaffoldModules property loads external Skaffold configuration. The git block specifies the repository URL, file path, and branch or tag reference. The configs array lists which Skaffold module names to include from the referenced file. Cloud Deploy fetches the configuration at deployment time, so changes to the repository affect future deployments without updating the custom target type.
Load Skaffold modules from Cloud Storage
Organizations that store deployment artifacts in Cloud Storage can reference Skaffold configuration files directly from GCS buckets.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const custom_target_type = new gcp.clouddeploy.CustomTargetType("custom-target-type", {
location: "us-central1",
name: "my-custom-target-type",
description: "My custom target type",
customActions: {
renderAction: "renderAction",
deployAction: "deployAction",
includeSkaffoldModules: [{
configs: ["my-config"],
googleCloudStorage: {
source: "gs://example-bucket/dir/configs/*",
path: "skaffold.yaml",
},
}],
},
});
import pulumi
import pulumi_gcp as gcp
custom_target_type = gcp.clouddeploy.CustomTargetType("custom-target-type",
location="us-central1",
name="my-custom-target-type",
description="My custom target type",
custom_actions={
"render_action": "renderAction",
"deploy_action": "deployAction",
"include_skaffold_modules": [{
"configs": ["my-config"],
"google_cloud_storage": {
"source": "gs://example-bucket/dir/configs/*",
"path": "skaffold.yaml",
},
}],
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/clouddeploy"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := clouddeploy.NewCustomTargetType(ctx, "custom-target-type", &clouddeploy.CustomTargetTypeArgs{
Location: pulumi.String("us-central1"),
Name: pulumi.String("my-custom-target-type"),
Description: pulumi.String("My custom target type"),
CustomActions: &clouddeploy.CustomTargetTypeCustomActionsArgs{
RenderAction: pulumi.String("renderAction"),
DeployAction: pulumi.String("deployAction"),
IncludeSkaffoldModules: clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArray{
&clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs{
Configs: pulumi.StringArray{
pulumi.String("my-config"),
},
GoogleCloudStorage: &clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleGoogleCloudStorageArgs{
Source: pulumi.String("gs://example-bucket/dir/configs/*"),
Path: pulumi.String("skaffold.yaml"),
},
},
},
},
})
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 custom_target_type = new Gcp.CloudDeploy.CustomTargetType("custom-target-type", new()
{
Location = "us-central1",
Name = "my-custom-target-type",
Description = "My custom target type",
CustomActions = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsArgs
{
RenderAction = "renderAction",
DeployAction = "deployAction",
IncludeSkaffoldModules = new[]
{
new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs
{
Configs = new[]
{
"my-config",
},
GoogleCloudStorage = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsIncludeSkaffoldModuleGoogleCloudStorageArgs
{
Source = "gs://example-bucket/dir/configs/*",
Path = "skaffold.yaml",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.clouddeploy.CustomTargetType;
import com.pulumi.gcp.clouddeploy.CustomTargetTypeArgs;
import com.pulumi.gcp.clouddeploy.inputs.CustomTargetTypeCustomActionsArgs;
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 custom_target_type = new CustomTargetType("custom-target-type", CustomTargetTypeArgs.builder()
.location("us-central1")
.name("my-custom-target-type")
.description("My custom target type")
.customActions(CustomTargetTypeCustomActionsArgs.builder()
.renderAction("renderAction")
.deployAction("deployAction")
.includeSkaffoldModules(CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs.builder()
.configs("my-config")
.googleCloudStorage(CustomTargetTypeCustomActionsIncludeSkaffoldModuleGoogleCloudStorageArgs.builder()
.source("gs://example-bucket/dir/configs/*")
.path("skaffold.yaml")
.build())
.build())
.build())
.build());
}
}
resources:
custom-target-type:
type: gcp:clouddeploy:CustomTargetType
properties:
location: us-central1
name: my-custom-target-type
description: My custom target type
customActions:
renderAction: renderAction
deployAction: deployAction
includeSkaffoldModules:
- configs:
- my-config
googleCloudStorage:
source: gs://example-bucket/dir/configs/*
path: skaffold.yaml
The googleCloudStorage block replaces the git block when sourcing from GCS. The source property accepts GCS URIs with wildcards, and path specifies which file within the matched objects contains the Skaffold configuration. This approach works well for organizations that generate or archive Skaffold configuration as part of their build process.
Reference Skaffold modules from Cloud Build repositories
Teams using Cloud Build’s repository connections can reference Skaffold configuration from connected Git repositories.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const custom_target_type = new gcp.clouddeploy.CustomTargetType("custom-target-type", {
location: "us-central1",
name: "my-custom-target-type",
description: "My custom target type",
customActions: {
renderAction: "renderAction",
deployAction: "deployAction",
includeSkaffoldModules: [{
configs: ["my-config"],
googleCloudBuildRepo: {
repository: "projects/example/locations/us-central1/connections/git/repositories/example-repo",
path: "configs/skaffold.yaml",
ref: "main",
},
}],
},
});
import pulumi
import pulumi_gcp as gcp
custom_target_type = gcp.clouddeploy.CustomTargetType("custom-target-type",
location="us-central1",
name="my-custom-target-type",
description="My custom target type",
custom_actions={
"render_action": "renderAction",
"deploy_action": "deployAction",
"include_skaffold_modules": [{
"configs": ["my-config"],
"google_cloud_build_repo": {
"repository": "projects/example/locations/us-central1/connections/git/repositories/example-repo",
"path": "configs/skaffold.yaml",
"ref": "main",
},
}],
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/clouddeploy"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := clouddeploy.NewCustomTargetType(ctx, "custom-target-type", &clouddeploy.CustomTargetTypeArgs{
Location: pulumi.String("us-central1"),
Name: pulumi.String("my-custom-target-type"),
Description: pulumi.String("My custom target type"),
CustomActions: &clouddeploy.CustomTargetTypeCustomActionsArgs{
RenderAction: pulumi.String("renderAction"),
DeployAction: pulumi.String("deployAction"),
IncludeSkaffoldModules: clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArray{
&clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs{
Configs: pulumi.StringArray{
pulumi.String("my-config"),
},
GoogleCloudBuildRepo: &clouddeploy.CustomTargetTypeCustomActionsIncludeSkaffoldModuleGoogleCloudBuildRepoArgs{
Repository: pulumi.String("projects/example/locations/us-central1/connections/git/repositories/example-repo"),
Path: pulumi.String("configs/skaffold.yaml"),
Ref: pulumi.String("main"),
},
},
},
},
})
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 custom_target_type = new Gcp.CloudDeploy.CustomTargetType("custom-target-type", new()
{
Location = "us-central1",
Name = "my-custom-target-type",
Description = "My custom target type",
CustomActions = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsArgs
{
RenderAction = "renderAction",
DeployAction = "deployAction",
IncludeSkaffoldModules = new[]
{
new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs
{
Configs = new[]
{
"my-config",
},
GoogleCloudBuildRepo = new Gcp.CloudDeploy.Inputs.CustomTargetTypeCustomActionsIncludeSkaffoldModuleGoogleCloudBuildRepoArgs
{
Repository = "projects/example/locations/us-central1/connections/git/repositories/example-repo",
Path = "configs/skaffold.yaml",
Ref = "main",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.clouddeploy.CustomTargetType;
import com.pulumi.gcp.clouddeploy.CustomTargetTypeArgs;
import com.pulumi.gcp.clouddeploy.inputs.CustomTargetTypeCustomActionsArgs;
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 custom_target_type = new CustomTargetType("custom-target-type", CustomTargetTypeArgs.builder()
.location("us-central1")
.name("my-custom-target-type")
.description("My custom target type")
.customActions(CustomTargetTypeCustomActionsArgs.builder()
.renderAction("renderAction")
.deployAction("deployAction")
.includeSkaffoldModules(CustomTargetTypeCustomActionsIncludeSkaffoldModuleArgs.builder()
.configs("my-config")
.googleCloudBuildRepo(CustomTargetTypeCustomActionsIncludeSkaffoldModuleGoogleCloudBuildRepoArgs.builder()
.repository("projects/example/locations/us-central1/connections/git/repositories/example-repo")
.path("configs/skaffold.yaml")
.ref("main")
.build())
.build())
.build())
.build());
}
}
resources:
custom-target-type:
type: gcp:clouddeploy:CustomTargetType
properties:
location: us-central1
name: my-custom-target-type
description: My custom target type
customActions:
renderAction: renderAction
deployAction: deployAction
includeSkaffoldModules:
- configs:
- my-config
googleCloudBuildRepo:
repository: projects/example/locations/us-central1/connections/git/repositories/example-repo
path: configs/skaffold.yaml
ref: main
The googleCloudBuildRepo block uses Cloud Build’s repository connection format. The repository property references a Cloud Build connection by its full resource path, while path and ref work like the git block. This approach integrates with Cloud Build’s authentication and access control for private repositories.
Beyond these examples
These snippets focus on specific custom target type features: custom action definitions, Skaffold module sourcing from Git, GCS, and Cloud Build, and metadata organization with annotations and labels. They’re intentionally minimal rather than full deployment pipelines.
The examples may reference pre-existing infrastructure such as Cloud Deploy service accounts with appropriate permissions, and Git repositories, GCS buckets, or Cloud Build connections for Skaffold module examples. They focus on defining the custom target type rather than provisioning the surrounding infrastructure.
To keep things focused, common custom target type patterns are omitted, including:
- Custom action implementation details (renderAction and deployAction are string references)
- Target resource creation (CustomTargetType defines the type, not the target itself)
- Delivery pipeline integration
- IAM permissions for service accounts
These omissions are intentional: the goal is to illustrate how each custom target type feature is wired, not provide drop-in deployment modules. See the Cloud Deploy CustomTargetType resource reference for all available configuration options.
Let's configure GCP Cloud Deploy Custom Target Types
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Resource Configuration & Immutability
location, name, and project properties are immutable and cannot be modified after creation. Changes to these properties will require replacing the resource.location, name, and customActions. The customActions property requires both renderAction and deployAction.Annotations & Labels
annotations and labels fields are non-authoritative, meaning Pulumi only manages the annotations and labels present in your configuration. To see all annotations and labels on the resource (including those set by other clients or services), use the effectiveAnnotations and effectiveLabels output properties.Skaffold Modules & Custom Actions
You have three options for including Skaffold modules:
- Git repository - Use
gitwithrepo,path, andrefproperties - Google Cloud Storage - Use
googleCloudStoragewithsourceandpathproperties - Google Cloud Build repository - Use
googleCloudBuildRepowithrepository,path, andrefproperties
Using a different cloud?
Explore integration guides for other cloud providers: