diff --git a/docs/docs.json b/docs/docs.json
index c1ee9880..8ec37c2b 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -272,6 +272,13 @@
{
"tab": "Integrations",
"groups": [
+ {
+ "group": "Workload Identity",
+ "pages": [
+ "integrations/workload-identity/overview",
+ "integrations/workload-identity/github-actions"
+ ]
+ },
{
"group": "API",
"pages": [
diff --git a/docs/integrations/workload-identity/github-actions.mdx b/docs/integrations/workload-identity/github-actions.mdx
new file mode 100644
index 00000000..4d9b7dfb
--- /dev/null
+++ b/docs/integrations/workload-identity/github-actions.mdx
@@ -0,0 +1,217 @@
+---
+title: GitHub Actions
+---
+
+This guide explains how to configure Workload Identity for GitHub Actions to authenticate with Bytebase without storing long-lived credentials.
+
+## Step 1: Create a Workload Identity in Bytebase
+
+1. Go to **IAM & Admin** > **Users & Groups**.
+2. Click **Add User** in the upper-right corner.
+3. Select **Workload Identity** as the Type.
+4. Fill in the configuration:
+
+| Field | Description | Example |
+|-------|-------------|---------|
+| **Name** | Display name for this identity | `GitHub Actions Deploy` |
+| **Email** | Unique email for this identity (must end with `@workload.bytebase.com`) | `github-actions-deploy@workload.bytebase.com` |
+| **Platform** | Select GitHub Actions | `GitHub Actions` |
+| **Owner** | GitHub organization or username | `my-org` |
+| **Repository** | Repository name | `my-repo` |
+| **Branch** | Branch name (use `*` for all branches) | `main` |
+
+5. Click **Confirm** to create the Workload Identity.
+
+## Step 2: Assign Roles
+
+After creating the Workload Identity, assign the `GitOps Service Agent` role to enable automated CI/CD workflows:
+
+1. Go to your project's **Settings** > **Members**.
+2. Click **Grant Access**.
+3. Enter the Workload Identity email (e.g., `github-actions-deploy@workload.bytebase.com`).
+4. Select the **GitOps Service Agent** role.
+5. Click **Confirm**.
+
+
+
+The `GitOps Service Agent` role is designed for automated CI/CD workflows, allowing the identity to create and execute database changes. See [Roles and Permissions](/administration/roles) for details.
+
+
+
+## Step 3: Configure GitHub Actions Workflow
+
+In your GitHub Actions workflow, add the following configuration:
+
+### Request OIDC Token
+
+Add `id-token: write` permission and use the `actions/github-script` action to get the token:
+
+```yaml
+name: Deploy Database Changes
+
+on:
+ push:
+ branches: [main]
+
+permissions:
+ id-token: write # Required for OIDC token
+ contents: read
+
+env:
+ BYTEBASE_URL: https://bytebase.example.com
+ WORKLOAD_IDENTITY_EMAIL: github-actions-deploy@workload.bytebase.com
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Get Bytebase Token
+ id: bytebase-token
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const token = await core.getIDToken('https://github.com/${{ github.repository_owner }}');
+ core.setSecret(token);
+ core.setOutput('token', token);
+
+ - name: Exchange for Bytebase API Token
+ id: exchange
+ run: |
+ RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
+ -H "Content-Type: application/json" \
+ -d "{\"token\": \"${{ steps.bytebase-token.outputs.token }}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}")
+
+ ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.accessToken')
+ echo "::add-mask::$ACCESS_TOKEN"
+ echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
+
+ - name: Call Bytebase API
+ run: |
+ curl -s "${BYTEBASE_URL}/v1/projects" \
+ -H "Authorization: Bearer ${{ steps.exchange.outputs.access_token }}"
+```
+
+## Complete Example
+
+Here's a complete workflow that creates a database change using Workload Identity:
+
+```yaml
+name: Database Schema Change
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'migrations/**'
+
+permissions:
+ id-token: write
+ contents: read
+
+env:
+ BYTEBASE_URL: https://bytebase.example.com
+ WORKLOAD_IDENTITY_EMAIL: github-deploy@workload.bytebase.com
+ PROJECT: projects/my-project
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Get OIDC Token
+ id: oidc
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const token = await core.getIDToken('https://github.com/${{ github.repository_owner }}');
+ core.setSecret(token);
+ core.setOutput('token', token);
+
+ - name: Exchange Token
+ id: auth
+ run: |
+ RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
+ -H "Content-Type: application/json" \
+ -d "{\"token\": \"${{ steps.oidc.outputs.token }}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}")
+
+ ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.accessToken')
+ if [ "$ACCESS_TOKEN" = "null" ] || [ -z "$ACCESS_TOKEN" ]; then
+ echo "Failed to get access token"
+ echo $RESPONSE
+ exit 1
+ fi
+
+ echo "::add-mask::$ACCESS_TOKEN"
+ echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
+
+ - name: Create Plan
+ id: plan
+ run: |
+ # Read migration SQL file
+ SQL_CONTENT=$(cat migrations/latest.sql | jq -Rs .)
+
+ RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/${PROJECT}/plans" \
+ -H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"title\": \"Migration from GitHub Actions\",
+ \"steps\": [{
+ \"specs\": [{
+ \"changeDatabaseConfig\": {
+ \"target\": \"instances/prod/databases/mydb\",
+ \"type\": \"MIGRATE\",
+ \"sheet\": \"${SQL_CONTENT}\"
+ }
+ }]
+ }]
+ }")
+
+ PLAN_NAME=$(echo $RESPONSE | jq -r '.name')
+ echo "plan_name=$PLAN_NAME" >> $GITHUB_OUTPUT
+
+ - name: Create Issue and Rollout
+ run: |
+ # Create issue
+ curl -s -X POST "${BYTEBASE_URL}/v1/${PROJECT}/issues" \
+ -H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"title\": \"Schema migration\",
+ \"type\": \"DATABASE_CHANGE\",
+ \"plan\": \"${{ steps.plan.outputs.plan_name }}\"
+ }"
+```
+
+## Troubleshooting
+
+### Token Exchange Fails
+
+If the token exchange returns an error:
+
+1. **Verify the repository and branch**: Check that your workflow's repository, branch match the configured values in Bytebase.
+
+2. **Check the audience**: Ensure the audience in your `getIDToken()` call matches `https://github.com/{owner}`.
+
+### Permission Denied
+
+If API calls return permission errors:
+
+1. Verify the Workload Identity has the `GitOps Service Agent` role assigned.
+2. Check that the Workload Identity is a member of the target project.
+
+### Debug Token Claims
+
+To inspect the OIDC token claims, decode the JWT:
+
+```yaml
+- name: Debug Token
+ run: |
+ echo "${{ steps.oidc.outputs.token }}" | cut -d. -f2 | base64 -d | jq .
+```
+
+This shows the token's claims including `sub`, `aud`, and `iss` that Bytebase validates.
diff --git a/docs/integrations/workload-identity/overview.mdx b/docs/integrations/workload-identity/overview.mdx
new file mode 100644
index 00000000..51c8d44b
--- /dev/null
+++ b/docs/integrations/workload-identity/overview.mdx
@@ -0,0 +1,66 @@
+---
+title: Overview
+---
+
+Workload Identity allows CI/CD pipelines and external services to authenticate with Bytebase using OpenID Connect (OIDC) tokens, eliminating the need to store long-lived credentials as secrets.
+
+## Why Workload Identity?
+
+Traditional CI/CD integration requires storing Service Account keys as secrets in your CI/CD platform. This approach has security risks:
+
+- **Secret sprawl**: Credentials stored in multiple locations increase attack surface
+- **No automatic rotation**: Long-lived keys require manual rotation
+- **Broad access**: Leaked credentials can be used from anywhere
+
+Workload Identity solves these problems by:
+
+- **No stored secrets**: Workflows request short-lived tokens directly from your CI/CD platform
+- **Automatic rotation**: Tokens are generated per-job and expire automatically
+- **Scoped access**: Tokens are bound to specific repositories, branches, and workflows
+
+## How It Works
+
+```
+┌─────────────────┐ 1. Request OIDC Token ┌─────────────────┐
+│ │ ──────────────────────────────► │ │
+│ CI/CD Platform │ │ OIDC Provider │
+│ (e.g. GitHub) │ ◄────────────────────────────── │ │
+│ │ 2. Return JWT Token └─────────────────┘
+└────────┬────────┘
+ │
+ │ 3. Exchange Token
+ │ (JWT + Workload Identity Email)
+ ▼
+┌─────────────────┐
+│ │ 4. Validate Token
+│ Bytebase │ - Verify issuer
+│ │ - Check audience
+└────────┬────────┘ - Match subject pattern
+ │
+ │ 5. Return Bytebase API Token
+ ▼
+┌─────────────────┐
+│ CI/CD Workflow │ 6. Call Bytebase API
+│ continues... │ with API Token
+└─────────────────┘
+```
+
+1. Your CI/CD workflow requests an OIDC token from the platform's token endpoint
+2. The platform returns a signed JWT containing claims about the workflow (repository, branch, etc.)
+3. Your workflow calls Bytebase's `exchangeToken` API with the JWT and Workload Identity email
+4. Bytebase validates the token against the configured Workload Identity settings
+5. Bytebase returns a short-lived API token (1-hour validity)
+6. Your workflow uses this token for subsequent Bytebase API calls
+
+## Supported Platforms
+
+
+
+ Configure OIDC authentication for GitHub Actions workflows
+
+
+
+## Prerequisites
+
+- Bytebase version 3.13.0 or later
+- A CI/CD platform that supports OIDC token issuance