Files
zitadel/apps/docs/content/guides/migrate/sources/keycloak.mdx
T
d8ea041c56 docs: introduce versioned docs and migrate to fuma (#11166)
## Todos for release

- [x] Configure Env in docs project on vercel
- [x] Configure Root Path in the docs project on vercel
- [ ] Remove old CSP https://github.com/zitadel/website/pull/1592

## What we did

This pull request migrates the project documentation from the old
`docs/` directory to the new `apps/docs/` directory, introduces a new
documentation system built with Next.js and Fumadocs, and updates all
relevant references, configuration files, and documentation to reflect
this change. It also adds new configuration and ignore files for the new
documentation app, updates CI and linting to exclude the new docs from
certain checks, and revises the contributing guidelines accordingly.

**Documentation System Migration and New Docs App**

* Migrated all documentation from `docs/` to `apps/docs/`, and updated
all references in `README.md`, `CONTRIBUTING.md`, and other files to
point to the new location.
[[1]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L585-L640)
[[2]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L660-R607)
[[3]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L740-R687)
[[4]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L2-R3)
[[5]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L30-R30)
* Added a new Next.js/Fumadocs-based documentation app under
`apps/docs/`, including core app files, layouts, routing, search API,
and a comprehensive `README.md` with development and contribution
instructions.
[[1]](diffhunk://#diff-5a1b07344a2c1b4d3f37b23ff1388b62cd9f57dea3c1cd23d8a0412b7602b132R1-R74)
[[2]](diffhunk://#diff-462b9ad1eabbb7d1c29bb9c36e4931eb180fd7190900e6d2babf8f4d66ad1c28R1-R39)
[[3]](diffhunk://#diff-e16ae25660ded787b10ac35dea96d5ecaacf895dae0afc8a9bd4382dc79a8c87R1-R7)
[apps/docs/app/[[...slug]]/layout.tsxR1-R81](diffhunk://#diff-59e08acde4e805b7aeccef1dcf98f1d71dfc550777e6b9402085cee0e9fa4e0aR1-R81),
[apps/docs/app/[[...slug]]/page.tsxR1-R79](diffhunk://#diff-e5df3f80d0fa01e12d63d81f29c57a9d14e846c78fd3beabb2ef768e38fd9580R1-R79),
[[4]](diffhunk://#diff-389b34918e040cacaa87cd7201ffa462cc2b0b716736f537e3d3c660ac69353bR1-R7)
[[5]](diffhunk://#diff-d3b03416d1c457b19f1c27b26f2db741412df841b875c26ccadc36e4522247f4R1-R29)
[[6]](diffhunk://#diff-c8fb8339570a5305809be7c618e14705fd86390dc278e6ce8ba224a7bc8b0c3cR1-R25)

**Configuration and Tooling Updates**

* Updated `.github/workflows/codeql.yml`, `.golangci.yaml`, and
`.github/dependabot.yml` to properly handle the new docs app: excluded
`apps/docs` from certain checks, added npm dependency updates for the
docs app, and excluded generated content.
[[1]](diffhunk://#diff-12783128521e452af0cfac94b99b8d250413c516ec71fe6d97dbea666ff7ba27L8-R14)
[[2]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R5-R6)
[[3]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R126-R129)
[[4]](diffhunk://#diff-dd4fbda47e51f1e35defb9275a9cd9c212ecde0b870cba89ddaaae65c5f3cd28R89-R106)
* Updated `.devcontainer/devcontainer.json` to use the latest Go 1.25.3
version for consistency.

**Licensing and Miscellaneous**

* Added `apps/docs/` to the list of licensed directories in
`LICENSING.md`.

These changes ensure the documentation is now maintained in a modern,
scalable system and all project tooling is updated to support the new
structure.

---------

Co-authored-by: Federico Coppede <[email protected]>
2026-01-29 08:38:36 -08:00

254 lines
7.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Migrate from Keycloak
sidebar_label: From Keycloak
---
## 1. Introduction
This guide will walk you through the steps to migrate users from **Keycloak** to **ZITADEL**, including password hashes, using the `zitadel-tools` CLI and the user import APIs.
**What you'll learn with this guide**
* How to export users from Keycloak
* Use of the ZITADEL migration tooling
* Performing the user import via ZITADEL's API
* Troubleshooting and validating the migration
---
## 2. Prerequisites
### 2.1. Install Go
The migration tool is written in Go. Download and install the latest version of Go from the [official Go website](https://go.dev/doc/install).
### 2.2. Create a ZITADEL Instance and Organization
You'll need a target organization in ZITADEL to import your users. You can create a new organization or use an existing one.
If you don't have a ZITADEL instance, you can [sign up for free here](https://zitadel.com) to create a new one for you.
See: [Managing Organizations in ZITADEL](https://zitadel.com/docs/guides/manage/console/organizations-overview).
> **Note:** Copy your Organization ID (Resource ID) since you will use the id in the later steps.
---
## 3. Exporting User Data from Keycloak
### 3.1. Set up Keycloak Locally (Optional)
To run a local development Keycloak instance, use the official Docker image:
```bash
docker run -d -p 8081:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:22.0.1 start-dev
```
### 3.2. Export Users from Keycloak
Run the following command inside the Keycloak container to export your realm and users:
```bash
docker exec <container_name> \
/opt/keycloak/bin/kc.sh export \
--dir /tmp/export \
--realm <your_realm_name> \
--users realm_file
```
Then copy the exported file to your host machine:
```bash
docker cp <container_name>:/tmp/export/<your_realm_name>-realm.json .
```
This creates a file such as:
```
<your_realm_name>-realm.json
```
---
## 4. Running the ZITADEL Migration Tool
### 4.1. Install the Migration Tool
Follow the installation instructions to set up the ZITADEL migration tool from [ZITADEL Tools](https://github.com/zitadel/zitadel-tools?tab=readme-ov-file#installation).
### 4.2. Generate Import JSON
Use the migration tool to convert the Keycloak realm export into a ZITADEL-compatible JSON file:
```bash
zitadel-tools migrate keycloak \
--org=<ORG_ID> \
--realm=<your_realm_name>-realm.json \
--output=./importBody.json \
--timeout=5m0s \
--multiline
```
The tool will generate `importBody.json`, which is ready for importing into ZITADEL.
---
## 5. Importing Users into ZITADEL
### 5.1. Obtain Access Token (or PAT) for API Access
To call the ZITADEL Management API, you need to authenticate using a **Service User** with the `IAM_OWNER` Manager permissions.
There are two recommended authentication methods:
* **Client Credentials Flow**
[Learn how to authenticate with client credentials.](https://zitadel.com/docs/guides/integrate/service-users/client-credentials)
* **Personal Access Token (PAT)**
[Learn how to create and use a PAT.](https://zitadel.com/docs/guides/integrate/service-users/personal-access-token)
**Reference:** [Service Users & API Authentication](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users#authentication-methods)
---
### 5.2. Import Data with the ZITADEL API
Use your **access token** or **PAT** to authenticate, then call the [Management API – Human User Import](/reference/api/management/zitadel.management.v1.ManagementService.ImportHumanUser) endpoint.
**Import Endpoint:**
* `POST /admin/v1/import`
* `Authorization: Bearer <token>`
* **Body:** Generated in step 4.2
#### Example cURL request
```bash
curl --request POST \
--url https://${CUSTOM_DOMAIN}/admin/v1/import \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data @importBody.json
```
Successful Response:
```bash
{
"success": {
"orgs": [
{
"orgId": "318900732864567390",
"humanUserIds": [
"da72ac13-6994-4498-8b27-3ff9555661b2",
"4e987a01-34db-4393-b61c-1ce753baf69c",
"1041d710-8a89-48f8-85b5-1ab9656190f3",
"7b23b799-4f0f-4964-bc6d-95c534787d2c",
"6f2f1b2f-b292-4431-932b-620124e065ec",
"2c65045a-9de8-4d28-b686-b27bf3a70fc3",
"aca2dd3e-689c-4ab6-b446-0990127b1e0d",
"18a23e01-f0fe-443f-9f1c-2a8135cd22c2",
"c49af4bf-0dbb-4994-b453-b8dd0d5006ea"
]
}
]
},
"errors": [
{
"type": "org",
"id": "318900732864567390",
"message": "ID=ORG-lapo2m Message=Errors.Org.AlreadyExisting"
}
]
}
```
ℹ️ Note: The above response indicates that the organization already existed, and users were successfully added. This is not an error, and you can consider the import successful as long as the HTTP status code is **200**.
---
## 6. Testing the Migration
### 6.1. Test User Login
Use the **ZITADEL login page** or your integrated app to test logging in with one of the imported users.
Confirm that the migrated password works as expected.
---
### 6.2. Troubleshooting
**Common issues:**
* Invalid Keycloak export format
* Malformed JSON
* Missing `orgId` or access token
* Timeout exceeded during import
The import API returns a detailed response with any errors encountered during the process.
#### Where to check logs and get help
You can verify that users were imported successfully by querying the **events API** and looking for the `user.human.added` event type.
Use the following request:
```bash
curl --location 'https://${CUSTOM_DOMAIN}/admin/v1/events/_search' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"asc": true,
"limit": 1000,
"event_types": [
"user.human.added"
]
}'
```
This will return a list of user creation events including details such as email, username, and hashed password to help you confirm the imported data.
Successful Response
```bash
{
"events": [
{
"type": {
"type": "user.human.added",
"localized": {
"key": "EventTypes.user.human.added",
"localizedMessage": "Person added"
}
},
"payload": {
"displayName": "test user",
"email": "[email protected]",
"userName": "testuser"
},
"aggregate": {
"id": "da72ac13-6994-4498-8b27-3ff9555661b2",
"resourceOwner": "318900732864567390"
},
"creationDate": "2025-07-22T15:16:06.364302Z"
}
]
}
```
ℹ️ Note: If you see entries with "type": "user.human.added" and correct payload data, the import was successful.
---
## 7. Q&A and Further Resources
### Real-World Scenarios & Common Questions
**Q:** What is the maximum number of users that can be imported in a single batch?
**A:** There is no hard limit on the number of users. However, there is a **timeout**.
For **ZITADEL Cloud deployments**, the timeout is **5 minutes**, which typically allows for importing around **5,000 users per batch**.
---