Files
llm-wiki/raw/branch-notes/feature-keycloak-docker-compose-stack.md
T

33 KiB

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
title source_type status id kind project work_item inherits refines overrides depends_on contract_packet branch parent_branch related_projects tags created target_merge status_label contract_packet_sha256
branch / feature-keycloak-docker-compose-stack (docker-compose 환경 구성 — keycloak + postgres + spring + nginx) branch-note raw BR-KEYCLOAK-PATTERNS-OVERVIEW-001 project-work-item keycloak-patterns-overview WI-KEYCLOAK-PATTERNS-OVERVIEW-001
DEC-KEYCLOAK-PATTERNS-OVERVIEW-DEPLOYMENT-001@1
1 feature-keycloak-docker-compose-stack
keycloak-patterns
branch
keycloak-patterns
p3a
implementation
docker-compose
single-host
infra
2026-05-25 in-progress c8ba20c0738c66a511f3acc218959a05d2b4ffef6616b2dfc528cd1f49915348

branch: feature-keycloak-docker-compose-stack (docker-compose 환경 구성)

Layer: raw/branch-notes/raw/project-notes/keycloak-patterns-overview Work Item의 직접 자식 branch. P3A는 실 구현 대상. 본 sub-sub는 학습 + 작업 plan 기록 — 실 구현은 별도 git repo /home/donghyeon/workspace/keycloak-patterns/.

부모 (필수)

raw/project-notes/keycloak-patterns-overview

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: Keycloak·PostgreSQL·nginx·Spring 4 containers가 healthy이고 admin console에 접속된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-KEYCLOAK-PATTERNS-OVERVIEW-DEPLOYMENT-001@1 AP1~AP4 E2E 구현 topology는 single-EC2 docker-compose다 Keycloak·PostgreSQL·nginx·Spring의 local single-host topology에 적용한다 raw/project-notes/keycloak-patterns-overview

브랜치 지역 결정

Decision ID Decision Relation Supporting Claims Status
D1 학습 환경에서는 Keycloak start-dev를 사용한다 local raw/project-notes/keycloak-patterns-overview proposed
D2 Keycloak database로 PostgreSQL을 사용한다 local raw/project-notes/keycloak-patterns-overview proposed
D3 healthcheck 기반 startup dependency를 사용한다 local raw/project-notes/keycloak-patterns-overview proposed
D4 realm JSON auto-import를 사용한다 local raw/project-notes/keycloak-patterns-overview proposed
D5 학습 topology hostname을 localhost로 고정한다 local raw/project-notes/keycloak-patterns-overview proposed
D6 local port mapping과 admin secret 분리를 고정한다 local raw/project-notes/keycloak-patterns-overview proposed
D7 bridge retrieval profile은 owner 결정을 소비한다 local raw/project-notes/keycloak-patterns-overview proposed

선언한 예외

Override ID Overrides Reason Approval Status

없음.

목표

Keycloak (PostgreSQL realm 저장) + Spring Boot + nginx (vanilla JS SPA static) 단일 host docker-compose 환경을 구성한다. 학습 친화성의 핵심 지표는 환경 reset 1줄 (docker compose down -v && docker compose up -d).

면접 질문: "OIDC 학습 환경을 어떻게 구성했나요?" → "단일 EC2(또는 로컬) docker-compose 한 파일로 keycloak / postgres / spring boot / nginx 네 서비스를 띄웠습니다. volume 두 개(keycloak data, postgres data)를 정의해 realm export JSON이 자동 import되도록 했고, healthcheck로 backend가 keycloak ready 이후에만 기동하도록 depends_on: condition: service_healthy를 걸었습니다."

  • 이슈:
  • PR: (별도 keycloak-patterns repo)

범위

포함 범위

  • docker-compose.yml 작성 (서비스 4개)
  • volume 정의 (keycloak data, postgres data)
  • 단일 network
  • port 매핑: 8080 keycloak / 8081 spring boot / 80 nginx
  • .env 파일로 KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD / POSTGRES_PASSWORD 분리
  • depends_on + healthcheck
  • 로컬 실행 명령어 문서화

제외 범위

근거 (필수, 최소 1개+)

