Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: "Connect Any Git or Mercurial Repo to Pulumi with Custom VCS"
date: 2026-04-14
draft: false
meta_desc: "Custom VCS integrations connect any Git or Mercurial version control system to Pulumi Deployments with webhook-driven push-to-deploy and ESC credentials."
meta_image: meta.png
feature_image: feature.png
authors:
- michael-fallihee
tags:
- features
- pulumi-cloud
schema_type: auto

# Social media copy — auto-posted to X, LinkedIn, and Bluesky when merged to master.
# Character limits: X ~280, Bluesky 300, LinkedIn 3000. Leave blank to skip a platform.
social:
twitter:
linkedin:
bluesky:
---

Custom VCS is a new Pulumi Cloud integration that connects any Git or Mercurial version control system to [Pulumi Deployments](/docs/deployments/deployments/) using webhooks and centrally managed credentials. Pulumi Cloud already has native integrations with [GitHub](/docs/version-control/github-app/), [GitLab](/docs/version-control/gitlab/), and [Azure DevOps](/docs/version-control/azure-devops-integration/), but if your team uses a self-hosted or third-party VCS, you've been limited to manually configuring credentials per stack with no webhook-driven automation. Custom VCS closes that gap.

<!--more-->

## The problem

Many teams run self-hosted or third-party Git servers that Pulumi Cloud doesn't have a native integration for, and some teams still use Mercurial. Until now, their only option was the raw git source approach: embedding credentials directly in each stack's deployment settings, with no way to trigger deployments automatically on push, and no support for Mercurial at all.

This meant:

- **No push-to-deploy**: Every deployment had to be triggered manually or through a separate CI pipeline.
- **Scattered credentials**: Each stack configured its own credentials independently, with no centralized management.
- **No org-level integration**: There was no shared configuration that multiple stacks could reference.

## How Custom VCS works

Custom VCS integrations introduce an org-level integration type that works with any Git or Mercurial server. The setup has three parts:

**Credentials through ESC**: Instead of OAuth flows, you store your VCS credentials (a personal access token, SSH key, or username/password) in a [Pulumi ESC](/docs/esc/) environment. The same credential structure works for both Git and Mercurial. The integration references this environment by name and resolves credentials at deployment time. Multiple stacks can share the same credentials without duplicating secrets.

**Manual repository registration**: You add repositories to the integration by name. Pulumi joins the repository name with the integration's base URL to form clone URLs. There's no auto-discovery, so you control exactly which repositories are available.

**Webhook-driven deployments**: Pulumi provides a webhook endpoint and an HMAC shared secret. You configure your VCS server to POST a JSON payload on push events, and Pulumi automatically triggers deployments for matching stacks. The webhook supports branch filtering and optional path filtering.

## What's supported

Custom VCS focuses on the deployment automation use case. Here's how it compares to native integrations:

| Capability | Native integrations | Custom VCS |
|---|---|---|
| Push-to-deploy | Yes | Yes |
| Path filtering | Yes | Yes |
| PR/MR previews | Yes | No |
| Commit status checks | Yes | No |
| PR comments | Yes | No |
| Review stacks | Yes | No |

Features like PR comments, commit statuses, and review stacks require deep API integration with each VCS platform, so they aren't available with Custom VCS. If your VCS provider is GitHub, GitLab, or Azure DevOps, we recommend using the native integration for the full feature set.

## Neo support

[Neo](/docs/ai/), Pulumi's AI assistant, works with Custom VCS integrations for repository operations that don't depend on VCS-specific APIs. Neo can clone and push to Git and Mercurial repositories registered with your Custom VCS integration using the credentials from the integration's ESC environment. Neo cannot open pull requests or create new repositories on Custom VCS servers at this time. Those operations require APIs unique to each VCS platform and are only available through native integrations.

## Get started

To set up a Custom VCS integration:

1. Navigate to **Management** > **Version control** in Pulumi Cloud.
1. Select **Add integration** and choose **Custom VCS**.
1. Provide a name, base URL, and ESC environment containing your credentials.
1. Add your repositories.
1. Configure your VCS server to send webhooks to the provided URL.

