Skip to content
Saegida
Go back

재부팅 한 번에 알림이 끊겼다 — 홈서버 도커 서비스를 자동 복구하는 3가지

재부팅 한 번에 알림이 끊겼다 — 홈서버 도커 서비스를 자동 복구하는 3가지(재시작 정책·헬스체크·상태 영속)를 정리한 글의 대표 이미지

서버를 한 번 재부팅했더니, 늘 오던 알림이 조용히 멈춘 적 있으신가요? 눈에 띄는 에러도 없고, 그냥 어느 순간부터 아무 소식이 없는 상태 말입니다. 홈서버나 개인 PC에서 docker로 뭔가를 상시 돌리고 있다면 한 번쯤 겪게 되는 일인데요, 이 글은 바로 그 상황을 직접 겪고 고친 기록입니다.

정리해 둘 핵심은 하나입니다 — 홈서버 같은 단일 호스트에서 컨테이너가 “재부팅해도 알아서 다시 뜨게” 만드는 건 결국 세 가지로 요약됩니다. 재시작 정책, 기동 순서(헬스체크), 상태 영속. 순서대로 짚어보겠습니다.

알림은 왜 조용히 멈췄을까요?

상황은 이랬습니다. 홈서버에서 매일 정해진 시각에 작업을 돌리고, 끝나면 디스코드로 알림을 보내는 개인 서비스를 docker로 운영하고 있었습니다. 그런데 어느 날 그 정기 알림이 오지 않았죠. 로그를 열어보기 전에 먼저 확인한 건 호스트 상태였는데, uptime을 보니 전날 밤에 재부팅이 있었습니다.

여기서 이상한 점이 하나 있었습니다. 같은 호스트에 올려둔 다른 스택은 멀쩡히 살아있었거든요. 똑같이 재부팅을 겪었는데 어떤 건 살고 어떤 건 죽었다 — 이 대조가 원인을 정확히 가리키고 있었습니다.

재부팅 후 restart 정책이 있는 스택은 자동 복구되어 살아있고, 정책이 없는 스택은 꺼진 채로 남아 알림이 끊긴 상황을 대비한 다이어그램

원인은 하나가 아니었습니다

파고들어 보니 문제는 두 겹이었습니다.

즉 “재부팅 후 알림이 끊긴” 건 버그가 아니라 설정의 공백이었습니다. 그럼 이 공백을 세 가지 원칙으로 메워보겠습니다.

원칙 1 — 재시작 정책: 재부팅해도 살아있게

가장 먼저, 컨테이너에 재시작 정책(restart policy) 을 준다. docker가 제공하는 값은 네 가지입니다. ([소스 1])

핵심은 재부팅했을 때의 동작입니다. alwaysunless-stopped둘 다 Docker 데몬이 시작될 때 컨테이너를 다시 띄웁니다. 리눅스에서 docker 서비스가 부팅과 함께 자동 시작되도록 돼 있으면, 이 정책이 걸린 컨테이너는 재부팅 후 알아서 복구됩니다. ([소스 1])

그럼 둘 중 뭘 쓰면 될까요? 차이는 “내가 일부러 꺼둔 컨테이너” 를 어떻게 대하느냐입니다. unless-stopped는 수동으로 멈춰둔 컨테이너라면 데몬이 재시작돼도 다시 띄우지 않습니다. 반면 always는 수동으로 꺼뒀어도 데몬 재시작 때 다시 살아나죠. ([소스 1]) 홈서버에서는 “내가 점검하려고 끈 건 꺼진 채로 두는” 쪽이 대체로 편하니, 실무 기본값으로는 unless-stopped 가 무난합니다.

compose라면 서비스에 한 줄이면 됩니다.

services:
  db:
    image: postgres:16
    restart: unless-stopped   # ← 재부팅해도 자동 복구

이미 돌아가고 있는 컨테이너라면, 다시 만들지 않고도 정책만 바꿔 끼울 수 있습니다.

# 무중단으로 재시작 정책만 변경
docker update --restart unless-stopped <컨테이>

# 잘 걸렸는지 확인
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' <컨테이>

유의사항 1. 재시작 정책은 docker 데몬 자체가 부팅 시 시작돼야 의미가 있습니다. 데몬이 안 떠 있으면 컨테이너도 못 뜨겠죠. sudo systemctl enable docker로 부팅 자동시작을 켜 두세요.

유의사항 2. docker는 무한 재시작을 막으려고, 컨테이너가 최소 10초 이상 실행돼야 안정 상태로 봅니다. 시작하자마자 죽는 컨테이너가 정책 때문에 계속 재시작을 도는 상황을 방지하는 장치죠. ([소스 1])