TODO

각 항목 옆에 증거 등급. 현재 모두 planned — 실 구현 후 별도 작업에서 actually-implemented/locally-verified로 승급.

  • docker-compose.yml 작성 — 등급: planned
  • 서비스 keycloak 정의 (quay.io/keycloak/keycloak:26.x, start-dev, env: KC_DB=postgres, KC_DB_URL, KC_HOSTNAME=localhost, KC_HTTP_ENABLED=true, KC_BOOTSTRAP_ADMIN_USERNAME, KC_BOOTSTRAP_ADMIN_PASSWORD) — 등급: planned
  • 서비스 postgres 정의 (postgres:16, env: POSTGRES_DB=keycloak, POSTGRES_USER, POSTGRES_PASSWORD) — 등급: planned
  • 서비스 app 정의 (Spring Boot, 빌드는 별도 Dockerfile, port 8081:8081) — 등급: planned
  • 서비스 nginx 정의 (nginx:alpine, volume mount: SPA dist//usr/share/nginx/html, port 80:80) — 등급: planned
  • volume 정의 (keycloak_data, postgres_data) — 등급: planned
  • 단일 network 정의 (keycloak-net) — 등급: planned
  • port 매핑: 8080:8080 (keycloak), 8081:8081 (app), 80:80 (nginx) — 등급: planned
  • .env 파일 작성 + .gitignore에 추가 (KEYCLOAK_ADMIN secret 노출 방지) — 등급: planned
  • depends_on healthcheck: postgres ready → keycloak 기동 / keycloak ready → app 기동 (condition: service_healthy) — 등급: planned
  • keycloak healthcheck (/health/ready 엔드포인트, start-dev에서 활성화) — 등급: planned
  • postgres healthcheck (pg_isready) — 등급: planned
  • realm export JSON auto-import volume (./realm-export.json:/opt/keycloak/data/import/realm-export.json) + --import-realm 옵션 — 등급: planned
  • 로컬 실행 명령어 문서화 (docker compose up -d / docker compose logs -f keycloak / docker compose down -v) — 등급: planned
  • README에 환경 reset 1줄 명령어 명시 — 등급: planned

진행 중 메모

  • Keycloak 26.x 기준 KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD가 admin 부트스트랩에 사용됨 (구버전 KEYCLOAK_ADMIN은 deprecated).
  • start-dev는 학습 전용. start --optimized는 prod 모드 (build 단계 분리 필요).
  • KC_HOSTNAME=localhost 강제는 P3A 본질 — raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch 함정 시연용.
  • nginx는 단순 static 파일 서빙. SPA fallback (try_files $uri /index.html) 추가 검토 (history mode 사용 시).
  • depends_on: condition: service_healthy는 Compose v3 spec에서 사용 가능.
  • (2026-07-16 자동조사) KC_HEALTH_ENABLED 기본값은 false (raw/official-docs/keycloak-health-checks.md#KC-HEALTH-C3) — 명시적으로 켜지 않으면 /health/ready 가 노출되지 않아 healthcheck 가 영구 실패한다. health endpoint 는 main HTTP 포트가 아니라 management port 9000 (KC-HEALTH-C1). 공식 컨테이너 이미지엔 curl 이 없어(KC-HEALTH-C4) healthcheck.test 는 bash /dev/tcp 패턴을 써야 한다.

결정 사항 (decisions)

  • 2026-05-25: Keycloak 26.x + start-dev 사용. 이유: 학습 환경, optimized 빌드 단계 회피.
  • 2026-05-25: PostgreSQL 사용 (Keycloak 기본 H2 대신). 이유: realm 데이터 영속 + prod-like 환경 학습.
  • 2026-05-25: healthcheck로 의존성 강제. 이유: app의 첫 token 검증 네트워크 호출 전에 Keycloak readiness를 보장한다.
  • 2026-05-25: realm JSON auto-import 채택. 이유: 환경 reset 후에도 realm 설정 즉시 복원 — 학습 반복 비용 최소화.

결정-근거 매핑

docker-compose 환경 구성 결정. D2/D3/D4 의 UNSUPPORTED_DECISION 라벨은 2026-07-16 /branch-spec 자동조사(§5)로 모두 해소됨: D2(PostgreSQL) → raw/official-docs/keycloak-configuring-database KC-DB-C1C5 (official-vendor-doc); D3(healthcheck depends_on) → raw/official-docs/docker-compose-depends-on-healthcheck COMPOSE-DEP-* (official-standard, Compose 문법) + raw/official-docs/keycloak-health-checks KC-HEALTH-* (official-vendor-doc, health endpoint/port/enable 요건); D4(realm auto-import) → raw/official-docs/keycloak-import-export-realms KC-IMPORT-C1C4 (official-vendor-doc). 선택 조건 열(R2)은 "이 조건이면 이 결정, 다른 조건이면 어떤 대안" — 근거 claim 으로 대안까지 명시.

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 Keycloak 26.x + start-dev 사용 (학습 환경) 학습/로컬/데모 환경일 때 이 결정. prod 진입 시 → 대안 start (after build, optimized image) 로 전환 (KC-CONTAINER-C3 이 dev mode 의 prod 사용을 strictly avoid 하라 경고, KC-CONTAINER-C4 가 optimized build 근거). raw/official-docs/keycloak-server-containers-docker.md#KC-CONTAINER-C2, raw/official-docs/keycloak-server-containers-docker.md#KC-CONTAINER-C3, raw/official-docs/keycloak-getting-started-docker.md#KC-GSD-C1, raw/official-docs/keycloak-getting-started-docker.md#KC-GSD-C2 official-vendor-doc KC-CONTAINER-C3 은 production 에서 start-dev strictly avoided 라고 경고 — 본 결정은 학습 한정. 실수로 prod 노출 시 보안 사고
D2 PostgreSQL 사용 (Keycloak 기본 dev-file 대신) realm 데이터 영속 + prod-like 환경 학습이 목표일 때 이 결정. 순수 throwaway 데모(영속 불필요)면 → 대안 기본 dev-file (설정 0, KC-DB-C1). 조직이 다른 RDBMS 로 표준화돼 있으면 → 대안 mariadb/mysql/mssql/oracle/tidb (KC-DB-C2 동등 지원). raw/official-docs/keycloak-configuring-database.md#KC-DB-C1, raw/official-docs/keycloak-configuring-database.md#KC-DB-C2, raw/official-docs/keycloak-configuring-database.md#KC-DB-C3, raw/official-docs/keycloak-configuring-database.md#KC-DB-C4, raw/official-docs/keycloak-configuring-database.md#KC-DB-C5 official-vendor-doc KC-DB-C2 는 postgres 가 지원됨을 증명할 뿐 권장됨은 증명 안 함 (mariadb/mysql/mssql/oracle/tidb 도 동등 지원) — PostgreSQL 선택 자체는 branch 의 "prod-like 환경 학습" 이유에 의한 자체 결정. 페이지가 rolling docs 라 Keycloak 26.x 특정 버전에 pin 된 확인은 아님
D3 healthcheck 로 의존성 강제 (depends_on: condition: service_healthy) app 의 startup discovery 또는 첫 JWT 검증 네트워크 호출 전에 Keycloak readiness 를 보장해야 할 때 이 결정. 서비스 간 readiness 의존이 없으면 → 대안 short syntax (depends_on: [x], 순서만·healthy 대기 안 함 COMPOSE-DEP-C4) 또는 depends_on 생략. raw/official-docs/docker-compose-depends-on-healthcheck.md#COMPOSE-DEP-C1, raw/official-docs/docker-compose-depends-on-healthcheck.md#COMPOSE-DEP-C2, raw/official-docs/docker-compose-depends-on-healthcheck.md#COMPOSE-DEP-C5, raw/official-docs/docker-compose-depends-on-healthcheck.md#COMPOSE-DEP-C6, raw/official-docs/keycloak-health-checks.md#KC-HEALTH-C1, raw/official-docs/keycloak-health-checks.md#KC-HEALTH-C2, raw/official-docs/keycloak-health-checks.md#KC-HEALTH-C3, raw/official-docs/keycloak-health-checks.md#KC-HEALTH-C4 official-standard + official-vendor-doc Compose 는 "시작 순서" 만 보증(COMPOSE-DEP-C5) — health probe 자체의 정확성은 Keycloak 측 근거로 확보(KC-HEALTH-*). 남은 위험: KC_HEALTH_ENABLEDstart-devruntime env 로 받는지 vs build-time 옵션인지는 KC-HEALTH-C3 로 확정 안 됨 (아래 Claims To Verify). 미설정 시 기본 false → healthcheck 영구 실패(§엣지·실패·의존)
D4 realm JSON auto-import 채택 (--import-realm + volume mount) 환경 reset 반복 + realm 설정 즉시 복원이 목표일 때 이 결정 (down -v 후 재기동 시 재import). 1회성 수동 설정이면 → 대안 Admin UI 수동 생성. 기존 realm 을 강제로 덮어써야 하면 → 대안 offline import 명령 (--override 기본 true, KC-IMPORT-C4; auto-import 는 skip KC-IMPORT-C3). raw/official-docs/keycloak-import-export-realms.md#KC-IMPORT-C1, raw/official-docs/keycloak-import-export-realms.md#KC-IMPORT-C2, raw/official-docs/keycloak-import-export-realms.md#KC-IMPORT-C3, raw/official-docs/keycloak-import-export-realms.md#KC-IMPORT-C4 official-vendor-doc KC-IMPORT-C2 는 페이지 버전 셀렉터(Nightly/26.7.0)만 노출 — 특정 26.x patch 에 pin 된 확인은 아님. KC-IMPORT-C1 은 컨테이너 이미지의 entrypoint/CMD 가 --import-realm 을 실제로 어떻게 전달받는지까지는 증명 안 함 (컨테이너 entrypoint 세부는 별도 확인 필요, 아래 Claims To Verify)
D5 KC_HOSTNAME=localhost 강제 (P3A 본질, iss claim 함정 시연용) iss claim mismatch 함정을 의도적으로 시연·학습할 때 이 결정 (parent D3 와 결합). prod 진입 시 → 대안 hostname-strict=true + 실제 도메인 (KC-HOST-C5). raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2, raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C3, raw/official-docs/keycloak-server-containers-docker.md#KC-CONTAINER-C1 official-vendor-doc KC-HOST-C5 는 production 에서 hostname-strict true 권고 — 학습 환경 한정으로 충분. 실제 mismatch 시연 동작은 sibling feature-keycloak-iss-claim-hostname-mismatch 에서 검증
D6 port 매핑: keycloak 8080:8080, app 8081:8081, nginx 80:80 + .env 로 admin secret 분리 단일 host 학습 환경에서 세 서비스에 브라우저가 직접 접근해야 할 때 이 결정 (전 인터페이스 bind). 외부 노출/prod 면 → 대안 loopback bind 127.0.0.1:8080:8080 (KC-GSD-C1) + reverse proxy 뒤 배치. raw/official-docs/keycloak-getting-started-docker.md#KC-GSD-C1 (quickstart 의 8080 노출 패턴) official-vendor-doc KC-GSD-C1127.0.0.1:8080:8080 (loopback bind) 명시 — 본 결정은 8080:8080 (모든 인터페이스) 사용. 학습 환경 외 EC2 외부 노출 시 admin 인증 우회 위험
D7 DELEGATEDraw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6 — bridge retrieval profile owner가 선택한 profile을 Compose app service에 배치·wiring할 때만 본 task가 적용된다. profile 값이나 대안 선택은 owner에서 변경한다. owner 참조 delegated Compose wiring의 runtime 도달성은 owner의 401→200 E2E 전까지 needs-confirmation

구현 가이드

본 §는 위 Decision 들이 어디에 어떻게 구현되는가의 사전 명세 (다음 구현자가 되묻지 않고 docker-compose.yml 을 작성할 수준). in-scope 항목만. 각 sub-section 은 Decision ID + Supporting Claim ID 를 Trace 로 명시하고, 근거가 detail 을 규정하지 않는 임의 결정은 UNSUPPORTED_IMPL_DECISION 라벨 + trade-off 한 줄로 남긴다. 범위 경계: app/nginx 서비스의 service 정의(이미지·포트·마운트·depends_on)는 본 branch 의 compose 파일 in-scope 이지만, 그 빌드 산출물(Spring jar/Dockerfile, SPA dist/, realm JSON)은 sibling branch 소유 → §엣지·실패·의존 의 "다른 계약 의존" 참조.

1. docker-compose 서비스 정의 (4 services)

Trace: D1 (keycloak start-dev, KC-CONTAINER-C2) · D2 (postgres 연결 env, KC-DB-C2/C3/C4/C5) · D5 (KC_HOSTNAME, KC-HOST-C2).

  • UNSUPPORTED_IMPL_DECISION:
    • postgres 이미지 태그 postgres:16 — Keycloak 문서는 vendor(postgres)만 명시하고 특정 major 를 권고 안 함(KC-DB-C2). trade-off: 16 = 현시점 안정 major, 단 Keycloak 26.x DB 지원 매트릭스 재확인 필요.
    • KC_DB_URL 의 host = compose service 명 postgres. KC-DB-C4 기본형은 jdbc:postgresql://localhost/keycloak, KC-DB-C3 예시는 db-url-host=keycloak-postgres — 값이 문서마다 달라 컨테이너 내부 DNS(=service 명)에 맞춰 임의 결정. trade-off: postgres service 이름을 바꾸면 URL 도 바뀜.
    • nginx:alpine 태그 — 경량 목적 임의 선택. trade-off: 정적 서빙이라 musl libc 이슈 가능성 낮음.
서비스 이미지 command / 핵심 env port Trace
keycloak quay.io/keycloak/keycloak:26.x start-dev --import-realm; KC_DB=postgres, KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak, KC_DB_USERNAME, KC_DB_PASSWORD, KC_HOSTNAME=localhost, KC_HTTP_ENABLED=true, KC_HEALTH_ENABLED=true, KC_BOOTSTRAP_ADMIN_USERNAME, KC_BOOTSTRAP_ADMIN_PASSWORD 8080:8080 D1·D2·D4·D5·D6
postgres postgres:16 POSTGRES_DB=keycloak, POSTGRES_USER, POSTGRES_PASSWORD (내부만) D2
app 별도 Dockerfile 빌드 (sibling) raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6가 선택한 bridge retrieval profile의 Compose 배치·host wiring만 수행 8081:8081 D6·D7 (delegated owner D6)
nginx nginx:alpine SPA dist/(sibling) → /usr/share/nginx/html mount 80:80 D6

2. 네트워크 · 포트 · 볼륨 토폴로지

Trace: D6 (port 매핑, KC-GSD-C1) · 범위 §In scope (단일 network, volume 2개) · KC-HEALTH-C1 (health/management port 9000 은 내부 전용).

  • UNSUPPORTED_IMPL_DECISION:
    • network 이름 keycloak-net, volume 이름 keycloak_data/postgres_data — Docker 문서 미규정, 가독성 위주 임의 명명. trade-off: 충돌 시 rename 만 하면 됨.
    • management/health port 9000 을 host 로 매핑하지 않음 — healthcheck 는 컨테이너 내부 /dev/tcp/localhost/9000 로 수행(KC-HEALTH-C4)하므로 외부 노출 불필요. trade-off: 외부에서 /health/ready 를 직접 디버깅하려면 9000:9000 을 임시 추가.
  • network: keycloak-net (단일 bridge, 4개 서비스 동일 network)
  • volumes: keycloak_data, postgres_data (postgres data 영속 → realm 유지; down -v 시 삭제되어 reset)
  • ports (host:container): keycloak 8080:8080, app 8081:8081, nginx 80:80

3. 의존성 순서 + healthcheck

Trace: D3 (COMPOSE-DEP-C1/C2/C5/C6 — depends_on long syntax + healthcheck 필드; KC-HEALTH-C1~C4 — endpoint/port/enable/커맨드).

  • UNSUPPORTED_IMPL_DECISION:
    • healthcheck interval/timeout/retries/start_period 수치 — COMPOSE-DEP-C6 은 필드 존재만 보증하고 값은 미권고. trade-off: keycloak 초기 기동이 느려 start_period 를 크게(예 40~60s) 잡음 — 임의값, 실측 후 조정.
    • keycloak healthcheck 를 /dev/tcp in-container probe 로 둘지 vs depends_on 만 믿을지 — KC-HEALTH-C4 의 공식 curl-free Containerfile 패턴 채택. trade-off: bash /dev/tcp 는 keycloak 이미지에 내장된 bash 필요(존재함).
서비스 healthcheck.test depends_on (condition) Trace
postgres pg_isready -U $POSTGRES_USER COMPOSE-DEP-C6
keycloak bash /dev/tcpHEAD /health/ready on :9000 (curl 없음 KC-HEALTH-C4); 전제 KC_HEALTH_ENABLED=true KC-HEALTH-C3 postgres: {condition: service_healthy} COMPOSE-DEP-C2, KC-HEALTH-C1/C2/C3/C4
app (Spring actuator /actuator/health — sibling 소유) keycloak: {condition: service_healthy} — owner profile의 첫 token 검증 네트워크 호출 전 readiness 보장 COMPOSE-DEP-C2/C5, D7
nginx (선택) app: {condition: service_started} (static only, 강 의존 아님) COMPOSE-DEP-C4

keycloak healthcheck 정확형 (KC-HEALTH-C4 verbatim 커맨드 인라인 — depth-audit finding #1): compose 의 test: 는 반드시 bash 형태로 명시한다. 기본 CMD-SHELL/bin/sh(dash)라 /dev/tcp redirect 를 지원하지 않아 실패하므로 bash -c 를 강제:

healthcheck:
  test: ["CMD", "bash", "-c", "{ printf 'HEAD /health/ready HTTP/1.0\r\n\r\n' >&0; grep 'HTTP/1.0 200'; } 0<>/dev/tcp/localhost/9000"]
  interval: 10s        # UNSUPPORTED_IMPL_DECISION — COMPOSE-DEP-C6 필드만 보증, 값 임의
  timeout: 5s
  retries: 12
  start_period: 60s    # keycloak 초기 기동 느림 → 크게

4. Secret 분리 (.env)

Trace: D6 (.env 로 admin secret 분리) · 범위 §In scope.

  • UNSUPPORTED_IMPL_DECISION:
    • .env 키 이름 KEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORD (범위 §In scope 표기) vs 컨테이너 env KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD (26.x, KEYCLOAK_ADMIN 자체는 deprecated — 진행중 메모) — 매핑을 compose environment: 에서 KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN} 형태로 연결. trade-off: 레거시 키 이름을 그대로 쓰면 혼란 → compose 에 주석 필요. (권고: .env 키도 KC_BOOTSTRAP_ADMIN_* 로 통일 고려.)
  • .env: KEYCLOAK_ADMIN, KEYCLOAK_ADMIN_PASSWORD, POSTGRES_PASSWORD (+ KC_DB_PASSWORDPOSTGRES_PASSWORD 공유 또는 별도)
  • .gitignore.env 추가 (secret 커밋 방지)
  • compose environment: 에서 ${VAR} 치환 + KEYCLOAK_ADMIN → KC_BOOTSTRAP_ADMIN_USERNAME 매핑

5. Realm auto-import

Trace: D4 (KC-IMPORT-C1 --import-realm startup import · KC-IMPORT-C2 컨테이너 경로 /opt/keycloak/data/import, .json 만 · KC-IMPORT-C3 기존 realm skip).

  • UNSUPPORTED_IMPL_DECISION:
    • mount 를 파일(./realm-export.json:/opt/keycloak/data/import/realm-export.json) vs 디렉토리(./import:/opt/keycloak/data/import)로 할지 — KC-IMPORT-C2 는 서버가 import 디렉토리를 스캔(.json only, sub-dir 무시)한다고 명시하므로 디렉토리 mount 가 더 안전. 범위 §In scope 는 파일 단위 mount 표기. trade-off: 파일 단위도 동작하나 realm 여러 개로 확장 시 디렉토리 mount 권장 → 정합 권고: 디렉토리 mount 로 조정 검토.
  • keycloak command: start-dev --import-realm
  • volume mount: ./realm-export.json:/opt/keycloak/data/import/realm-export.json:ro (또는 위 정합 권고대로 디렉토리 mount)
  • 재import 동작(KC-IMPORT-C3): 기존 realm 존재 시 skip → realm JSON 수정 반영하려면 down -v(postgres volume 삭제) 후 재기동, 또는 offline import --override
  • realm JSON 산출물(realm-export.json)은 sibling feature-keycloak-realm-client-export 소유 (§엣지·실패·의존)

6. Owner retrieval profile의 Compose wiring (D7 delegated)

Trace: raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6 — bridge retrieval profile.

profile 값·선택·fallback은 owner만 변경한다. 본 branch는 owner가 선택한 profile을 Compose app service에 배치하고, 그 profile이 요구하는 host wiring을 연결하는 책임만 가진다.

  • Compose app service에 owner-selected profile과 필수 host mapping을 배치한다.
  • acceptance: docker compose config가 해당 wiring을 해석하고 app container가 owner의 retrieval endpoint에 도달한다. profile 값은 이 문서에 복사하지 않는다.
  • runtime 도달성 status: needs-confirmation.

7. 로컬 실행 · 환경 reset 명령

Trace: 목표 §WHY (환경 reset 1줄 = 학습 친화성 핵심 지표). 표준 compose 명령이라 UNSUPPORTED 없음.

  • 기동: docker compose up -d
  • 로그: docker compose logs -f keycloak
  • 환경 reset 1줄: docker compose down -v && docker compose up -d (volume 삭제 → realm 재import)
  • README 에 위 reset 1줄 명시

엣지·실패·의존

정상 경로 외에 구현 중 부딪힐 실패/엣지, 그리고 다른 branch 계약 의존을 미리 열거 (R4).

  • 실패·엣지 경로:
    • health-disabled 함정 (신규 발견, KC-HEALTH-C3): KC_HEALTH_ENABLED 기본 false → 설정 누락 시 /health/ready 미노출 → keycloak healthcheck 영구 unhealthy → depends_on: service_healthyapp 이 영구 대기(교착). 기대 동작: keycloak env 에 KC_HEALTH_ENABLED=true 명시.
    • curl 부재 (KC-HEALTH-C4): healthcheck.test 에 curl/wget 사용 시 "not found" 로 항상 실패 → bash /dev/tcp/localhost/9000 raw HTTP 패턴 필요.
    • postgres not-ready: keycloak 이 postgres healthy 전에 기동하면 DB 연결 실패로 crash-loop → depends_on: postgres {condition: service_healthy} + pg_isready.
    • realm import 재실행 idempotency (KC-IMPORT-C3): 기존 realm skip → realm JSON 수정해도 down -v 없이 재기동하면 반영 안 됨. 기대: down -v 후 재기동 또는 offline import --override.
    • volume mount permission: EC2 ubuntu(UID 1000) vs 컨테이너 UID → data volume ownership 충돌로 permission denied 가능 (→ Claims To Verify).
    • nginx SPA history-mode fallback: deep-link(/some/route) 직접 GET 시 try_files $uri /index.html 없으면 404.
    • iss mismatch (의도적 함정): browser 와 컨테이너의 address 관점 차이로 JWT iss 검증이 실패할 수 있다. raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6 — bridge retrieval profile이 해결 owner이며, 본 branch는 그 profile의 Compose wiring만 수행한다.
  • 다른 계약 의존:

검증해야 할 주장

Docker Compose 환경 구성은 실제 docker compose up -d 후에만 검증 가능. 근거 확보된 claim 은 status 를 documented-only(문법·명세는 공식 확인, 로컬 실행만 남음)로 표기.

Claim Why uncertain How to verify Status
KC_BOOTSTRAP_ADMIN_USERNAME / KC_BOOTSTRAP_ADMIN_PASSWORD 가 Keycloak 26.x 의 admin 부트스트랩 환경변수 (구버전 KEYCLOAK_ADMIN 대체) KC-CONTAINER-C5 는 정확한 환경변수 이름이 verbatim 부재로 needs-confirmation quay.io/keycloak/keycloak:26.x 컨테이너 시작 후 admin 로그인 시도 + Keycloak release notes 확인 needs-confirmation
KC_HEALTH_ENABLEDstart-devruntime env 로 받는지 (vs build-time 옵션), 켜면 /health/ready 가 management port 9000 에서 노출되는지 endpoint 경로·port·기본값(false)·활성화 플래그는 KC-HEALTH-C1~C4 로 확보됐으나, start-dev 가 이 옵션을 build-time 으로 요구하는지 runtime env 로 받는지의 구분은 KC-HEALTH-C3 로 확정 안 됨 (해당 raw 의 Usage Boundaries 에도 명시) docker compose up -ddocker compose exec keycloak bash -c '... /dev/tcp/localhost/9000'/health/ready 200 확인 + healthcheck 상태 healthy 확인 needs-confirmation
depends_on: condition: service_healthy 가 Compose spec 에서 사용 가능 2026-07-16 raw/official-docs/docker-compose-depends-on-healthcheck COMPOSE-DEP-C1/C2/C5 로 문법·의미 확인 완료 — 남은 불확실성은 로컬 docker compose version 이 이 문법을 지원하는 실제 버전인지만 docker compose config 로 파싱 에러 없이 로드되는지 확인 (문법은 이미 공식 확인됨) documented-only (문법 근거 확보, 로컬 실행 검증만 남음)
--import-realm + /opt/keycloak/data/import/ 경로가 Keycloak 26.x 컨테이너에서 동작 2026-07-16 raw/official-docs/keycloak-import-export-realms KC-IMPORT-C1/C2 로 옵션·컨테이너 경로·skip 동작 확인 — 남은 불확실성은 공식 이미지 entrypoint/CMD 가 --import-realm 을 실제로 전달하는지 + 로컬 실행 docker compose up -d 후 Admin UI 에서 realm 자동 import 확인 + docker compose logs keycloak 에 import 로그 확인 documented-only (옵션·경로 근거 확보, entrypoint 전달·로컬 실행 검증만 남음)
volume mount permission (EC2 ubuntu user UID 1000 vs keycloak container UID 1000) 충돌 없이 동작 OS / container UID 매핑은 공식 인용 범위 밖, 운영 환경 의존 docker compose up 후 keycloak data volume 의 ownership 확인 + permission denied 에러 부재 확인 planned
nginx static 서빙에서 SPA history mode 사용 시 try_files $uri /index.html fallback 동작 본 sub-sub-branch 의 in scope 결정 - nginx 설정 자체는 raw source 인용 없음 SPA 의 /some/spa/route 직접 GET 시 index.html 반환 확인 planned
raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6가 선택한 bridge retrieval profile의 Compose host wiring이 app container에서 동작 profile 값·선택은 owner가 소유하고, Compose runtime reachability만 환경 의존 docker compose config로 owner-required host wiring 확인 → app container에서 owner retrieval endpoint 도달 → owner의 401→200 E2E 확인 needs-confirmation

마주친 문제

  • (구현 시작 후 추가) depends_on healthy 미사용 시 app 기동 직후 들어온 첫 인증 요청이 Keycloak readiness 전에 JWKS를 fetch하면 검증 실패 예상.
  • (구현 시작 후 추가) volume mount permission 이슈 (특히 EC2 ubuntu user vs container UID) 예상.

묶음

본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

오류 기록 (이 sub-sub-branch 작업 중 발생)

  • (없음 — 현재 documented-only 단계)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • (없음 — Phase 3 실 구현 단계에 누적)

관련 일일 노트

완료 후 정리

실 구현(/home/donghyeon/workspace/keycloak-patterns/)에서 docker compose up -d 정상 기동 후 plannedactually-implemented/locally-verified 승급.

  • PR 링크: (별도 keycloak-patterns repo)
  • 리뷰 메모:
  • 머지 결과 / 배포 환경: 로컬 docker-compose / 단일 EC2 시뮬레이션
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: (구현 후 채움)
    • locally-verified 항목: (구현 후 채움)
    • prod-verified 항목: (없음)
  • 추출하지 않을 항목: 현재 전부 planned.