For the full setup guide including webhook payload format, HMAC signing, and credential configuration, see the [Custom VCS documentation](/docs/version-control/custom-vcs/).

## Learn more

- [Custom VCS documentation](/docs/version-control/custom-vcs/)
- [Pulumi ESC](/docs/esc/)
- [Pulumi Deployments](/docs/deployments/deployments/)
- [Push-to-deploy](/docs/deployments/deployments/using/triggers/#push-to-deploy)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 7 additions & 3 deletions content/docs/version-control/_index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title_tag: "Pulumi Version Control Integrations"
meta_desc: Connect Pulumi with your version control system using Pulumi-maintained integrations for GitHub, GitLab, and Azure DevOps.
meta_desc: Connect Pulumi with your version control system using integrations for GitHub, GitLab, Azure DevOps, and custom Git or Mercurial VCS providers.
title: Version Control
linktitle: Version Control
h1: Version Control
Expand All @@ -15,13 +15,13 @@ menu:
aliases:
- /docs/integrations/
description: |
Pulumi-maintained version control integrations connect Pulumi with your VCS provider, enabling infrastructure previews on pull requests and automated deployment workflows.
Pulumi version control integrations connect Pulumi with your VCS provider, enabling infrastructure previews on pull requests and automated deployment workflows. Use a native integration for GitHub, GitLab, or Azure DevOps, or connect any Git or Mercurial server with a Custom VCS integration.

sections:
- type: flat
heading: Multiple providers and accounts
description_md: |
You can connect multiple VCS providers to a single Pulumi organization simultaneously, for example GitHub, GitLab, and Azure DevOps all at once. You can also connect multiple accounts of the same provider, such as two separate GitHub organizations or two GitLab groups.
You can connect multiple VCS providers to a single Pulumi organization simultaneously, for example GitHub, GitLab, Azure DevOps, and Custom VCS all at once. You can also connect multiple accounts of the same provider, such as two separate GitHub organizations or two GitLab groups.

GitHub Enterprise Server is currently limited to one connection per Pulumi organization.

Expand All @@ -40,6 +40,10 @@ sections:
heading: Azure DevOps Integration
description: Connect Azure DevOps repositories to Pulumi Cloud Deployments to deploy on push, preview pull requests, and post PR summaries.
link: /docs/version-control/azure-devops-integration/
- image: /logos/tech/git.svg
heading: Custom VCS
description: Connect any Git or Mercurial VCS server to Pulumi Deployments using webhooks and ESC-managed credentials.
link: /docs/version-control/custom-vcs/

- type: flat
heading: Have questions?
Expand Down
205 changes: 205 additions & 0 deletions content/docs/version-control/custom-vcs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
title_tag: "Custom VCS Integration | Version Control"
meta_desc: Connect any Git or Mercurial version control system to Pulumi Cloud for webhook-driven deployments using Custom VCS integrations.
title: "Custom VCS"
h1: "Custom VCS"
meta_image: /images/docs/meta-images/docs-meta.png
menu:
version-control:
name: Custom VCS
parent: version-control-home
weight: 5
---

Custom VCS integrations let you connect any Git or Mercurial version control system to [Pulumi Deployments](/docs/deployments/deployments/), including self-hosted and third-party servers. You configure webhooks on your VCS server to notify Pulumi Cloud when commits are pushed, and Pulumi automatically triggers deployments for matching stacks.

Unlike the first-party [GitHub](/docs/version-control/github-app/), [GitLab](/docs/version-control/gitlab/), and [Azure DevOps](/docs/version-control/azure-devops-integration/) integrations, Custom VCS uses [ESC environments](/docs/esc/) for credential management and requires manual webhook configuration. It supports push-to-deploy but does not support pull request comments, commit status checks, or review stacks.

[Neo](/docs/ai/), Pulumi's AI assistant, can clone and push to Git and Mercurial repositories registered with a Custom VCS integration using the credentials from the integration's ESC environment. Neo cannot open pull requests or create new repositories on Custom VCS servers at this time. Those operations require VCS-specific APIs only available through native integrations.

## Prerequisites

- A [Pulumi Cloud](https://app.pulumi.com) organization
- A Git or Mercurial VCS server accessible from Pulumi Cloud (for cloning)
- Org admin access in Pulumi Cloud
- An [ESC environment](/docs/esc/) containing your VCS credentials (see [Configure credentials in ESC](#configure-credentials-in-esc))

## Configure the integration

{{% notes type="info" %}}
To set up a Custom VCS integration, you must be an org admin in Pulumi Cloud.
{{% /notes %}}

1. [Sign in to your Pulumi account.](https://app.pulumi.com/signin)
1. Navigate to **Management** > **Version control**.
1. Select **Add integration** and choose **Custom VCS**.
1. Enter a **Name** for the integration (e.g., "My Git Server"). Names must be unique within your organization.
1. Enter a **Base URL** — the URL prefix for your repositories (e.g., `https://git.example.com/myorg`). Repository names are appended to this URL to form clone URLs.
1. Select the **ESC environment** containing your VCS credentials, in `project/envName` format (e.g., `vcs-creds/git-prod`).
1. Select **Save**.

By default, Custom VCS integrations are created with a VCS type of `git`. To configure a Mercurial integration, create the integration via the [Pulumi Cloud REST API](/docs/reference/cloud-rest-api/) with `vcsType` set to `hg`.

After saving, Pulumi displays a **webhook URL** and **webhook secret**.

{{% notes type="warning" %}}
Copy the webhook secret immediately. It is only displayed once. If you lose it, you can regenerate it from the integration detail page, but you will need to update your VCS server's webhook configuration with the new secret.
{{% /notes %}}

### Configure credentials in ESC

Custom VCS integrations use an [ESC environment](/docs/esc/) to store VCS credentials. The same credential structure works for both Git and Mercurial integrations. Create an environment with one of the following authentication methods:

#### Personal access token

```yaml
values:
personalAccessToken:
fn::secret: "your-token-here"
```

#### SSH key

```yaml
values:
sshPrivateKey:
fn::secret: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
sshPassword:
fn::secret: "optional-passphrase"
```

#### Basic auth

```yaml
values:
username: "deploy-bot"
password:
fn::secret: "your-password"
```

Credentials are resolved at deployment time using the access permissions of the user who configured the integration. If that user loses access to the ESC environment, deployments will fail with an authentication error.

## Add repositories

Custom VCS integrations do not auto-discover repositories from your VCS server. You must add each repository manually.

1. Navigate to **Management** > **Version control** and select your Custom VCS integration.
1. Select **Add repository**.
1. Enter the repository **name** or path (e.g., `infra-prod` or `team/infra-prod`).
1. Optionally enter a **display name** for the repository.

The repository name is joined with the integration's base URL to form the clone URL. For example, a base URL of `https://git.example.com/myorg` and a repository name of `infra-prod` produces the clone URL `https://git.example.com/myorg/infra-prod`.

To remove a repository, select the repository from the integration detail page and choose **Remove**.

## Configure webhooks

After creating the integration and adding repositories, configure your VCS server to send webhook notifications to Pulumi Cloud.

### Webhook URL

Configure your VCS server to send POST requests to:

```text
https://api.pulumi.com/workflow/generic-vcs
```

### Required headers

Your webhook requests must include the following headers:

| Header | Description | Example |
|---|---|---|
| `X-Generic-VCS-Integration-ID` | Your integration UUID (shown on the integration detail page) | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` |
| `X-Generic-VCS-Signature` | HMAC-SHA256 signature of the request body | `v1=abc123def456...` |
| `Content-Type` | Must be `application/json` | `application/json` |

### Payload format

The request body must be a JSON object with the following fields:

```json
{
"event": "push",
"commit": "abc123def456789...",
"branch": "main",
"repoUrl": "https://git.example.com/myorg/infra-prod",
"sender": "deploy-bot",
"changedFiles": ["infra/main.go", "Pulumi.yaml"]
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `event` | string | Yes | Event type. Only `push` events trigger deployments. |
| `commit` | string | Yes | Full commit SHA (Git) or changeset ID (Mercurial) |
| `branch` | string | Yes | Branch name that was pushed to |
| `repoUrl` | string | Yes | Full repository URL (must match a configured repository) |
| `sender` | string | No | Identifier for the user who pushed |
| `changedFiles` | string[] | No | File paths changed in this push. Enables [path filtering](#deployment-settings). If omitted, path filters are skipped and all pushes trigger deployments. |

### HMAC signing

Pulumi verifies webhook authenticity using HMAC-SHA256 signatures. To sign a request:

1. Compute `HMAC-SHA256(webhook_secret, raw_request_body)` using the webhook secret provided during integration setup.
1. Hex-encode the result.
1. Set the header: `X-Generic-VCS-Signature: v1=<hex-encoded-hmac>`.

The `v1` prefix identifies the signing algorithm version.

{{% notes type="info" %}}
Most VCS systems use their own native webhook payload format. You may need a lightweight transformer — such as a serverless function or CI pipeline step — to convert your VCS server's push events into the payload format above.
{{% /notes %}}

## Deployment settings

Configure deployment behavior for Custom VCS-backed stacks under **Stack** > **Settings** > **Deploy**.

| Setting | Description |
|---|---|
| Push to deploy | Automatically deploy when commits are pushed to the configured branch |
| Path filters | Only trigger deployments when changed files match specified glob patterns (e.g., `infrastructure/**`). Requires the webhook payload to include the `changedFiles` field. |

### Selecting a repository and branch

1. Choose the Custom VCS integration (if multiple are configured).
1. Select a repository from the dropdown. This list shows the repositories you have [manually configured](#add-repositories) on the integration.
1. Enter the target branch name.

{{% notes type="info" %}}
Unlike native integrations, Custom VCS does not auto-discover branches. You must type the branch name manually.
{{% /notes %}}

## Comparison with native integrations

Custom VCS provides webhook-driven push-to-deploy but does not include the deeper VCS platform features available with native integrations.

| Capability | Native integrations | Custom VCS |
|---|---|---|
| Push-to-deploy | Yes | Yes (via webhook) |
| PR/MR previews | Yes | No |
| Commit status checks | Yes | No |
| PR comments | Yes | No |
| Review stacks | Yes | No |
| Auto-discover repos | Yes | No (manual) |
| Branch listing | Yes | No (manual entry) |
| Path filtering | Yes | Yes (requires `changedFiles` in webhook) |
| Credential management | OAuth/app tokens | ESC environment |
| Mercurial support | No | Yes |
| Neo clone/push to repos | Yes | Yes (Git and Mercurial) |
| Neo pull request creation | Yes | No |
| Neo repository creation | Yes | No |

## Troubleshooting

| Issue | Resolution |
|---|---|
| Webhook returns 401 Unauthorized | Verify your HMAC signature computation. Ensure you are signing the raw request body with the correct webhook secret and using HMAC-SHA256. Check that the `v1=` prefix is included. |
| Deployments not triggering | Check that `repoUrl` in your webhook payload matches a configured repository URL. Verify the branch matches your deployment settings. Confirm `event` is set to `push` and `deploy on push` is enabled for the stack. |
| Clone fails with authentication error | Verify credentials in your ESC environment. Check that the configuring user still has access to the ESC environment. |
| Integration shows broken credentials | The ESC environment may have been deleted or the configuring user may have lost access. Update the ESC environment reference or have an admin reconfigure the integration. |
| Path filters not working | Path filtering requires the `changedFiles` field in your webhook payload. If omitted, all pushes trigger deployments regardless of path filter settings. |
1 change: 1 addition & 0 deletions static/logos/tech/git.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading