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 |
|
1 | feature-keycloak-docker-compose-stack |
|
|
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 매핑:
8080keycloak /8081spring boot /80nginx .env파일로KEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORD/POSTGRES_PASSWORD분리depends_on+ healthcheck- 로컬 실행 명령어 문서화
제외 범위
- Keycloak realm/client 설정 (→ raw/branch-notes/feature-keycloak-realm-client-export)
- Spring Boot 코드 (→ raw/branch-notes/feature-keycloak-spring-rs-role-mapping)
- SPA 코드 (→ raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce)
- HTTPS / Caddy (학습 환경)
- prod 배포 / EC2 IaC
근거 (필수, 최소 1개+)
- raw/official-docs/keycloak-server-containers-docker — Keycloak Docker 공식 (KC_* 환경 변수)
- raw/official-docs/keycloak-getting-started-docker — Docker quickstart
- raw/official-docs/keycloak-health-checks — health endpoint 경로(
/health,/health/ready,/health/live,/health/started), management port9000,KC_HEALTH_ENABLED(기본값false) 활성화 요건의 공식 근거 (D3) - raw/official-docs/keycloak-configuring-database — Keycloak 공식 "Configuring the database" (
/server/db) —KC_DB=postgresvendor 값,KC_DB_URL/KC_DB_USERNAME/KC_DB_PASSWORDJDBC 연결 환경변수 정확한 이름·형식. D2 (PostgreSQL 사용) 의 verbatim 근거 —KC-DB-C1~KC-DB-C5 - raw/official-docs/keycloak-import-export-realms — Keycloak 공식 Import/Export 가이드 (
--import-realm옵션, 컨테이너 import 경로/opt/keycloak/data/import, 기존 realm 존재 시 skip 동작 — D4 근거) - raw/official-docs/docker-compose-depends-on-healthcheck — Docker Compose 공식 (
depends_onlong syntaxcondition: service_healthy+healthcheck필드 문법, D3 근거)
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, port8081:8081) — 등급:planned - 서비스
nginx정의 (nginx:alpine, volume mount: SPAdist/→/usr/share/nginx/html, port80: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 노출 방지) — 등급:planneddepends_onhealthcheck: 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-databaseKC-DB-C1C5(official-vendor-doc); D3(healthcheck depends_on) → raw/official-docs/docker-compose-depends-on-healthcheckCOMPOSE-DEP-*(official-standard, Compose 문법) + raw/official-docs/keycloak-health-checksKC-HEALTH-*(official-vendor-doc, health endpoint/port/enable 요건); D4(realm auto-import) → raw/official-docs/keycloak-import-export-realmsKC-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_ENABLED 를 start-dev 가 runtime 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-C1 은 127.0.0.1:8080:8080 (loopback bind) 명시 — 본 결정은 8080:8080 (모든 인터페이스) 사용. 학습 환경 외 EC2 외부 노출 시 admin 인증 우회 위험 |
| D7 | DELEGATED — raw/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, SPAdist/, 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, app8081:8081, nginx80: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/tcpin-container probe 로 둘지 vsdepends_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/tcp → HEAD /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-C4verbatim 커맨드 인라인 — depth-audit finding #1): compose 의test:는 반드시 bash 형태로 명시한다. 기본CMD-SHELL은/bin/sh(dash)라/dev/tcpredirect 를 지원하지 않아 실패하므로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 컨테이너 envKC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD(26.x,KEYCLOAK_ADMIN자체는 deprecated — 진행중 메모) — 매핑을 composeenvironment:에서KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}형태로 연결. trade-off: 레거시 키 이름을 그대로 쓰면 혼란 → compose 에 주석 필요. (권고:.env키도KC_BOOTSTRAP_ADMIN_*로 통일 고려.)
.env:KEYCLOAK_ADMIN,KEYCLOAK_ADMIN_PASSWORD,POSTGRES_PASSWORD(+KC_DB_PASSWORD는POSTGRES_PASSWORD공유 또는 별도).gitignore에.env추가 (secret 커밋 방지)- compose
environment:에서${VAR}치환 +KEYCLOAK_ADMIN → KC_BOOTSTRAP_ADMIN_USERNAME매핑
5. Realm auto-import
Trace: D4 (
KC-IMPORT-C1--import-realmstartup 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 디렉토리를 스캔(.jsononly, 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 삭제) 후 재기동, 또는 offlineimport --override - realm JSON 산출물(
realm-export.json)은 siblingfeature-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
appservice에 배치하고, 그 profile이 요구하는 host wiring을 연결하는 책임만 가진다.
- Compose
appservice에 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_healthy로app이 영구 대기(교착). 기대 동작: keycloak env 에KC_HEALTH_ENABLED=true명시. - curl 부재 (
KC-HEALTH-C4): healthcheck.test 에curl/wget사용 시 "not found" 로 항상 실패 → bash/dev/tcp/localhost/9000raw 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후 재기동 또는 offlineimport --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만 수행한다.
- health-disabled 함정 (신규 발견,
- 다른 계약 의존:
- raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6 — bridge retrieval profile. 본 branch는 owner-selected profile의 Compose 배치·host wiring만 수행한다.
- raw/branch-notes/feature-keycloak-realm-client-export 에 의존 — auto-import 대상
realm-export.json(realm+client+테스트 사용자)의 owner. realm 구조 변경 시 mount 파일 변경. - raw/branch-notes/feature-keycloak-spring-rs-role-mapping 에 의존 —
appservice 가 실행하는 Spring Boot 이미지/Dockerfile 의 owner (build context 계약). - raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce 에 의존 —
nginxservice 가 서빙하는 SPAdist/산출물의 owner. - raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6 — iss mismatch 시연·검증과 profile 값의 owner. 본 compose 의
KC_HOSTNAME/network 결정은 그 시연의 배치 전제다.
검증해야 할 주장
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_ENABLED 를 start-dev 가 runtime 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 -d 후 docker 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) 예상.
묶음
- raw/official-docs/docker-compose-depends-on-healthcheck
- raw/official-docs/keycloak-configuring-database
- raw/official-docs/keycloak-getting-started-docker
- raw/official-docs/keycloak-health-checks
- raw/official-docs/keycloak-import-export-realms
- raw/official-docs/keycloak-server-containers-docker
본 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정상 기동 후planned→actually-implemented/locally-verified승급.
- PR 링크: (별도 keycloak-patterns repo)
- 리뷰 메모:
- 머지 결과 / 배포 환경: 로컬 docker-compose / 단일 EC2 시뮬레이션
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: (구현 후 채움)locally-verified항목: (구현 후 채움)prod-verified항목: (없음)
- 추출하지 않을 항목: 현재 전부
planned.