Configure GCP Cloud Build Bitbucket Server Integration

The gcp:cloudbuild/bitbucketServerConfig:BitbucketServerConfig resource, part of the Pulumi GCP provider, defines the connection between Cloud Build and a Bitbucket Server instance: authentication credentials, network access configuration, and repository selection. This guide focuses on three capabilities: basic authentication and webhook setup, repository selection, and VPC peering for private instances.

Bitbucket Server configurations require Secret Manager secrets for authentication tokens and may reference VPC networks for private connectivity. The examples are intentionally small. Combine them with your own Secret Manager secrets, IAM permissions, and build trigger configurations.

Connect Cloud Build to a Bitbucket Server host

Teams using self-hosted Bitbucket Server establish authentication and webhook configuration so Cloud Build can monitor repositories.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const bbs_config = new gcp.cloudbuild.BitbucketServerConfig("bbs-config", {
    configId: "bbs-config",
    location: "us-central1",
    hostUri: "https://bbs.com",
    secrets: {
        adminAccessTokenVersionName: "projects/myProject/secrets/mybbspat/versions/1",
        readAccessTokenVersionName: "projects/myProject/secrets/mybbspat/versions/1",
        webhookSecretVersionName: "projects/myProject/secrets/mybbspat/versions/1",
    },
    username: "test",
    apiKey: "<api-key>",
});
import pulumi
import pulumi_gcp as gcp

bbs_config = gcp.cloudbuild.BitbucketServerConfig("bbs-config",
    config_id="bbs-config",
    location="us-central1",
    host_uri="https://bbs.com",
    secrets={
        "admin_access_token_version_name": "projects/myProject/secrets/mybbspat/versions/1",
        "read_access_token_version_name": "projects/myProject/secrets/mybbspat/versions/1",
        "webhook_secret_version_name": "projects/myProject/secrets/mybbspat/versions/1",
    },
    username="test",
    api_key="<api-key>")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudbuild"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudbuild.NewBitbucketServerConfig(ctx, "bbs-config", &cloudbuild.BitbucketServerConfigArgs{
			ConfigId: pulumi.String("bbs-config"),
			Location: pulumi.String("us-central1"),
			HostUri:  pulumi.String("https://bbs.com"),
			Secrets: &cloudbuild.BitbucketServerConfigSecretsArgs{
				AdminAccessTokenVersionName: pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
				ReadAccessTokenVersionName:  pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
				WebhookSecretVersionName:    pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
			},
			Username: pulumi.String("test"),
			ApiKey:   pulumi.String("<api-key>"),
		})
		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 bbs_config = new Gcp.CloudBuild.BitbucketServerConfig("bbs-config", new()
    {
        ConfigId = "bbs-config",
        Location = "us-central1",
        HostUri = "https://bbs.com",
        Secrets = new Gcp.CloudBuild.Inputs.BitbucketServerConfigSecretsArgs
        {
            AdminAccessTokenVersionName = "projects/myProject/secrets/mybbspat/versions/1",
            ReadAccessTokenVersionName = "projects/myProject/secrets/mybbspat/versions/1",
            WebhookSecretVersionName = "projects/myProject/secrets/mybbspat/versions/1",
        },
        Username = "test",
        ApiKey = "<api-key>",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudbuild.BitbucketServerConfig;
import com.pulumi.gcp.cloudbuild.BitbucketServerConfigArgs;
import com.pulumi.gcp.cloudbuild.inputs.BitbucketServerConfigSecretsArgs;
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 bbs_config = new BitbucketServerConfig("bbs-config", BitbucketServerConfigArgs.builder()
            .configId("bbs-config")
            .location("us-central1")
            .hostUri("https://bbs.com")
            .secrets(BitbucketServerConfigSecretsArgs.builder()
                .adminAccessTokenVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .readAccessTokenVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .webhookSecretVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .build())
            .username("test")
            .apiKey("<api-key>")
            .build());

    }
}
resources:
  bbs-config:
    type: gcp:cloudbuild:BitbucketServerConfig
    properties:
      configId: bbs-config
      location: us-central1
      hostUri: https://bbs.com
      secrets:
        adminAccessTokenVersionName: projects/myProject/secrets/mybbspat/versions/1
        readAccessTokenVersionName: projects/myProject/secrets/mybbspat/versions/1
        webhookSecretVersionName: projects/myProject/secrets/mybbspat/versions/1
      username: test
      apiKey: <api-key>