docker 재시작 정책 no·always·on-failure·unless-stopped를 재부팅 복구 여부와 수동 stop 존중 여부로 비교한 표. 홈서버 기본값은 unless-stopped

원칙 2 — 기동 순서와 헬스체크: 준비된 다음에

재시작 정책만으로 끝이 아닙니다. 재부팅 뒤 여러 컨테이너가 동시에 살아나면, 앱이 DB가 미처 준비되기 전에 접속을 시도해 실패할 수 있거든요. “컨테이너는 떴는데 안의 프로세스는 아직 준비 안 됨” 상태에 물리는 겁니다.

그래서 헬스체크(healthcheck)기동 순서를 함께 겁니다. compose의 depends_oncondition: service_healthy를 주면, 의존 서비스가 healthy 판정을 받은 뒤에 다음 서비스가 시작됩니다. ([소스 2])

services:
  db:
    image: postgres:16
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

  app:
    build: .
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy   # ← DB가 준비된 다음 기동
    env_file: [.env]

유의사항. depends_on시작 순서만 보장합니다. 서비스가 돌아가는 도중에 의존 대상이 잠깐 죽는 경우까지 막아주지는 않아요. ([소스 2]) 그러니 앱은 “DB 연결이 끊기면 잠시 뒤 다시 붙는” 재연결을 스스로 견디도록 짜 두는 게 안전합니다.

원칙 3 — 상태는 컨테이너 밖에

마지막은 제가 실제로 발을 헛디딘 지점입니다. 컨테이너 파일시스템은 다시 만들면 사라집니다. 그래서 유지해야 할 상태(데이터·설정·플래그)는 컨테이너 밖에 둬야 하죠. 방법은 두 가지입니다. ([소스 3])

제가 겪은 함정이 바로 여기였습니다. 앱의 동작을 켜고 끄는 상태 플래그 파일이 있었는데, 이미지를 만들 때 이 파일이 든 폴더를 일부러 제외해 버렸거든요. 그랬더니 컨테이너 안에서는 그 파일이 아예 없는 것으로 취급됐고, 앱은 “기능이 꺼져 있다”고 읽어버렸습니다. 겉으로는 잘 떴는데 정작 하는 일이 없는 상태였죠.

해결은 간단했습니다. 그 상태 폴더를 호스트에서 bind mount로 물려, 컨테이너가 재생성돼도 상태가 그대로 유지되게 했습니다.

  app:
    # ...
    volumes:
      - ./state:/app/state   # 런타임 상태(플래그 등)를 호스트에 영속

유의사항. “이미지에는 굽지 않고 마운트로만 제공되는 파일”은, 마운트를 깜빡 빠뜨리면 없는 것으로 취급됩니다. 상태 파일을 마운트에 의존한다면, 그 마운트가 항상 함께 붙는지 꼭 확인하세요.

그래서, 세 가지 설정이면 됩니다

세 원칙을 한 compose에 모으면 대략 이런 모양이 됩니다. (값은 모두 예시용 더미입니다.)

services:
  db:
    image: postgres:16
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10
    volumes:
      - db_data:/var/lib/postgresql/data

  app:
    build: .
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    env_file: [.env]
    volumes:
      - ./state:/app/state

volumes:
  db_data:

과장 없이, 재부팅 자동복구를 가르는 건 사실상 이 세 가지 설정입니다 — restart 정책 · depends_on+healthcheck · volumes(상태 영속). 걸어두고 나면, 다음 명령으로 실제 정책이 붙었는지 재부팅 없이 확인할 수 있습니다.

docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' <컨테이>   # unless-stopped
docker ps   # 상태(Up/healthy) 확인

마무리

돌아보면 이번 장애의 교훈은 담백합니다 — 개인 프로젝트도 “자동으로 다시 뜨는가”는 이 세 가지 설정에서 갈린다. 평소엔 티가 안 나다가, 재부팅 한 번에 조용히 드러나는 종류의 빈틈이죠.

지금 홈서버에서 뭔가를 상시 돌리고 있다면, 거창한 점검 말고 딱 하나만 해보면 좋습니다. docker inspect각 컨테이너의 재시작 정책부터 확인해 보세요. no가 하나라도 보이면, 그게 다음 재부팅에 조용히 사라질 후보입니다.

참고 자료 (공식 출처)

재시작 정책·헬스체크·마운트의 동작은 Docker 공식 문서를 근거로 확인했습니다.


Share this post:

Previous Post
'데이터 좀 뽑아주세요'를 줄이는 길 — 대화형 데이터와 데이터 조직의 다음 단계
Next Post
FortiGate SSL VPN이 사라진다: Tailscale·AWS Client VPN 전환 비교