GitHub Deployments
How GitHub push webhooks trigger deployments via BuildKit git context
GitHub Deployments
When users connect a GitHub repository to their Unkey project, push events automatically trigger deployments. This document explains the complete flow from webhook to running containers.
Architecture Overview
BuildKit fetches the repository directly from GitHub using its native git context support. Authentication for private repositories is handled via a GitHub App installation token passed through BuildKit's secret provider.
Webhook Handler
Location: svc/ctrl/api/github_webhook.go
The webhook handler is intentionally thin—it validates, maps, creates a deployment record, and delegates to the durable workflow.
Request Flow
-
Validate HTTP request
- Must be
POST - Requires
X-GitHub-Eventheader - Requires
X-Hub-Signature-256header
- Must be
-
Verify signature
- Uses HMAC-SHA256 with the webhook secret
- Implemented in
githubclient.VerifyWebhookSignature()
-
Parse push payload
- Extracts branch from
refs/heads/<branch> - Non-branch refs (tags) are ignored
- Extracts branch from
-
Map repository to project
- Looks up
github_repo_connectionstable using(installation_id, repository_id) - Returns 200 OK if no connection exists (repo not linked to any project)
- Looks up
-
Determine environment
- If pushed branch equals project's default branch →
production - Otherwise →
preview
- If pushed branch equals project's default branch →
-
Create deployment record
- Trigger workflow
Deploy Workflow
Location: svc/ctrl/worker/deploy/deploy_handler.go
The Restate workflow orchestrates the complete deployment lifecycle. It handles two source types:
GitSource: Build from GitHub repository (via webhook)DockerImage: Use pre-built image (via CLI/API)
Workflow Steps
Failure Handling
The workflow uses a deferred handler to ensure deployments are marked as failed on any error:
Git-Based Build
Location: svc/ctrl/worker/deploy/build.go
The buildDockerImageFromGit() function builds container images using BuildKit's native git context support.
BuildKit Git Context
The build context is passed to BuildKit as a git URL:
BuildKit fetches the repository at the specified commit SHA and uses it as the build context.
GitHub Token Authentication
For private repositories, BuildKit needs authentication. We use BuildKit's secret provider with the well-known secret name GIT_AUTH_TOKEN.github.com:
When BuildKit fetches from github.com, it looks for a secret named GIT_AUTH_TOKEN.github.com and uses it for HTTP authentication.
Token Acquisition
Location: svc/ctrl/worker/github/client.go
The GitHub App client generates installation tokens:
- Create a JWT signed with the App's private key
- Call GitHub API:
POST /app/installations/:id/access_tokens - Receive a short-lived installation token (~1 hour)
Installation tokens are scoped to the repositories where the GitHub App is installed, providing a good security boundary.
Depot Integration
Location: svc/ctrl/worker/deploy/build.go
Depot provides remote BuildKit builders with persistent caching.
Build Flow
Depot Project Management
Each Unkey project gets a corresponding Depot project for caching:
Build Execution
Build Observability
Build status events are streamed to ClickHouse for monitoring:
Proto Definitions
Location: svc/ctrl/proto/hydra/v1/deployment.proto
Important Constraints
Commit SHA
BuildKit requires the full 40-character commit SHA for reliable builds. Short SHAs may fail or fetch unexpected objects.
Private Submodules
Private submodules using SSH URLs won't work with this approach. The GIT_AUTH_TOKEN only provides HTTPS authentication. For SSH submodules, you'd need SSH key forwarding through BuildKit.
Context Path
The context path is normalized before use:
- Whitespace trimmed
- Leading
/stripped .treated as repository root
External API
The external API (ctrlv1.CreateDeploymentRequest) only supports pre-built Docker images. Git-based builds are only triggered via the GitHub webhook integration.
Workflow Orchestration Patterns
The Deploy workflow demonstrates several Restate patterns:
- Idempotent state loading: Deployment ID as stable external key
- Deferred failure handler: Crashes converge to correct terminal status
- Side effects in
restate.Run: Deterministic replay + retry semantics - Unique constraints for idempotency: Sentinel creation uses DB unique index
- Fan-out/join: Parallel region operations with barrier wait