Configure GCP Cloud Build Bitbucket Server Integration

The gcp:cloudbuild/bitbucketServerConfig:BitbucketServerConfig resource, part of the Pulumi GCP provider, establishes the connection between Cloud Build and a self-hosted Bitbucket Server instance, including authentication, webhook configuration, and optional private network connectivity. This guide focuses on three capabilities: basic authentication and webhook setup, repository registration for build triggers, and VPC peering for on-premises instances.

All configurations require pre-existing Secret Manager secrets for authentication tokens and webhook secrets. The VPC peering example additionally requires a configured VPC network with service networking enabled. The examples are intentionally small. Combine them with your own secret management, repository structure, and network topology.

Connect Cloud Build to a Bitbucket Server host

Teams using self-hosted Bitbucket Server need to connect Cloud Build to enable automated builds from repository events.

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 three Secret Manager secret versions: an admin token for configuration, a read token for repository access, and a webhook secret for validating incoming events. The apiKey attaches to webhooks for additional validation. Once set, hostUri and apiKey cannot be changed without recreating the resource.

Register specific repositories for build triggers

After establishing the connection, specify which repositories should trigger Cloud Build workflows.

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 repositories by projectKey and repoSlug. Each entry enables Cloud Build to receive webhook events from that repository. This example connects two repositories across different projects, allowing selective automation rather than monitoring all repositories on the server.

Connect to on-premises Bitbucket via VPC peering

Organizations hosting Bitbucket Server on-premises without public internet access need VPC peering for private connectivity.

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 enables private connectivity by routing Cloud Build requests through a VPC network. This example creates the necessary VPC infrastructure: a network, a reserved IP range for peering, and a service networking connection. The sslCa property provides the SSL certificate for validating HTTPS connections to the Bitbucket Server instance. Without peeredNetwork, Cloud Build attempts to reach the instance over the public internet.

Beyond these examples

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

The examples rely on pre-existing infrastructure such as Secret Manager secrets (admin token, read token, webhook secret), Bitbucket Server instance with projects and repositories, and VPC network and service networking API (for peered network example). They focus on establishing the connection rather than provisioning the surrounding infrastructure.

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

  • SSL certificate management and rotation
  • Multiple repository configurations across projects
  • Network security and firewall rules
  • Webhook validation and troubleshooting

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 are immutable after creation?
The apiKey, hostUri, configId, location, and project properties cannot be changed after creation. Modifying apiKey or hostUri will delete and recreate the resource.
What happens if I need to change the Bitbucket Server URL?
The hostUri is immutable. To change it, you must create a new BitbucketServerConfig resource.
Can I rotate the API key without recreating the resource?
No, the apiKey is immutable. Changing it will result in deleting and recreating the resource.
Networking & Connectivity
How do I connect to an on-premises Bitbucket Server instance?
Set peeredNetwork to your VPC network in the format projects/{project}/global/networks/{network}. The VPC must be enabled for private service connection, as shown in the peered network example.
What format does the peered network property require?
The peeredNetwork must be in the format projects/{project}/global/networks/{network}, where {project} is a project number or ID and {network} is the VPC network name.
When should I use peered network configuration?
Use peeredNetwork when your Bitbucket Server instance is hosted on-premises and not reachable by public internet. If left empty, calls will be made over the public internet.
Authentication & Secrets
What secrets are required for the configuration?

The secrets object requires three Secret Manager version names:

  1. adminAccessTokenVersionName
  2. readAccessTokenVersionName
  3. webhookSecretVersionName
What format should the SSL certificate be in?
The sslCa property accepts PEM format certificates with extensions .pem, .cer, or .crt.
Repository Management
How do I connect specific repositories to the configuration?
Use the connectedRepositories array with objects containing projectKey and repoSlug for each repository you want to connect.
Can I add repositories after creating the configuration?
Yes, connectedRepositories is not immutable and can be updated after creation.

Using a different cloud?

Explore integration guides for other cloud providers: