diff --git a/docs/administration/user-groups/workload-identity/gitlab-ci.mdx b/docs/administration/user-groups/workload-identity/gitlab-ci.mdx
new file mode 100644
index 00000000..cde83099
--- /dev/null
+++ b/docs/administration/user-groups/workload-identity/gitlab-ci.mdx
@@ -0,0 +1,199 @@
+---
+title: Workload Identity for GitLab CI/CD
+---
+
+This guide explains how to configure Workload Identity for GitLab CI/CD 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 | `GitLab Deploy` |
+| **Email** | Unique email prefix (automatically appended with `@workload.bytebase.com`) | `gitlab-deploy` |
+| **Platform** | Select GitLab CI | `GitLab CI` |
+| **Group / Username** | GitLab group or username (required) | `my-group` |
+| **Project** | Project name (leave empty to allow all projects) | `my-project` |
+| **Allowed Branches/Tags** | Select branch/tag restrictions | `All branches and tags` |
+| **Roles** | Assign workspace roles | `GitOps Service Agent` |
+
+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., `gitlab-ci-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 GitLab CI/CD Pipeline
+
+In your GitLab CI/CD pipeline, add the following configuration:
+
+### Request OIDC Token
+
+Add `id_tokens` configuration to get the JWT token from GitLab:
+
+```yaml
+stages:
+ - deploy
+
+deploy-database:
+ stage: deploy
+ image: alpine:latest
+ id_tokens:
+ GITLAB_OIDC_TOKEN:
+ aud: https://gitlab.com
+ variables:
+ BYTEBASE_URL: https://bytebase.example.com
+ WORKLOAD_IDENTITY_EMAIL: gitlab-ci-deploy@workload.bytebase.com
+ before_script:
+ - apk add --no-cache curl jq
+ script:
+ - |
+ # Exchange GitLab OIDC token for Bytebase API token
+ RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
+ -H "Content-Type: application/json" \
+ -d "{\"token\": \"${GITLAB_OIDC_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
+
+ # Verify the token by calling the user info API
+ USER_INFO=$(curl -s "${BYTEBASE_URL}/v1/users/me" \
+ -H "Authorization: Bearer $ACCESS_TOKEN")
+ echo "Authenticated as: $USER_INFO"
+ rules:
+ - if: $CI_COMMIT_BRANCH == "main"
+```
+
+## Complete Example
+
+Here's a complete GitOps workflow that uses Workload Identity to deploy database migrations:
+
+```yaml
+# .gitlab-ci.yml
+stages:
+ - review
+ - deploy
+
+variables:
+ BYTEBASE_URL: https://bytebase.example.com
+ WORKLOAD_IDENTITY_EMAIL: gitlab-deploy@workload.bytebase.com
+
+# SQL Review on merge requests
+sql-review:
+ stage: review
+ image: bytebase/sql-review-action:latest
+ id_tokens:
+ GITLAB_OIDC_TOKEN:
+ aud: https://gitlab.com
+ script:
+ - |
+ # Exchange OIDC token for Bytebase token
+ export BYTEBASE_TOKEN=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
+ -H "Content-Type: application/json" \
+ -d "{\"token\": \"${GITLAB_OIDC_TOKEN}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}" \
+ | jq -r '.accessToken')
+
+ # Run SQL review
+ sql-review --url ${BYTEBASE_URL} --token ${BYTEBASE_TOKEN} \
+ --file-pattern "migrations/**/*.sql"
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ changes:
+ - migrations/**
+
+# Deploy on merge to main
+rollout:
+ stage: deploy
+ image: bytebase/bytebase-action:latest
+ id_tokens:
+ GITLAB_OIDC_TOKEN:
+ aud: https://gitlab.com
+ script:
+ - |
+ # Exchange OIDC token for Bytebase token
+ export BYTEBASE_TOKEN=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
+ -H "Content-Type: application/json" \
+ -d "{\"token\": \"${GITLAB_OIDC_TOKEN}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}" \
+ | jq -r '.accessToken')
+
+ # Create release and rollout
+ bytebase-action rollout \
+ --url ${BYTEBASE_URL} \
+ --token ${BYTEBASE_TOKEN} \
+ --file-pattern "migrations/**/*.sql" \
+ --project projects/my-project \
+ --targets instances/prod/databases/mydb
+ rules:
+ - if: $CI_COMMIT_BRANCH == "main"
+ changes:
+ - migrations/**
+```
+
+
+
+For more details on GitOps workflows, see [GitOps Overview](/gitops/overview) and [Migration-Based Workflow](/gitops/migration-based-workflow/overview).
+
+
+
+## Self-Hosted GitLab
+
+For self-hosted GitLab instances, update the audience (`aud`) to match your GitLab instance URL:
+
+```yaml
+id_tokens:
+ GITLAB_OIDC_TOKEN:
+ aud: https://gitlab.your-company.com
+```
+
+When creating the Workload Identity in Bytebase, ensure the configuration matches your self-hosted GitLab instance.
+
+## Troubleshooting
+
+### Token Exchange Fails
+
+If the token exchange returns an error:
+
+1. **Verify the project path and branch**: Check that your pipeline's project path and branch match the configured values in Bytebase.
+
+2. **Check the audience**: Ensure the `aud` in your `id_tokens` configuration matches your GitLab instance URL (e.g., `https://gitlab.com` for GitLab.com).
+
+3. **Verify OIDC is enabled**: GitLab CI/CD OIDC tokens require GitLab 15.7 or later.
+
+### 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
+script:
+ - |
+ echo "$GITLAB_OIDC_TOKEN" | cut -d. -f2 | base64 -d | jq .
+```
+
+This shows the token's claims including `sub`, `aud`, `namespace_path`, `project_path`, and `ref` that Bytebase validates.
diff --git a/docs/docs.json b/docs/docs.json
index b2e70759..1956e80b 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -247,7 +247,8 @@
"group": "Users & Groups",
"pages": [
"administration/user-groups/overview",
- "administration/user-groups/workload-identity/github-actions"
+ "administration/user-groups/workload-identity/github-actions",
+ "administration/user-groups/workload-identity/gitlab-ci"
]
},
"administration/roles",