| name | GitHub Integration |
| description | Guide for setting up and managing Docklift's GitHub App integration. |
GitHub Integration Guide
Docklift integrates with GitHub using a GitHub App. This allows for accessing private repositories and receiving webhook events (push) for auto-deployments.
Architecture
- Routes:
backend/src/routes/github.ts
- Service:
backend/src/services/git.ts (for cloning/pulling)
- Frontend helpers:
startGithubInstallSession() / startGithubInstallAndNavigate() in frontend/src/lib/auth.ts
- Authentication: Uses JWT signed with a private key to authenticate as the GitHub App.
- Callback URL: derived from
DOCKLIFT_FRONTEND_URL — it must be the public dashboard URL, or
GitHub redirects the user somewhere unreachable.
Setup Flow (Manifest Flow)
- Initiation: User clicks "Connect GitHub" in UI.
- Manifest Generation:
POST /api/github/manifest generates a GitHub App manifest.
- Redirect: User is redirected to GitHub to create the app.
- Callback: GitHub redirects back to
/api/github/manifest/callback with a code.
- Exchange: Backend exchanges code for App Credentials (ID, Client ID, Secret, Private Key, Webhook Secret).
- Storage: Credentials are stored in the
Settings table in the database.
- Installation: User is redirected to install the newly created app on their account/orgs.
CSRF Protection on the Setup Flow
The manifest and install callbacks are public endpoints (GitHub calls them unauthenticated), so
they are guarded by a one-time nonce instead:
- Each setup attempt writes a per-state file under
data/github-setup/<state>.json
({ createdAt, returnUrl? } — return URL lives per state, not a global github_return_url).
- Also sets an
HttpOnly, SameSite=Lax cookie (docklift_github_state, Secure over HTTPS).
- Verification uses
timingSafeEqual, enforces TTL, and clears the used state — single-use.
- Legacy Settings-key fallback may still be read for older in-flight flows.
Legacy /api/github/callback
Unsafe OAuth code→token exchange is removed. The route may still redirect installation_id to
/api/github/setup; otherwise it sends the user to Settings with github_error=legacy_oauth_disabled.
Key Components
Branches & tags (New Project)
- Branches:
GET /api/github/branches?repo=owner/name&type=public|private
- Tags:
GET /api/github/tags?repo=owner/name&type=public|private — up to 500 tags, newest-first
- UI: Branch | Tag switcher on New Project; tag pin note (push auto-deploy won't follow tags)
- Git:
cloneRepo / pullRepo accept branch or tag; pull fetches --tags and resets to tag when not a remote branch
Repository Listing
- Endpoint:
GET /api/github/repos (optional ?owner=login)
- Response:
{ repositories, failedInstallations, fallbackSingle } — never silently
drop a failed install into an empty success list. fallbackSingle: true means only the
saved install was queried (installations list failed).
- Logic: Lists all App installations (paginated), then repos for each install
(paginated), deduped by repo id.
check-installation must not overwrite a valid
saved install with installations[0] (that hid personal behind the last org).
- UI: New Project Private tab shows All + every connected
@login chip; search
filters the combined list (not a single-account badge). Toast on partial/fallback failures.
Webhooks (Auto-Deploy)
- Endpoint:
POST /api/github/webhook (global — project matching is by repo URL, not /webhook/:projectId).
- UI / API should advertise
/api/github/webhook.
- Event: Listen for
push events.
- Logic:
- Requires
github_webhook_secret to be configured — requests are rejected (401) if the secret is missing (fail closed).
- Verifies HMAC signature FIRST — before any database queries (prevents unauthenticated DB lookups). Uses
req.rawBody captured via express.json({ verify }) callback for accurate comparison.
- Skip when
payload.deleted === true or payload.head_commit == null (deleted-branch pushes).
- Matches repository URL from payload with
Project database entries.
- Triggers deployment for matching projects with
auto_deploy: true.
- Debounced via
recentDeploys Map with 10-second cooldown per project (no global concurrency cap — careful with many projects on one repo).
Authentication
- App Auth: Uses
jsonwebtoken to sign a JWT with the stored Private Key (RS256).
- Installation Auth: Uses the App JWT to request an "Installation Token" for specific API acts (like cloning or fetching repos).
Troubleshooting
- "Repositories not loading": Check if the App is installed on the specific GitHub account.
- "Webhook not triggering": Verify the
webhook_secret in DB matches GitHub. Check if auto_deploy is enabled for the project.
- "Authentication Failed": The Private Key might be missing or invalid in the database.
Useful Database Settings (Settings table)
github_app_id
github_private_key
github_webhook_secret
github_installation_id (Default installation ID)