The hostUri points to your Bitbucket Server instance. The secrets block references Secret Manager versions containing admin tokens, read tokens, and webhook secrets. Cloud Build uses the username and apiKey to authenticate API calls and verify webhook requests. The configId becomes part of the resource name and must be unique within the location.

Register specific repositories for build triggers

After connecting to Bitbucket Server, you specify which repositories Cloud Build should monitor rather than granting access to all repositories.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const bbs_config_with_repos = new gcp.cloudbuild.BitbucketServerConfig("bbs-config-with-repos", {
    configId: "bbs-config",
    location: "us-central1",
    hostUri: "https://bbs.com",
    secrets: {
        adminAccessTokenVersionName: "projects/myProject/secrets/mybbspat/versions/1",
        readAccessTokenVersionName: "projects/myProject/secrets/mybbspat/versions/1",
        webhookSecretVersionName: "projects/myProject/secrets/mybbspat/versions/1",
    },
    username: "test",
    apiKey: "<api-key>",
    connectedRepositories: [
        {
            projectKey: "DEV",
            repoSlug: "repo1",
        },
        {
            projectKey: "PROD",
            repoSlug: "repo1",
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

bbs_config_with_repos = gcp.cloudbuild.BitbucketServerConfig("bbs-config-with-repos",
    config_id="bbs-config",
    location="us-central1",
    host_uri="https://bbs.com",
    secrets={
        "admin_access_token_version_name": "projects/myProject/secrets/mybbspat/versions/1",
        "read_access_token_version_name": "projects/myProject/secrets/mybbspat/versions/1",
        "webhook_secret_version_name": "projects/myProject/secrets/mybbspat/versions/1",
    },
    username="test",
    api_key="<api-key>",
    connected_repositories=[
        {
            "project_key": "DEV",
            "repo_slug": "repo1",
        },
        {
            "project_key": "PROD",
            "repo_slug": "repo1",
        },
    ])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudbuild"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudbuild.NewBitbucketServerConfig(ctx, "bbs-config-with-repos", &cloudbuild.BitbucketServerConfigArgs{
			ConfigId: pulumi.String("bbs-config"),
			Location: pulumi.String("us-central1"),
			HostUri:  pulumi.String("https://bbs.com"),
			Secrets: &cloudbuild.BitbucketServerConfigSecretsArgs{
				AdminAccessTokenVersionName: pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
				ReadAccessTokenVersionName:  pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
				WebhookSecretVersionName:    pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
			},
			Username: pulumi.String("test"),
			ApiKey:   pulumi.String("<api-key>"),
			ConnectedRepositories: cloudbuild.BitbucketServerConfigConnectedRepositoryArray{
				&cloudbuild.BitbucketServerConfigConnectedRepositoryArgs{
					ProjectKey: pulumi.String("DEV"),
					RepoSlug:   pulumi.String("repo1"),
				},
				&cloudbuild.BitbucketServerConfigConnectedRepositoryArgs{
					ProjectKey: pulumi.String("PROD"),
					RepoSlug:   pulumi.String("repo1"),
				},
			},
		})
		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 bbs_config_with_repos = new Gcp.CloudBuild.BitbucketServerConfig("bbs-config-with-repos", new()
    {
        ConfigId = "bbs-config",
        Location = "us-central1",
        HostUri = "https://bbs.com",
        Secrets = new Gcp.CloudBuild.Inputs.BitbucketServerConfigSecretsArgs
        {
            AdminAccessTokenVersionName = "projects/myProject/secrets/mybbspat/versions/1",
            ReadAccessTokenVersionName = "projects/myProject/secrets/mybbspat/versions/1",
            WebhookSecretVersionName = "projects/myProject/secrets/mybbspat/versions/1",
        },
        Username = "test",
        ApiKey = "<api-key>",
        ConnectedRepositories = new[]
        {
            new Gcp.CloudBuild.Inputs.BitbucketServerConfigConnectedRepositoryArgs
            {
                ProjectKey = "DEV",
                RepoSlug = "repo1",
            },
            new Gcp.CloudBuild.Inputs.BitbucketServerConfigConnectedRepositoryArgs
            {
                ProjectKey = "PROD",
                RepoSlug = "repo1",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.cloudbuild.BitbucketServerConfig;
import com.pulumi.gcp.cloudbuild.BitbucketServerConfigArgs;
import com.pulumi.gcp.cloudbuild.inputs.BitbucketServerConfigSecretsArgs;
import com.pulumi.gcp.cloudbuild.inputs.BitbucketServerConfigConnectedRepositoryArgs;
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 bbs_config_with_repos = new BitbucketServerConfig("bbs-config-with-repos", BitbucketServerConfigArgs.builder()
            .configId("bbs-config")
            .location("us-central1")
            .hostUri("https://bbs.com")
            .secrets(BitbucketServerConfigSecretsArgs.builder()
                .adminAccessTokenVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .readAccessTokenVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .webhookSecretVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .build())
            .username("test")
            .apiKey("<api-key>")
            .connectedRepositories(            
                BitbucketServerConfigConnectedRepositoryArgs.builder()
                    .projectKey("DEV")
                    .repoSlug("repo1")
                    .build(),
                BitbucketServerConfigConnectedRepositoryArgs.builder()
                    .projectKey("PROD")
                    .repoSlug("repo1")
                    .build())
            .build());

    }
}
resources:
  bbs-config-with-repos:
    type: gcp:cloudbuild:BitbucketServerConfig
    properties:
      configId: bbs-config
      location: us-central1
      hostUri: https://bbs.com
      secrets:
        adminAccessTokenVersionName: projects/myProject/secrets/mybbspat/versions/1
        readAccessTokenVersionName: projects/myProject/secrets/mybbspat/versions/1
        webhookSecretVersionName: projects/myProject/secrets/mybbspat/versions/1
      username: test
      apiKey: <api-key>
      connectedRepositories:
        - projectKey: DEV
          repoSlug: repo1
        - projectKey: PROD
          repoSlug: repo1

The connectedRepositories array lists Bitbucket projects and repositories by their projectKey and repoSlug identifiers. Cloud Build creates webhooks for these repositories to receive push and pull request events. This example monitors two repositories: DEV/repo1 and PROD/repo1.

Connect to on-premises Bitbucket via VPC peering

Organizations hosting Bitbucket Server on-premises without public internet access use VPC peering to allow Cloud Build to reach the instance privately.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
import * as std from "@pulumi/std";

const project = gcp.organizations.getProject({});
const servicenetworking = new gcp.projects.Service("servicenetworking", {service: "servicenetworking.googleapis.com"});
const vpcNetwork = new gcp.compute.Network("vpc_network", {name: "vpc-network"}, {
    dependsOn: [servicenetworking],
});
const privateIpAlloc = new gcp.compute.GlobalAddress("private_ip_alloc", {
    name: "private-ip-alloc",
    purpose: "VPC_PEERING",
    addressType: "INTERNAL",
    prefixLength: 16,
    network: vpcNetwork.id,
});
const _default = new gcp.servicenetworking.Connection("default", {
    network: vpcNetwork.id,
    service: "servicenetworking.googleapis.com",
    reservedPeeringRanges: [privateIpAlloc.name],
}, {
    dependsOn: [servicenetworking],
});
const bbs_config_with_peered_network = new gcp.cloudbuild.BitbucketServerConfig("bbs-config-with-peered-network", {
    configId: "bbs-config",
    location: "us-central1",
    hostUri: "https://bbs.com",
    secrets: {
        adminAccessTokenVersionName: "projects/myProject/secrets/mybbspat/versions/1",
        readAccessTokenVersionName: "projects/myProject/secrets/mybbspat/versions/1",
        webhookSecretVersionName: "projects/myProject/secrets/mybbspat/versions/1",
    },
    username: "test",
    apiKey: "<api-key>",
    peeredNetwork: pulumi.all([vpcNetwork.id, project, project]).apply(([id, project, project1]) => std.replaceOutput({
        text: id,
        search: project.name,
        replace: project1.number,
    })).apply(invoke => invoke.result),
    sslCa: `-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
`,
}, {
    dependsOn: [_default],
});
import pulumi
import pulumi_gcp as gcp
import pulumi_std as std

project = gcp.organizations.get_project()
servicenetworking = gcp.projects.Service("servicenetworking", service="servicenetworking.googleapis.com")
vpc_network = gcp.compute.Network("vpc_network", name="vpc-network",
opts = pulumi.ResourceOptions(depends_on=[servicenetworking]))
private_ip_alloc = gcp.compute.GlobalAddress("private_ip_alloc",
    name="private-ip-alloc",
    purpose="VPC_PEERING",
    address_type="INTERNAL",
    prefix_length=16,
    network=vpc_network.id)
default = gcp.servicenetworking.Connection("default",
    network=vpc_network.id,
    service="servicenetworking.googleapis.com",
    reserved_peering_ranges=[private_ip_alloc.name],
    opts = pulumi.ResourceOptions(depends_on=[servicenetworking]))
bbs_config_with_peered_network = gcp.cloudbuild.BitbucketServerConfig("bbs-config-with-peered-network",
    config_id="bbs-config",
    location="us-central1",
    host_uri="https://bbs.com",
    secrets={
        "admin_access_token_version_name": "projects/myProject/secrets/mybbspat/versions/1",
        "read_access_token_version_name": "projects/myProject/secrets/mybbspat/versions/1",
        "webhook_secret_version_name": "projects/myProject/secrets/mybbspat/versions/1",
    },
    username="test",
    api_key="<api-key>",
    peered_network=vpc_network.id.apply(lambda id: std.replace(text=id,
        search=project.name,
        replace=project.number)).apply(lambda invoke: invoke.result),
    ssl_ca="""-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
""",
    opts = pulumi.ResourceOptions(depends_on=[default]))
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/cloudbuild"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/servicenetworking"
	"github.com/pulumi/pulumi-std/sdk/go/std"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{
}, nil);
if err != nil {
return err
}
servicenetworking, err := projects.NewService(ctx, "servicenetworking", &projects.ServiceArgs{
Service: pulumi.String("servicenetworking.googleapis.com"),
})
if err != nil {
return err
}
vpcNetwork, err := compute.NewNetwork(ctx, "vpc_network", &compute.NetworkArgs{
Name: pulumi.String("vpc-network"),
}, pulumi.DependsOn([]pulumi.Resource{
servicenetworking,
}))
if err != nil {
return err
}
privateIpAlloc, err := compute.NewGlobalAddress(ctx, "private_ip_alloc", &compute.GlobalAddressArgs{
Name: pulumi.String("private-ip-alloc"),
Purpose: pulumi.String("VPC_PEERING"),
AddressType: pulumi.String("INTERNAL"),
PrefixLength: pulumi.Int(16),
Network: vpcNetwork.ID(),
})
if err != nil {
return err
}
_default, err := servicenetworking.NewConnection(ctx, "default", &servicenetworking.ConnectionArgs{
Network: vpcNetwork.ID(),
Service: pulumi.String("servicenetworking.googleapis.com"),
ReservedPeeringRanges: pulumi.StringArray{
privateIpAlloc.Name,
},
}, pulumi.DependsOn([]pulumi.Resource{
servicenetworking,
}))
if err != nil {
return err
}
invokeReplace, err := std.Replace(ctx, &std.ReplaceArgs{
Text: id,
Search: project.Name,
Replace: project.Number,
}, nil)
if err != nil {
return err
}
_, err = cloudbuild.NewBitbucketServerConfig(ctx, "bbs-config-with-peered-network", &cloudbuild.BitbucketServerConfigArgs{
ConfigId: pulumi.String("bbs-config"),
Location: pulumi.String("us-central1"),
HostUri: pulumi.String("https://bbs.com"),
Secrets: &cloudbuild.BitbucketServerConfigSecretsArgs{
AdminAccessTokenVersionName: pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
ReadAccessTokenVersionName: pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
WebhookSecretVersionName: pulumi.String("projects/myProject/secrets/mybbspat/versions/1"),
},
Username: pulumi.String("test"),
ApiKey: pulumi.String("<api-key>"),
PeeredNetwork: pulumi.String(vpcNetwork.ID().ApplyT(func(id string) (std.ReplaceResult, error) {
%!v(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)).(std.ReplaceResultOutput).ApplyT(func(invoke std.ReplaceResult) (*string, error) {
return invoke.Result, nil
}).(pulumi.StringPtrOutput)),
SslCa: pulumi.String("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n"),
}, pulumi.DependsOn([]pulumi.Resource{
_default,
}))
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
using Std = Pulumi.Std;

return await Deployment.RunAsync(() => 
{
    var project = Gcp.Organizations.GetProject.Invoke();

    var servicenetworking = new Gcp.Projects.Service("servicenetworking", new()
    {
        ServiceName = "servicenetworking.googleapis.com",
    });

    var vpcNetwork = new Gcp.Compute.Network("vpc_network", new()
    {
        Name = "vpc-network",
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            servicenetworking,
        },
    });

    var privateIpAlloc = new Gcp.Compute.GlobalAddress("private_ip_alloc", new()
    {
        Name = "private-ip-alloc",
        Purpose = "VPC_PEERING",
        AddressType = "INTERNAL",
        PrefixLength = 16,
        Network = vpcNetwork.Id,
    });

    var @default = new Gcp.ServiceNetworking.Connection("default", new()
    {
        Network = vpcNetwork.Id,
        Service = "servicenetworking.googleapis.com",
        ReservedPeeringRanges = new[]
        {
            privateIpAlloc.Name,
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            servicenetworking,
        },
    });

    var bbs_config_with_peered_network = new Gcp.CloudBuild.BitbucketServerConfig("bbs-config-with-peered-network", new()
    {
        ConfigId = "bbs-config",
        Location = "us-central1",
        HostUri = "https://bbs.com",
        Secrets = new Gcp.CloudBuild.Inputs.BitbucketServerConfigSecretsArgs
        {
            AdminAccessTokenVersionName = "projects/myProject/secrets/mybbspat/versions/1",
            ReadAccessTokenVersionName = "projects/myProject/secrets/mybbspat/versions/1",
            WebhookSecretVersionName = "projects/myProject/secrets/mybbspat/versions/1",
        },
        Username = "test",
        ApiKey = "<api-key>",
        PeeredNetwork = Output.Tuple(vpcNetwork.Id, project, project).Apply(values =>
        {
            var id = values.Item1;
            var project = values.Item2;
            var project1 = values.Item3;
            return Std.Replace.Invoke(new()
            {
                Text = id,
                Search = project.Apply(getProjectResult => getProjectResult.Name),
                Replace = project1.Number,
            });
        }).Apply(invoke => invoke.Result),
        SslCa = @"-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
",
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            @default,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.projects.Service;
import com.pulumi.gcp.projects.ServiceArgs;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.GlobalAddress;
import com.pulumi.gcp.compute.GlobalAddressArgs;
import com.pulumi.gcp.servicenetworking.Connection;
import com.pulumi.gcp.servicenetworking.ConnectionArgs;
import com.pulumi.gcp.cloudbuild.BitbucketServerConfig;
import com.pulumi.gcp.cloudbuild.BitbucketServerConfigArgs;
import com.pulumi.gcp.cloudbuild.inputs.BitbucketServerConfigSecretsArgs;
import com.pulumi.std.StdFunctions;
import com.pulumi.std.inputs.ReplaceArgs;
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) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        var servicenetworking = new Service("servicenetworking", ServiceArgs.builder()
            .service("servicenetworking.googleapis.com")
            .build());

        var vpcNetwork = new Network("vpcNetwork", NetworkArgs.builder()
            .name("vpc-network")
            .build(), CustomResourceOptions.builder()
                .dependsOn(servicenetworking)
                .build());

        var privateIpAlloc = new GlobalAddress("privateIpAlloc", GlobalAddressArgs.builder()
            .name("private-ip-alloc")
            .purpose("VPC_PEERING")
            .addressType("INTERNAL")
            .prefixLength(16)
            .network(vpcNetwork.id())
            .build());

        var default_ = new Connection("default", ConnectionArgs.builder()
            .network(vpcNetwork.id())
            .service("servicenetworking.googleapis.com")
            .reservedPeeringRanges(privateIpAlloc.name())
            .build(), CustomResourceOptions.builder()
                .dependsOn(servicenetworking)
                .build());

        var bbs_config_with_peered_network = new BitbucketServerConfig("bbs-config-with-peered-network", BitbucketServerConfigArgs.builder()
            .configId("bbs-config")
            .location("us-central1")
            .hostUri("https://bbs.com")
            .secrets(BitbucketServerConfigSecretsArgs.builder()
                .adminAccessTokenVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .readAccessTokenVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .webhookSecretVersionName("projects/myProject/secrets/mybbspat/versions/1")
                .build())
            .username("test")
            .apiKey("<api-key>")
            .peeredNetwork(vpcNetwork.id().applyValue(_id -> StdFunctions.replace(ReplaceArgs.builder()
                .text(_id)
                .search(project.name())
                .replace(project.number())
                .build())).applyValue(_invoke -> _invoke.result()))
            .sslCa("""
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
            """)
            .build(), CustomResourceOptions.builder()
                .dependsOn(default_)
                .build());

    }
}
resources:
  servicenetworking:
    type: gcp:projects:Service
    properties:
      service: servicenetworking.googleapis.com
  vpcNetwork:
    type: gcp:compute:Network
    name: vpc_network
    properties:
      name: vpc-network
    options:
      dependsOn:
        - ${servicenetworking}
  privateIpAlloc:
    type: gcp:compute:GlobalAddress
    name: private_ip_alloc
    properties:
      name: private-ip-alloc
      purpose: VPC_PEERING
      addressType: INTERNAL
      prefixLength: 16
      network: ${vpcNetwork.id}
  default:
    type: gcp:servicenetworking:Connection
    properties:
      network: ${vpcNetwork.id}
      service: servicenetworking.googleapis.com
      reservedPeeringRanges:
        - ${privateIpAlloc.name}
    options:
      dependsOn:
        - ${servicenetworking}
  bbs-config-with-peered-network:
    type: gcp:cloudbuild:BitbucketServerConfig
    properties:
      configId: bbs-config
      location: us-central1
      hostUri: https://bbs.com
      secrets:
        adminAccessTokenVersionName: projects/myProject/secrets/mybbspat/versions/1
        readAccessTokenVersionName: projects/myProject/secrets/mybbspat/versions/1
        webhookSecretVersionName: projects/myProject/secrets/mybbspat/versions/1
      username: test
      apiKey: <api-key>
      peeredNetwork:
        fn::invoke:
          function: std:replace
          arguments:
            text: ${vpcNetwork.id}
            search: ${project.name}
            replace: ${project.number}
          return: result
      sslCa: |
        -----BEGIN CERTIFICATE-----
        -----END CERTIFICATE-----
        -----BEGIN CERTIFICATE-----
        -----END CERTIFICATE-----        
    options:
      dependsOn:
        - ${default}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The peeredNetwork property references a VPC network configured for private service connection. Cloud Build uses this network when making requests to the Bitbucket Server instance. The sslCa property provides the SSL certificate chain in PEM format for validating HTTPS connections. This example creates the VPC peering infrastructure (network, IP allocation, service connection) before configuring the Bitbucket connection.

Beyond these examples

These snippets focus on specific Bitbucket Server configuration features: authentication and webhook configuration, repository selection, and VPC peering for private access. They’re intentionally minimal rather than full CI/CD pipelines.

The examples rely on pre-existing infrastructure such as Secret Manager secrets for authentication tokens, a Bitbucket Server instance (on-premises or cloud-hosted), and VPC networks for peered network configurations. They focus on connection configuration rather than provisioning the surrounding infrastructure.

To keep things focused, common integration patterns are omitted, including:

  • Secret Manager secret creation and rotation
  • IAM permissions for Cloud Build service account
  • Build trigger configuration (separate resource)
  • SSL certificate generation and management

These omissions are intentional: the goal is to illustrate how each Bitbucket Server connection feature is wired, not provide drop-in CI/CD modules. See the BitbucketServerConfig resource reference for all available configuration options.

Let's configure GCP Cloud Build Bitbucket Server Integration

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Immutability
What properties can't be changed after creating a Bitbucket Server config?
The apiKey, hostUri, configId, location, and project properties are immutable. Changing apiKey or hostUri will delete and recreate the resource, so plan accordingly or create a new config instead.
What secrets do I need to configure Bitbucket Server integration?
You must provide three Secret Manager secret version names in the secrets property: adminAccessTokenVersionName, readAccessTokenVersionName, and webhookSecretVersionName.
Networking & Private Connectivity
How do I connect Cloud Build to an on-premises Bitbucket Server?
Set the peeredNetwork property to connect via VPC peering. The VPC network must be enabled for private service connection. If peeredNetwork is empty, Cloud Build will connect over the public internet.
What format should the peered network property use?
Use the format projects/{project}/global/networks/{network}, where {project} is a project number or ID and {network} is the VPC network name. The peered network example shows transforming the network ID to this format.
What SSL certificate format does Bitbucket Server config accept?
The sslCa property accepts PEM format certificates with .pem, .cer, or .crt extensions.
Repository Management
How do I connect specific Bitbucket repositories to Cloud Build?
Use the connectedRepositories array with projectKey and repoSlug for each repository you want to connect.
Can I import an existing Bitbucket Server config into Pulumi?
Yes, you can import using the format projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}, or the shorter formats {{project}}/{{location}}/{{config_id}} or {{location}}/{{config_id}}.

Using a different cloud?

Explore integration guides for other cloud providers: