[{"data":1,"prerenderedAt":1510},["ShallowReactive",2],{"post-document:\u002Fdevops\u002Fdocker\u002F2026\u002F08\u002F17\u002Fdocker-13-compose-healthcheck-and-practice\u002F":3},{"id":4,"title":5,"body":6,"categories":1492,"date":1495,"description":1496,"extension":1497,"image":1498,"key_concepts":1498,"last_modified_at":1498,"legacyPath":1499,"meta":1500,"navigation":327,"part":1502,"path":1503,"published":327,"robots":1498,"seo":1504,"series":1505,"stem":1506,"strengths":1498,"summary":1498,"tags":1507,"tradeoffs":1498,"__hash__":1509},"posts\u002Fposts\u002FCI-CD-Docker\u002FDocker\u002F2026-08-17-docker-13-compose-healthcheck-and-practice.md","Docker 기초 13편: Compose Healthcheck와 실전 구성",{"type":7,"value":8,"toc":1473},"minimark",[9,14,19,97,109,117,120,168,175,181,372,386,418,421,489,509,512,516,551,620,623,635,638,642,646,654,658,1144,1149,1153,1279,1318,1320,1324,1328,1459,1463,1469],[10,11,13],"h2",{"id":12},"_1-의존-관계-healthcheck-재시작-정책","1. 의존 관계, Healthcheck, 재시작 정책",[15,16,18],"h3",{"id":17},"먼저-알아둘-단어","먼저 알아둘 단어",[20,21,22,38],"table",{},[23,24,25],"thead",{},[26,27,28,32,35],"tr",{},[29,30,31],"th",{},"단어",[29,33,34],{},"뜻",[29,36,37],{},"쉬운 비유",[39,40,41,53,64,75,86],"tbody",{},[26,42,43,47,50],{},[44,45,46],"td",{},"Startup Order",[44,48,49],{},"어떤 서비스를 먼저 시작할지 정한 순서",[44,51,52],{},"가게 문을 여는 순서",[26,54,55,58,61],{},[44,56,57],{},"Readiness",[44,59,60],{},"실제 요청을 처리할 준비가 된 상태",[44,62,63],{},"직원이 출근한 것이 아니라 업무 준비까지 끝난 상태",[26,65,66,69,72],{},[44,67,68],{},"Healthcheck",[44,70,71],{},"컨테이너 내부 서비스의 상태를 주기적으로 검사",[44,73,74],{},"정기 건강검진",[26,76,77,80,83],{},[44,78,79],{},"Restart Policy",[44,81,82],{},"프로세스 종료 후 컨테이너 재시작 규칙",[44,84,85],{},"고장 시 자동 전원 복구 설정",[26,87,88,91,94],{},[44,89,90],{},"PID 1",[44,92,93],{},"컨테이너의 생명주기를 대표하는 메인 프로세스",[44,95,96],{},"가게의 영업 책임자",[98,99,100],"blockquote",{},[101,102,103,104,108],"p",{},"쉬운 비유: DB 컨테이너가 시작됐다는 것은 식당 직원이 출근했다는 뜻이고, ",[105,106,107],"code",{},"healthy","는 재료 준비까지 마쳐 주문을 받을 수 있다는 뜻.",[15,110,112,113,116],{"id":111},"_11-depends_on의-한계","1.1 ",[105,114,115],{},"depends_on","의 한계",[101,118,119],{},"짧은 문법은 컨테이너의 시작 순서만 조정.",[121,122,127],"pre",{"className":123,"code":124,"language":125,"meta":126,"style":126},"language-yaml shiki shiki-themes github-light github-dark","services:\n  backend:\n    depends_on:\n      - db\n","yaml","",[105,128,129,142,150,158],{"__ignoreMap":126},[130,131,134,138],"span",{"class":132,"line":133},"line",1,[130,135,137],{"class":136},"s9eBZ","services",[130,139,141],{"class":140},"sVt8B",":\n",[130,143,145,148],{"class":132,"line":144},2,[130,146,147],{"class":136},"  backend",[130,149,141],{"class":140},[130,151,153,156],{"class":132,"line":152},3,[130,154,155],{"class":136},"    depends_on",[130,157,141],{"class":140},[130,159,161,164],{"class":132,"line":160},4,[130,162,163],{"class":140},"      - ",[130,165,167],{"class":166},"sZZnC","db\n",[101,169,170,171,174],{},"DB 프로세스가 실제 연결을 받을 준비가 끝날 때까지 기다린다는 뜻은 아니다. 준비 상태까지 기다리려면 DB에 Healthcheck를 만들고 ",[105,172,173],{},"service_healthy"," 조건을 사용.",[15,176,178,179],{"id":177},"_12-healthcheck와-service_healthy","1.2 Healthcheck와 ",[105,180,173],{},[121,182,184],{"className":123,"code":183,"language":125,"meta":126,"style":126},"services:\n  db:\n    image: postgres:15\n    environment:\n      POSTGRES_USER: app\n      POSTGRES_DB: app\n      POSTGRES_PASSWORD: local-only\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB\"]\n      interval: 5s\n      timeout: 5s\n      retries: 5\n      start_period: 10s\n\n  backend:\n    image: my-backend:1.0\n    depends_on:\n      db:\n        condition: service_healthy\n",[105,185,186,192,199,210,217,228,238,249,257,278,289,299,311,322,329,336,346,353,361],{"__ignoreMap":126},[130,187,188,190],{"class":132,"line":133},[130,189,137],{"class":136},[130,191,141],{"class":140},[130,193,194,197],{"class":132,"line":144},[130,195,196],{"class":136},"  db",[130,198,141],{"class":140},[130,200,201,204,207],{"class":132,"line":152},[130,202,203],{"class":136},"    image",[130,205,206],{"class":140},": ",[130,208,209],{"class":166},"postgres:15\n",[130,211,212,215],{"class":132,"line":160},[130,213,214],{"class":136},"    environment",[130,216,141],{"class":140},[130,218,220,223,225],{"class":132,"line":219},5,[130,221,222],{"class":136},"      POSTGRES_USER",[130,224,206],{"class":140},[130,226,227],{"class":166},"app\n",[130,229,231,234,236],{"class":132,"line":230},6,[130,232,233],{"class":136},"      POSTGRES_DB",[130,235,206],{"class":140},[130,237,227],{"class":166},[130,239,241,244,246],{"class":132,"line":240},7,[130,242,243],{"class":136},"      POSTGRES_PASSWORD",[130,245,206],{"class":140},[130,247,248],{"class":166},"local-only\n",[130,250,252,255],{"class":132,"line":251},8,[130,253,254],{"class":136},"    healthcheck",[130,256,141],{"class":140},[130,258,260,263,266,269,272,275],{"class":132,"line":259},9,[130,261,262],{"class":136},"      test",[130,264,265],{"class":140},": [",[130,267,268],{"class":166},"\"CMD-SHELL\"",[130,270,271],{"class":140},", ",[130,273,274],{"class":166},"\"pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB\"",[130,276,277],{"class":140},"]\n",[130,279,281,284,286],{"class":132,"line":280},10,[130,282,283],{"class":136},"      interval",[130,285,206],{"class":140},[130,287,288],{"class":166},"5s\n",[130,290,292,295,297],{"class":132,"line":291},11,[130,293,294],{"class":136},"      timeout",[130,296,206],{"class":140},[130,298,288],{"class":166},[130,300,302,305,307],{"class":132,"line":301},12,[130,303,304],{"class":136},"      retries",[130,306,206],{"class":140},[130,308,310],{"class":309},"sj4cs","5\n",[130,312,314,317,319],{"class":132,"line":313},13,[130,315,316],{"class":136},"      start_period",[130,318,206],{"class":140},[130,320,321],{"class":166},"10s\n",[130,323,325],{"class":132,"line":324},14,[130,326,328],{"emptyLinePlaceholder":327},true,"\n",[130,330,332,334],{"class":132,"line":331},15,[130,333,147],{"class":136},[130,335,141],{"class":140},[130,337,339,341,343],{"class":132,"line":338},16,[130,340,203],{"class":136},[130,342,206],{"class":140},[130,344,345],{"class":166},"my-backend:1.0\n",[130,347,349,351],{"class":132,"line":348},17,[130,350,155],{"class":136},[130,352,141],{"class":140},[130,354,356,359],{"class":132,"line":355},18,[130,357,358],{"class":136},"      db",[130,360,141],{"class":140},[130,362,364,367,369],{"class":132,"line":363},19,[130,365,366],{"class":136},"        condition",[130,368,206],{"class":140},[130,370,371],{"class":166},"service_healthy\n",[101,373,374,377,378,381,382,385],{},[105,375,376],{},"$$","는 Compose가 ",[105,379,380],{},"$POSTGRES_USER","를 호스트에서 미리 치환하지 않고, 컨테이너 안의 Shell에 ",[105,383,384],{},"$"," 문자를 전달하도록 합니다.",[121,387,391],{"className":388,"code":389,"language":390,"meta":126,"style":126},"language-mermaid shiki shiki-themes github-light github-dark","flowchart LR\n    S[\"DB container started\"] --> H[\"Healthcheck 반복\"]\n    H -->|실패| H\n    H -->|성공| OK[\"DB healthy\"]\n    OK --> B[\"backend 시작\"]\n","mermaid",[105,392,393,398,403,408,413],{"__ignoreMap":126},[130,394,395],{"class":132,"line":133},[130,396,397],{},"flowchart LR\n",[130,399,400],{"class":132,"line":144},[130,401,402],{},"    S[\"DB container started\"] --> H[\"Healthcheck 반복\"]\n",[130,404,405],{"class":132,"line":152},[130,406,407],{},"    H -->|실패| H\n",[130,409,410],{"class":132,"line":160},[130,411,412],{},"    H -->|성공| OK[\"DB healthy\"]\n",[130,414,415],{"class":132,"line":219},[130,416,417],{},"    OK --> B[\"backend 시작\"]\n",[101,419,420],{},"Healthcheck 항목의 의미는 다음과 같습니다.",[20,422,423,433],{},[23,424,425],{},[26,426,427,430],{},[29,428,429],{},"항목",[29,431,432],{},"의미",[39,434,435,445,455,465,479],{},[26,436,437,442],{},[44,438,439],{},[105,440,441],{},"test",[44,443,444],{},"실행할 검사 명령",[26,446,447,452],{},[44,448,449],{},[105,450,451],{},"interval",[44,453,454],{},"검사 주기",[26,456,457,462],{},[44,458,459],{},[105,460,461],{},"timeout",[44,463,464],{},"한 번의 검사 제한 시간",[26,466,467,472],{},[44,468,469],{},[105,470,471],{},"retries",[44,473,474,475,478],{},"연속 실패 후 ",[105,476,477],{},"unhealthy","가 되는 기준",[26,480,481,486],{},[44,482,483],{},[105,484,485],{},"start_period",[44,487,488],{},"초기 기동 실패를 본격 집계하기 전 유예 시간",[101,490,491,492,271,495,271,497,499,500,502,503,505,506,508],{},"Healthcheck는 상태를 ",[105,493,494],{},"starting",[105,496,107],{},[105,498,477],{},"로 표시할 뿐, 일반 Docker Compose에서 ",[105,501,477],{}," 컨테이너를 자동으로 교체하거나 재시작하지는 않습니다. ",[105,504,115],{},"도 최초 시작 시 기다리는 용도이며, 실행 중 DB가 나중에 ",[105,507,477],{},"가 됐다고 Backend를 자동으로 재시작하지 않습니다.",[101,510,511],{},"애플리케이션도 DB 연결 실패에 대비해 재시도와 지수 백오프를 구현하는 것이 좋습니다.",[15,513,515],{"id":514},"_13-재시작-정책","1.3 재시작 정책",[121,517,519],{"className":123,"code":518,"language":125,"meta":126,"style":126},"services:\n  backend:\n    image: my-backend:1.0\n    restart: unless-stopped\n",[105,520,521,527,533,541],{"__ignoreMap":126},[130,522,523,525],{"class":132,"line":133},[130,524,137],{"class":136},[130,526,141],{"class":140},[130,528,529,531],{"class":132,"line":144},[130,530,147],{"class":136},[130,532,141],{"class":140},[130,534,535,537,539],{"class":132,"line":152},[130,536,203],{"class":136},[130,538,206],{"class":140},[130,540,345],{"class":166},[130,542,543,546,548],{"class":132,"line":160},[130,544,545],{"class":136},"    restart",[130,547,206],{"class":140},[130,549,550],{"class":166},"unless-stopped\n",[20,552,553,566],{},[23,554,555],{},[26,556,557,560,563],{},[29,558,559],{},"값",[29,561,562],{},"동작",[29,564,565],{},"적합한 예",[39,567,568,581,594,607],{},[26,569,570,575,578],{},[44,571,572],{},[105,573,574],{},"no",[44,576,577],{},"자동으로 재시작하지 않음. 기본값",[44,579,580],{},"일회성 작업",[26,582,583,588,591],{},[44,584,585],{},[105,586,587],{},"on-failure",[44,589,590],{},"프로세스가 0이 아닌 종료 코드로 끝나면 재시작",[44,592,593],{},"실패 시 재시도할 배치 작업",[26,595,596,601,604],{},[44,597,598],{},[105,599,600],{},"always",[44,602,603],{},"프로세스 종료 또는 Docker 데몬 재시작 후 다시 실행",[44,605,606],{},"상시 서비스",[26,608,609,614,617],{},[44,610,611],{},[105,612,613],{},"unless-stopped",[44,615,616],{},"사용자가 명시적으로 중지하지 않은 한 다시 실행",[44,618,619],{},"수동 중지 상태를 유지할 상시 서비스",[101,621,622],{},"재시작 판단의 기준은 컨테이너의 메인 프로세스(PID 1)가 종료됐는지 여부입니다. 단순히 Healthcheck가 실패한 것만으로는 프로세스가 종료되지 않으므로 재시작 정책이 작동하지 않는다.",[101,624,625,626,628,629,631,632,634],{},"사용자가 직접 중지한 컨테이너에는 재시작 정책이 즉시 적용되지 않습니다. ",[105,627,600],{},"와 ",[105,630,613],{},"는 Docker 데몬이 다시 시작될 때 수동 중지 상태를 처리하는 방식에서 차이가 있으므로, 의도적으로 꺼 둔 상태를 유지하려면 ",[105,633,613],{},"가 이해하기 쉽다.",[636,637],"hr",{},[10,639,641],{"id":640},"_2-실전-전체-예제","2. 실전 전체 예제",[15,643,645],{"id":644},"디렉터리-구조","디렉터리 구조",[121,647,652],{"className":648,"code":650,"language":651,"meta":126},[649],"language-text","project\u002F\n├── compose.yaml\n├── .env\n├── nginx.conf\n├── frontend\u002F\n│   └── dist\u002F\n└── backend\u002F\n    ├── Dockerfile\n    └── ...\n","text",[105,653,650],{"__ignoreMap":126},[15,655,657],{"id":656},"compose-파일","Compose 파일",[121,659,661],{"className":123,"code":660,"language":125,"meta":126,"style":126},"services:\n  db:\n    image: postgres:15\n    environment:\n      POSTGRES_USER: app\n      POSTGRES_DB: app\n      POSTGRES_PASSWORD: \"${DB_PASSWORD:?DB_PASSWORD를 설정하세요}\"\n    volumes:\n      - db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB\"]\n      interval: 5s\n      timeout: 5s\n      retries: 5\n      start_period: 10s\n    restart: unless-stopped\n    networks:\n      - private\n\n  backend:\n    build:\n      context: .\u002Fbackend\n      dockerfile: Dockerfile\n    environment:\n      APP_ENV: development\n      DB_HOST: db\n      DB_PORT: \"5432\"\n      DB_NAME: app\n      DB_USER: app\n      DB_PASSWORD: \"${DB_PASSWORD}\"\n    expose:\n      - \"8080\"\n    depends_on:\n      db:\n        condition: service_healthy\n    restart: unless-stopped\n    networks:\n      - public\n      - private\n\n  frontend:\n    image: nginx:alpine\n    ports:\n      - \"${FRONTEND_PORT:-8080}:80\"\n    volumes:\n      - .\u002Fnginx.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n      - .\u002Ffrontend\u002Fdist:\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml:ro\n    depends_on:\n      - backend\n    restart: unless-stopped\n    networks:\n      - public\n\nvolumes:\n  db-data:\n\nnetworks:\n  public:\n  private:\n    internal: true\n",[105,662,663,669,675,683,689,697,705,714,721,728,734,748,756,764,772,780,788,795,802,806,813,821,832,843,850,861,871,882,892,902,913,921,929,936,943,952,961,968,976,983,988,996,1006,1014,1022,1029,1037,1045,1052,1060,1069,1076,1083,1088,1096,1104,1109,1117,1125,1133],{"__ignoreMap":126},[130,664,665,667],{"class":132,"line":133},[130,666,137],{"class":136},[130,668,141],{"class":140},[130,670,671,673],{"class":132,"line":144},[130,672,196],{"class":136},[130,674,141],{"class":140},[130,676,677,679,681],{"class":132,"line":152},[130,678,203],{"class":136},[130,680,206],{"class":140},[130,682,209],{"class":166},[130,684,685,687],{"class":132,"line":160},[130,686,214],{"class":136},[130,688,141],{"class":140},[130,690,691,693,695],{"class":132,"line":219},[130,692,222],{"class":136},[130,694,206],{"class":140},[130,696,227],{"class":166},[130,698,699,701,703],{"class":132,"line":230},[130,700,233],{"class":136},[130,702,206],{"class":140},[130,704,227],{"class":166},[130,706,707,709,711],{"class":132,"line":240},[130,708,243],{"class":136},[130,710,206],{"class":140},[130,712,713],{"class":166},"\"${DB_PASSWORD:?DB_PASSWORD를 설정하세요}\"\n",[130,715,716,719],{"class":132,"line":251},[130,717,718],{"class":136},"    volumes",[130,720,141],{"class":140},[130,722,723,725],{"class":132,"line":259},[130,724,163],{"class":140},[130,726,727],{"class":166},"db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[130,729,730,732],{"class":132,"line":280},[130,731,254],{"class":136},[130,733,141],{"class":140},[130,735,736,738,740,742,744,746],{"class":132,"line":291},[130,737,262],{"class":136},[130,739,265],{"class":140},[130,741,268],{"class":166},[130,743,271],{"class":140},[130,745,274],{"class":166},[130,747,277],{"class":140},[130,749,750,752,754],{"class":132,"line":301},[130,751,283],{"class":136},[130,753,206],{"class":140},[130,755,288],{"class":166},[130,757,758,760,762],{"class":132,"line":313},[130,759,294],{"class":136},[130,761,206],{"class":140},[130,763,288],{"class":166},[130,765,766,768,770],{"class":132,"line":324},[130,767,304],{"class":136},[130,769,206],{"class":140},[130,771,310],{"class":309},[130,773,774,776,778],{"class":132,"line":331},[130,775,316],{"class":136},[130,777,206],{"class":140},[130,779,321],{"class":166},[130,781,782,784,786],{"class":132,"line":338},[130,783,545],{"class":136},[130,785,206],{"class":140},[130,787,550],{"class":166},[130,789,790,793],{"class":132,"line":348},[130,791,792],{"class":136},"    networks",[130,794,141],{"class":140},[130,796,797,799],{"class":132,"line":355},[130,798,163],{"class":140},[130,800,801],{"class":166},"private\n",[130,803,804],{"class":132,"line":363},[130,805,328],{"emptyLinePlaceholder":327},[130,807,809,811],{"class":132,"line":808},20,[130,810,147],{"class":136},[130,812,141],{"class":140},[130,814,816,819],{"class":132,"line":815},21,[130,817,818],{"class":136},"    build",[130,820,141],{"class":140},[130,822,824,827,829],{"class":132,"line":823},22,[130,825,826],{"class":136},"      context",[130,828,206],{"class":140},[130,830,831],{"class":166},".\u002Fbackend\n",[130,833,835,838,840],{"class":132,"line":834},23,[130,836,837],{"class":136},"      dockerfile",[130,839,206],{"class":140},[130,841,842],{"class":166},"Dockerfile\n",[130,844,846,848],{"class":132,"line":845},24,[130,847,214],{"class":136},[130,849,141],{"class":140},[130,851,853,856,858],{"class":132,"line":852},25,[130,854,855],{"class":136},"      APP_ENV",[130,857,206],{"class":140},[130,859,860],{"class":166},"development\n",[130,862,864,867,869],{"class":132,"line":863},26,[130,865,866],{"class":136},"      DB_HOST",[130,868,206],{"class":140},[130,870,167],{"class":166},[130,872,874,877,879],{"class":132,"line":873},27,[130,875,876],{"class":136},"      DB_PORT",[130,878,206],{"class":140},[130,880,881],{"class":166},"\"5432\"\n",[130,883,885,888,890],{"class":132,"line":884},28,[130,886,887],{"class":136},"      DB_NAME",[130,889,206],{"class":140},[130,891,227],{"class":166},[130,893,895,898,900],{"class":132,"line":894},29,[130,896,897],{"class":136},"      DB_USER",[130,899,206],{"class":140},[130,901,227],{"class":166},[130,903,905,908,910],{"class":132,"line":904},30,[130,906,907],{"class":136},"      DB_PASSWORD",[130,909,206],{"class":140},[130,911,912],{"class":166},"\"${DB_PASSWORD}\"\n",[130,914,916,919],{"class":132,"line":915},31,[130,917,918],{"class":136},"    expose",[130,920,141],{"class":140},[130,922,924,926],{"class":132,"line":923},32,[130,925,163],{"class":140},[130,927,928],{"class":166},"\"8080\"\n",[130,930,932,934],{"class":132,"line":931},33,[130,933,155],{"class":136},[130,935,141],{"class":140},[130,937,939,941],{"class":132,"line":938},34,[130,940,358],{"class":136},[130,942,141],{"class":140},[130,944,946,948,950],{"class":132,"line":945},35,[130,947,366],{"class":136},[130,949,206],{"class":140},[130,951,371],{"class":166},[130,953,955,957,959],{"class":132,"line":954},36,[130,956,545],{"class":136},[130,958,206],{"class":140},[130,960,550],{"class":166},[130,962,964,966],{"class":132,"line":963},37,[130,965,792],{"class":136},[130,967,141],{"class":140},[130,969,971,973],{"class":132,"line":970},38,[130,972,163],{"class":140},[130,974,975],{"class":166},"public\n",[130,977,979,981],{"class":132,"line":978},39,[130,980,163],{"class":140},[130,982,801],{"class":166},[130,984,986],{"class":132,"line":985},40,[130,987,328],{"emptyLinePlaceholder":327},[130,989,991,994],{"class":132,"line":990},41,[130,992,993],{"class":136},"  frontend",[130,995,141],{"class":140},[130,997,999,1001,1003],{"class":132,"line":998},42,[130,1000,203],{"class":136},[130,1002,206],{"class":140},[130,1004,1005],{"class":166},"nginx:alpine\n",[130,1007,1009,1012],{"class":132,"line":1008},43,[130,1010,1011],{"class":136},"    ports",[130,1013,141],{"class":140},[130,1015,1017,1019],{"class":132,"line":1016},44,[130,1018,163],{"class":140},[130,1020,1021],{"class":166},"\"${FRONTEND_PORT:-8080}:80\"\n",[130,1023,1025,1027],{"class":132,"line":1024},45,[130,1026,718],{"class":136},[130,1028,141],{"class":140},[130,1030,1032,1034],{"class":132,"line":1031},46,[130,1033,163],{"class":140},[130,1035,1036],{"class":166},".\u002Fnginx.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n",[130,1038,1040,1042],{"class":132,"line":1039},47,[130,1041,163],{"class":140},[130,1043,1044],{"class":166},".\u002Ffrontend\u002Fdist:\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml:ro\n",[130,1046,1048,1050],{"class":132,"line":1047},48,[130,1049,155],{"class":136},[130,1051,141],{"class":140},[130,1053,1055,1057],{"class":132,"line":1054},49,[130,1056,163],{"class":140},[130,1058,1059],{"class":166},"backend\n",[130,1061,1063,1065,1067],{"class":132,"line":1062},50,[130,1064,545],{"class":136},[130,1066,206],{"class":140},[130,1068,550],{"class":166},[130,1070,1072,1074],{"class":132,"line":1071},51,[130,1073,792],{"class":136},[130,1075,141],{"class":140},[130,1077,1079,1081],{"class":132,"line":1078},52,[130,1080,163],{"class":140},[130,1082,975],{"class":166},[130,1084,1086],{"class":132,"line":1085},53,[130,1087,328],{"emptyLinePlaceholder":327},[130,1089,1091,1094],{"class":132,"line":1090},54,[130,1092,1093],{"class":136},"volumes",[130,1095,141],{"class":140},[130,1097,1099,1102],{"class":132,"line":1098},55,[130,1100,1101],{"class":136},"  db-data",[130,1103,141],{"class":140},[130,1105,1107],{"class":132,"line":1106},56,[130,1108,328],{"emptyLinePlaceholder":327},[130,1110,1112,1115],{"class":132,"line":1111},57,[130,1113,1114],{"class":136},"networks",[130,1116,141],{"class":140},[130,1118,1120,1123],{"class":132,"line":1119},58,[130,1121,1122],{"class":136},"  public",[130,1124,141],{"class":140},[130,1126,1128,1131],{"class":132,"line":1127},59,[130,1129,1130],{"class":136},"  private",[130,1132,141],{"class":140},[130,1134,1136,1139,1141],{"class":132,"line":1135},60,[130,1137,1138],{"class":136},"    internal",[130,1140,206],{"class":140},[130,1142,1143],{"class":309},"true\n",[98,1145,1146],{},[101,1147,1148],{},"이 예제는 구조 학습용. 운영 환경에서는 DB 비밀번호를 환경 변수 대신 Secret Manager 또는 파일 기반 Secret으로 전달하고, TLS,백업,모니터링,로그 수집 정책도 별도로 구성해야 한다.",[15,1150,1152],{"id":1151},"실행과-점검","실행과 점검",[121,1154,1158],{"className":1155,"code":1156,"language":1157,"meta":126,"style":126},"language-bash shiki shiki-themes github-light github-dark","# 최종 설정과 변수 치환 결과 검증\ndocker compose config\n\n# 빌드 후 실행\ndocker compose up -d --build\n\n# 서비스 상태와 Health 확인\ndocker compose ps\n\n# 전체 로그 추적\ndocker compose logs -f --tail=100\n\n# 접속\ncurl http:\u002F\u002Flocalhost:8080\n\n# 종료하되 DB Volume은 보존\ndocker compose down\n","bash",[105,1159,1160,1166,1178,1182,1187,1202,1206,1211,1220,1224,1229,1244,1248,1253,1261,1265,1270],{"__ignoreMap":126},[130,1161,1162],{"class":132,"line":133},[130,1163,1165],{"class":1164},"sJ8bj","# 최종 설정과 변수 치환 결과 검증\n",[130,1167,1168,1172,1175],{"class":132,"line":144},[130,1169,1171],{"class":1170},"sScJk","docker",[130,1173,1174],{"class":166}," compose",[130,1176,1177],{"class":166}," config\n",[130,1179,1180],{"class":132,"line":152},[130,1181,328],{"emptyLinePlaceholder":327},[130,1183,1184],{"class":132,"line":160},[130,1185,1186],{"class":1164},"# 빌드 후 실행\n",[130,1188,1189,1191,1193,1196,1199],{"class":132,"line":219},[130,1190,1171],{"class":1170},[130,1192,1174],{"class":166},[130,1194,1195],{"class":166}," up",[130,1197,1198],{"class":309}," -d",[130,1200,1201],{"class":309}," --build\n",[130,1203,1204],{"class":132,"line":230},[130,1205,328],{"emptyLinePlaceholder":327},[130,1207,1208],{"class":132,"line":240},[130,1209,1210],{"class":1164},"# 서비스 상태와 Health 확인\n",[130,1212,1213,1215,1217],{"class":132,"line":251},[130,1214,1171],{"class":1170},[130,1216,1174],{"class":166},[130,1218,1219],{"class":166}," ps\n",[130,1221,1222],{"class":132,"line":259},[130,1223,328],{"emptyLinePlaceholder":327},[130,1225,1226],{"class":132,"line":280},[130,1227,1228],{"class":1164},"# 전체 로그 추적\n",[130,1230,1231,1233,1235,1238,1241],{"class":132,"line":291},[130,1232,1171],{"class":1170},[130,1234,1174],{"class":166},[130,1236,1237],{"class":166}," logs",[130,1239,1240],{"class":309}," -f",[130,1242,1243],{"class":309}," --tail=100\n",[130,1245,1246],{"class":132,"line":301},[130,1247,328],{"emptyLinePlaceholder":327},[130,1249,1250],{"class":132,"line":313},[130,1251,1252],{"class":1164},"# 접속\n",[130,1254,1255,1258],{"class":132,"line":324},[130,1256,1257],{"class":1170},"curl",[130,1259,1260],{"class":166}," http:\u002F\u002Flocalhost:8080\n",[130,1262,1263],{"class":132,"line":331},[130,1264,328],{"emptyLinePlaceholder":327},[130,1266,1267],{"class":132,"line":338},[130,1268,1269],{"class":1164},"# 종료하되 DB Volume은 보존\n",[130,1271,1272,1274,1276],{"class":132,"line":348},[130,1273,1171],{"class":1170},[130,1275,1174],{"class":166},[130,1277,1278],{"class":166}," down\n",[121,1280,1282],{"className":388,"code":1281,"language":390,"meta":126,"style":126},"flowchart LR\n    U[\"Browser\u003Cbr\u002F>localhost:8080\"] --> N[\"Nginx frontend\"]\n    N --> PUB[\"public network\"]\n    PUB -->|\"\u002Fapi → backend:8080\"| A[\"Backend API\"]\n    A --> PRI[\"private internal network\"]\n    PRI -->|\"db:5432\"| P[\"PostgreSQL\"]\n    P --> V[\"db-data volume\"]\n",[105,1283,1284,1288,1293,1298,1303,1308,1313],{"__ignoreMap":126},[130,1285,1286],{"class":132,"line":133},[130,1287,397],{},[130,1289,1290],{"class":132,"line":144},[130,1291,1292],{},"    U[\"Browser\u003Cbr\u002F>localhost:8080\"] --> N[\"Nginx frontend\"]\n",[130,1294,1295],{"class":132,"line":152},[130,1296,1297],{},"    N --> PUB[\"public network\"]\n",[130,1299,1300],{"class":132,"line":160},[130,1301,1302],{},"    PUB -->|\"\u002Fapi → backend:8080\"| A[\"Backend API\"]\n",[130,1304,1305],{"class":132,"line":219},[130,1306,1307],{},"    A --> PRI[\"private internal network\"]\n",[130,1309,1310],{"class":132,"line":230},[130,1311,1312],{},"    PRI -->|\"db:5432\"| P[\"PostgreSQL\"]\n",[130,1314,1315],{"class":132,"line":240},[130,1316,1317],{},"    P --> V[\"db-data volume\"]\n",[636,1319],{},[10,1321,1323],{"id":1322},"_3-전체-요약","3. 전체 요약",[15,1325,1327],{"id":1326},"핵심-구분표","핵심 구분표",[20,1329,1330,1340],{},[23,1331,1332],{},[26,1333,1334,1337],{},[29,1335,1336],{},"궁금한 점",[29,1338,1339],{},"사용하는 항목",[39,1341,1342,1351,1361,1371,1381,1389,1402,1416,1424,1438,1448],{},[26,1343,1344,1347],{},[44,1345,1346],{},"어떤 컨테이너를 실행하는가?",[44,1348,1349],{},[105,1350,137],{},[26,1352,1353,1356],{},[44,1354,1355],{},"Registry 이미지를 사용하는가?",[44,1357,1358],{},[105,1359,1360],{},"image",[26,1362,1363,1366],{},[44,1364,1365],{},"Dockerfile로 직접 만드는가?",[44,1367,1368],{},[105,1369,1370],{},"build",[26,1372,1373,1376],{},[44,1374,1375],{},"외부에서 어떻게 접근하는가?",[44,1377,1378],{},[105,1379,1380],{},"ports",[26,1382,1383,1386],{},[44,1384,1385],{},"서비스끼리 어떻게 찾는가?",[44,1387,1388],{},"Network + 서비스 이름",[26,1390,1391,1394],{},[44,1392,1393],{},"컨테이너에 설정값을 어떻게 주는가?",[44,1395,1396,271,1399],{},[105,1397,1398],{},"environment",[105,1400,1401],{},"env_file",[26,1403,1404,1407],{},[44,1405,1406],{},"Compose 파일의 변수를 어떻게 채우는가?",[44,1408,1409,271,1412,1415],{},[105,1410,1411],{},".env",[105,1413,1414],{},"--env-file",", Shell 환경 변수",[26,1417,1418,1421],{},[44,1419,1420],{},"데이터를 어떻게 보존하는가?",[44,1422,1423],{},"Named Volume, Bind Mount",[26,1425,1426,1429],{},[44,1427,1428],{},"DB 준비를 어떻게 기다리는가?",[44,1430,1431,1434,1435],{},[105,1432,1433],{},"healthcheck"," + ",[105,1436,1437],{},"depends_on.condition",[26,1439,1440,1443],{},[44,1441,1442],{},"프로세스 종료 후 어떻게 복구하는가?",[44,1444,1445],{},[105,1446,1447],{},"restart",[26,1449,1450,1453],{},[44,1451,1452],{},"설정 변경을 어떻게 반영하는가?",[44,1454,1455,1458],{},[105,1456,1457],{},"docker compose up -d","로 재생성",[15,1460,1462],{"id":1461},"초압축-암기","초압축 암기",[121,1464,1467],{"className":1465,"code":1466,"language":651,"meta":126},[649],"Compose file = 여러 컨테이너 애플리케이션의 선언형 설계도\nService      = 컨테이너 실행 설정\nProject      = Compose가 관리하는 리소스 묶음\nimage        = 기존 이미지 사용\nbuild        = Dockerfile로 이미지 생성\nports        = Host 포트를 Container에 공개\nexpose       = 내부 사용 포트 문서화, Host에는 미공개\nenvironment  = 컨테이너 환경 변수\n.env         = Compose 변수 보간의 기본 입력\nVolume       = 컨테이너와 분리된 영속 저장소\ndepends_on   = 시작 의존 관계\nhealthcheck  = 서비스 상태 검사\nrestart      = 메인 프로세스 종료 후 재시작 규칙\nup           = 생성,실행,필요 시 재생성\nstop         = 컨테이너를 남기고 중지\ndown         = 컨테이너와 Compose 네트워크 제거\n",[105,1468,1466],{"__ignoreMap":126},[1470,1471,1472],"style",{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":126,"searchDepth":144,"depth":144,"links":1474},[1475,1483,1488],{"id":12,"depth":144,"text":13,"children":1476},[1477,1478,1480,1482],{"id":17,"depth":152,"text":18},{"id":111,"depth":152,"text":1479},"1.1 depends_on의 한계",{"id":177,"depth":152,"text":1481},"1.2 Healthcheck와 service_healthy",{"id":514,"depth":152,"text":515},{"id":640,"depth":144,"text":641,"children":1484},[1485,1486,1487],{"id":644,"depth":152,"text":645},{"id":656,"depth":152,"text":657},{"id":1151,"depth":152,"text":1152},{"id":1322,"depth":144,"text":1323,"children":1489},[1490,1491],{"id":1326,"depth":152,"text":1327},{"id":1461,"depth":152,"text":1462},[1493,1494],"DevOps","Docker","2026-08-17 13:00:00 +0900","Docker Compose의 depends_on, Healthcheck, 재시작 정책과 Frontend, Backend, DB 전체 구성을 정리한다.","md",null,"\u002Fdevops\u002Fdocker\u002F2026\u002F08\u002F17\u002Fdocker-13-compose-healthcheck-and-practice\u002F",{"layout":1501},"post","13","\u002Fposts\u002Fci-cd-docker\u002Fdocker\u002F2026-08-17-docker-13-compose-healthcheck-and-practice",{"title":5,"description":1496},"Docker 기초","posts\u002FCI-CD-Docker\u002FDocker\u002F2026-08-17-docker-13-compose-healthcheck-and-practice",[1494,1508,68,115,79],"Docker Compose","12G7g-WK2fynQn-qFeKwY9Zfe8Puj0z4XVuVxYLOYO0",1788744787978]