Dựng Keycloak bằng Docker, export/import realm

4 câu hỏi~25 phútTừ khóa: docker compose, Postgres, start-dev, start, KC_BOOTSTRAP_ADMIN, kc.sh export, --import-realm

Tiếp đi: chạy Keycloak bằng Docker cho cả team dùng.

Bài trước đã thiết kế realm shop. Giờ dựng server thật, sao cho cả team chạy được cùng một cấu hình.

1. Bài toán

Team shop-app có 5 dev. Mỗi người cần một Keycloak local có sẵn realm shop, client, user test để chạy order-service.

2. Vấn đề

3. Giải pháp

start-devstart
Mục đíchDev localProduction
HTTPCho phép HTTPBắt buộc HTTPS hoặc cấu hình proxy
HostnameKhông bắt buộcBắt buộc KC_HOSTNAME (hoặc tắt strict có chủ đích)
Theme/cacheKhông cache, sửa theme thấy ngayCache bật
BuildTự build mỗi lầnNên kc.sh build trước (image tối ưu, --optimized)

4. Code từng bước

shop-app/
├── infra/
│   ├── docker-compose.yml
│   ├── .env.example
│   └── keycloak/import/shop-realm.json
└── ...

Bước 1: .env.example (commit file mẫu, không commit .env):

# Chỉ dùng cho dev local. Production lấy từ secret manager.
KC_ADMIN_USER=admin
KC_ADMIN_PASSWORD=change-me
KC_DB_PASSWORD=change-me

Bước 2: docker-compose.yml

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
    volumes:
      - kc-db:/var/lib/postgresql/data

  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    command: ["start-dev", "--import-realm"]
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_ADMIN_USER}
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
      KC_HEALTH_ENABLED: "true"
    ports:
      - "8080:8080"
    volumes:
      - ./keycloak/import:/opt/keycloak/data/import:ro
    depends_on:
      - postgres

volumes:
  kc-db:

Bước 3: Tạo realm, client, user lần đầu (admin console http://localhost:8080/admin hoặc CLI):

docker compose exec keycloak bash -c '
  /opt/keycloak/bin/kcadm.sh config credentials --server http://localhost:8080 \
    --realm master --user "$KC_BOOTSTRAP_ADMIN_USERNAME" --password "$KC_BOOTSTRAP_ADMIN_PASSWORD"
  /opt/keycloak/bin/kcadm.sh create users -r shop -s username=alice -s enabled=true \
    -s email=alice@example.test -s emailVerified=true
  /opt/keycloak/bin/kcadm.sh set-password -r shop --username alice --new-password "$TEST_USER_PASSWORD"
  /opt/keycloak/bin/kcadm.sh add-roles -r shop --uusername alice --rolename customer'

Admin bootstrap chỉ là tạm: đăng nhập xong hãy tạo admin thật (hoặc nối IdP công ty) rồi xóa user bootstrap.

Bước 4: Export realm cho team

# Export khi server đã dừng phục vụ (chạy container tạm cùng DB)
docker compose run --rm keycloak export \
  --dir /opt/keycloak/data/export --realm shop --users realm_file
# hoặc: Admin console → Realm settings → Action → Partial export (không có user, secret bị che)

Copy shop-realm.json vào infra/keycloak/import/ và commit. Dev mới chỉ cần:

cp .env.example .env
docker compose up -d
curl -s http://localhost:8080/realms/shop | jq .realm   # "shop"

5. Cách hoạt động

docker compose up
   |
   v
Keycloak khởi động ── KC_DB=postgres ──> bảng realm, user, client...
   |
   +-- --import-realm: đọc /opt/keycloak/data/import/*.json
   |       realm "shop" CHƯA có → import
   |       realm "shop" ĐÃ có  → bỏ qua (không ghi đè)
   |
   +-- master chưa có admin → tạo admin bootstrap từ KC_BOOTSTRAP_ADMIN_*
   v
:8080/admin   :8080/realms/shop   :9000/health (management port)

6. So sánh cách đưa cấu hình realm

CáchƯuNhược
Click admin consoleNhanh khi họcKhông tái lập, không review
--import-realm JSONĐơn giản, hợp dev localChỉ import lần đầu, không cập nhật realm đã có
keycloak-config-cli / Terraform providerIdempotent, cập nhật được, hợp CI/CDThêm công cụ

7. Khi nào nên dùng / không nên dùng

File realm export có cả client secret của order-service và password user test. Commit lên git như vậy có sao không?

Bạn lo đúng chỗ. Với dev local thì chấp nhận được nếu secret và user là giá trị test, không dùng ở môi trường khác. Nhưng nên làm sạch:

{
  "clientId": "order-service",
  "publicClient": false,
  "serviceAccountsEnabled": true,
  "secret": "${ORDER_SERVICE_SECRET}"
}

Cách quản lý secret chung xem bài 11.7.

Mình thêm client mới vào shop-realm.json, restart container mà không thấy client đâu. Sao vậy?

Đúng hành vi: --import-realm bỏ qua realm đã tồn tại trong DB. Volume Postgres vẫn giữ realm cũ.

# Cách 1: dev local, xóa dữ liệu rồi import lại
docker compose down -v && docker compose up -d

# Cách 2: thêm thủ công bằng kcadm (không mất user đang test)
# Cách 3: dùng keycloak-config-cli để apply thay đổi idempotent

Trên môi trường dùng chung (staging, prod), thay đổi realm nên đi qua công cụ idempotent trong pipeline, không xóa DB.

Phỏng vấn hỏi cách dựng Keycloak và chia sẻ cấu hình realm thì trả lời sao?

Q1. start-dev và start khác nhau thế nào?

🇻🇳 Tiếng Việt:

start-dev cho dev: cho HTTP, không cần hostname, tắt cache theme. start cho production: bắt buộc HTTPS hoặc proxy, cấu hình hostname, nên build image tối ưu trước rồi chạy với database ngoài như Postgres.

🇬🇧 English:

start-dev is for development: plain HTTP, no hostname required, theme caching off. start is for production: it requires HTTPS or proxy settings and a hostname, and we should build an optimized image first and use an external database like Postgres.

Q2. Team chia sẻ cấu hình realm thế nào?

🇻🇳 Tiếng Việt:

Export realm ra JSON, thay secret bằng placeholder biến môi trường, commit vào repo, container chạy với --import-realm. Import chỉ áp dụng khi realm chưa có, nên môi trường dùng chung em dùng keycloak-config-cli hoặc Terraform để apply thay đổi idempotent.

🇬🇧 English:

We export the realm to JSON, replace secrets with environment placeholders, commit it and start the container with --import-realm. Import only applies when the realm does not exist yet, so for shared environments I use keycloak-config-cli or Terraform to apply changes idempotently.

Cách nhớ ngắn gọn


Tự kiểm tra

Không khai báo KC_DB thì Keycloak dùng DB gì?

H2 file trong container (chỉ hợp dev). Xóa container là mất dữ liệu.

Thư mục import mặc định là gì?

/opt/keycloak/data/import.

Sửa JSON rồi restart không thấy thay đổi, vì sao?

Realm đã tồn tại trong DB, import bỏ qua. Xóa volume (local) hoặc dùng công cụ idempotent.

Biến môi trường admin bootstrap ở Keycloak 26 là gì?

KC_BOOTSTRAP_ADMIN_USERNAME và KC_BOOTSTRAP_ADMIN_PASSWORD.