Skip to content

GitHub Integration

Diverge provides first-class GitHub integration for automatic preview environments on every Pull Request. This guide covers webhook setup, GitHub Actions workflows with change detection, sticky PR comments, and merge gating.

Install the Diverge controller with GitHub notifier enabled:

Terminal window
helm repo add diverge https://divergedev.github.io/diverge
helm repo update
helm install diverge diverge/diverge \
--namespace diverge-system \
--create-namespace \
--set notifierProvider=github \
--set controller.env.DIVERGE_NOTIFIER_TOKEN=<github-token> \
--set controller.env.DIVERGE_WEBHOOK_SECRET=<webhook-secret>
Variable Description
DIVERGE_NOTIFIER_TOKEN GitHub personal access token or fine-grained token with issues:write and statuses:write
DIVERGE_WEBHOOK_SECRET Shared secret for HMAC-SHA256 webhook signature validation

Navigate to your GitHub repository or organization: Settings → Webhooks → Add webhook.

Setting Value
Payload URL https://diverge.yourdomain.com/github-webhook
Content type application/json
Secret Your DIVERGE_WEBHOOK_SECRET value

Select Let me select individual events, and check:

  • Pull requests (covers opened, synchronize, reopened, closed)

For PreviewGroups (multi-service), add a second webhook:

Setting Value
Payload URL https://diverge.yourdomain.com/github-previewgroup-webhook
PR Action Diverge Behavior
opened, reopened Creates Environment / PreviewGroup CR
synchronize (new push) Updates the CR with new commit SHA
closed Deletes the CR (triggers teardown)

Diverge provides a complete GitHub Actions workflow for automated preview environments. Add this to .github/workflows/diverge-preview.yml:

name: Diverge Preview Environment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
concurrency:
group: diverge-preview-${{ github.head_ref || github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write

Use diverge diff to detect which services changed based on git diff and .diverge.yaml path mappings:

- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history required for accurate diffing
- name: Install Diverge CLI
uses: divergedev/setup-diverge@v1
- name: Detect changed services
id: diff
run: |
DIFF_JSON=$(diverge diff --output json --base origin/main)
SERVICES=$(echo "$DIFF_JSON" | jq -r '.services // [] | join(", ")')
COUNT=$(echo "$DIFF_JSON" | jq -r '.count // 0')
echo "changed_services=${SERVICES}" >> $GITHUB_OUTPUT
echo "changed_count=${COUNT}" >> $GITHUB_OUTPUT

Trace the ingress path for each changed service to understand request routing:

- name: Trace request routes
id: routes
run: |
IFS=', ' read -ra SERVICES <<< "${{ steps.diff.outputs.changed_services }}"
for svc in "${SERVICES[@]}"; do
echo "::group::Route trace for $svc"
diverge route "$svc"
echo "::endgroup::"
done

Create the preview environment and post a summary comment on the PR:

- name: Deploy preview environment
id: deploy
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
ENV_NAME="preview-mr-${PR_NUMBER}"
DEPLOY_OUTPUT=$(diverge create --mr "${PR_NUMBER}")
echo "$DEPLOY_OUTPUT"
PREVIEW_URL=$(echo "$DEPLOY_OUTPUT" | grep -o 'https://[^ ]*' | head -n 1)
if [ -z "$PREVIEW_URL" ]; then
PREVIEW_URL="https://${ENV_NAME}.preview.example.com"
fi
echo "env_name=${ENV_NAME}" >> $GITHUB_OUTPUT
echo "preview_url=${PREVIEW_URL}" >> $GITHUB_OUTPUT
- name: Comment Preview URL on PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const commentIdentifier = '<!-- diverge-preview-comment -->';
const body = `${commentIdentifier}
## 🚀 Diverge Preview Environment
| Property | Value |
|---|---|
| **Environment** | \`${{ steps.deploy.outputs.env_name }}\` |
| **Preview URL** | [${{ steps.deploy.outputs.preview_url }}](${{ steps.deploy.outputs.preview_url }}) |
| **Changed Services** | \`${{ steps.diff.outputs.changed_services }}\` |
| **Deploy Mode** | \`delta\` |
*Updated for commit \`${context.sha.substring(0, 7)}\`.*`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body?.includes(commentIdentifier));
if (existing) {
await github.rest.issues.updateComment({
...context.repo, comment_id: existing.id, body,
});
} else {
await github.rest.issues.createComment({
...context.repo, issue_number: context.issue.number, body,
});
}

The <!-- diverge-preview-comment --> HTML marker ensures only one comment is maintained per PR — subsequent pushes update the existing comment.

Tear down environments when the PR is closed or merged:

cleanup:
name: Teardown Preview Environment
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- uses: divergedev/setup-diverge@v1
- name: Delete preview environment
run: |
diverge delete "preview-mr-${{ github.event.pull_request.number }}" || true

The divergedev/setup-diverge action handles CLI installation and caching automatically:

- uses: divergedev/setup-diverge@v1

To pin a specific version:

- uses: divergedev/setup-diverge@v1
with:
version: '0.8.2'

It resolves the latest release via GitHub API, caches the binary per OS/arch/version using actions/cache, and adds diverge to PATH.


Diverge posts diverge/preview commit statuses to GitHub, enabling required status checks.

Status Meaning
pending Environment is being provisioned
success Environment is healthy and serving traffic
failure An error occurred during provisioning
error Environment was canceled
  1. Go to Settings → Branches → Branch protection rules
  2. Check Require status checks to pass before merging
  3. Search for and add diverge/preview

Diverge automatically posts and updates Pull Request comments through the environment lifecycle:

Event Comment Content
Created Services being deployed, routing header
Ready Preview URL, curl command with routing header
Failed Error details, failed conditions, controller log hints
Teardown Cleanup confirmation and reason

Comments are deduplicated — Diverge tracks the comment ID and updates the existing comment on subsequent events.


Standard GitHub Actions variables used in Diverge workflows:

Variable Used For
github.event.pull_request.number PR number for diverge create --mr
github.head_ref Source branch for concurrency grouping
github.event.action Cleanup trigger (closed)
context.sha Commit hash for comment updates
secrets.KUBECONFIG Kubernetes cluster credentials
secrets.GITHUB_TOKEN PR comment posting (auto-provided)

Feature GitHub GitLab
Webhook authentication HMAC-SHA256 Token (constant-time)
MR/PR comments ✅ Sticky comments ✅ Sticky notes
Commit statuses diverge/preview diverge/preview
PreviewGroup support
Change detection (diverge diff)
Route tracing (diverge route)
Merge gating ✅ Branch protection ✅ Protected branches
Config fetching ✅ Contents API ✅ Repository Files API
CLI install + caching setup-diverge@v1 diverge-cli Docker image
Self-hosted support ✅ (--gitlab-url)

Issue Solution
No PR comments Verify workflow has pull-requests: write permission
Webhook 404 Check payload URL matches /github-webhook
Webhook signature failures Ensure DIVERGE_WEBHOOK_SECRET matches the GitHub webhook secret
Status checks not appearing Verify DIVERGE_NOTIFIER_TOKEN has statuses:write scope
Stale PR comments Check the <!-- diverge-preview-comment --> marker is present