환경 Docker Engine · Docker Compose 문서 기준 · 2026-10-23 확인
멈춰 둔 컨테이너가 재부팅 뒤 혼자 다시 떴거나, 반대로 안 떠서 헤맨 적이 있다면 이 두 정책이 갈리는 자리부터 봅니다.
나란히 놓으면 차이는 '손으로 멈춘 뒤'에서만 생긴다
always 와 unless-stopped 는 평소에는 똑같이 움직인다. 컨테이너 프로세스가 끝나면 종료 코드가 0 이든 1 이든 도커 데몬이 다시 띄운다. 서버를 재부팅해도, 도커 데몬이 다시 떠도 컨테이너를 살린다.
갈리는 건 사람이 docker stop 으로 내렸을 때다. always 는 그 순간에는 다시 띄우지 않지만, 데몬이 다시 시작되면 멈춰 있던 컨테이너까지 살린다. unless-stopped 는 손으로 멈춘 상태를 기억해서, 데몬이 다시 떠도 그대로 둔다.
on-failure 와 no 까지 같이 놓고 보면 위치가 잡힌다.
no on-failure always unless-stopped
----------------------- ------- ---------------- ---------------- --------------
정상 종료(코드 0) 안 띄움 안 띄움 띄움 띄움
오류 종료(코드 0 아님) 안 띄움 띄움(횟수 제한 가능) 띄움 띄움
docker stop 직후 - 안 띄움 안 띄움 안 띄움
그 뒤 데몬 재시작·재부팅 안 띄움 안 띄움 다시 띄움 계속 멈춤
멈추지 않은 채 재부팅 안 띄움 안 띄움 다시 띄움 다시 띄움
always 와 unless-stopped 는 넷째 줄 하나만 다르다
always 는 데몬이 다시 뜨면 손으로 멈춘 것까지 살리고, unless-stopped 는 멈춘 것은 멈춘 대로 둔다.
재부팅이 곧 데몬 재시작이라 always 가 멈춘 컨테이너를 되살린다
도커 문서는 always 를 이렇게 설명한다. 손으로 멈춘 컨테이너는 데몬이 다시 시작되거나 컨테이너를 직접 다시 시작할 때만 다시 뜬다. 손으로 멈추자마자 다시 띄우면 멈출 방법이 없으니, 그 순간만 정책을 쉬게 하는 셈이다.
문제는 데몬 재시작이 생각보다 자주 일어난다는 점이다. 서버 재부팅이 그렇고, 패키지 업데이트로 docker 엔진이 올라갈 때도 데몬이 다시 뜬다. daemon.json 을 고치고 systemctl restart docker 를 쳐도 마찬가지다. 이 중 어느 하나만 지나가도 always 컨테이너는 전부 살아난다.
그래서 '잠깐 내려 둔' 컨테이너가 always 라면, 그 상태는 다음 데몬 재시작까지만 유지된다고 보는 게 맞다. 옛 버전 워커, 마이그레이션 전 앱, 같은 포트를 쓰는 테스트 컨테이너가 이렇게 다시 떠서 엉뚱한 일을 한다.
# 지금 정책부터 확인
docker inspect -f '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)
# 재현: always 컨테이너를 멈춘 뒤 데몬을 다시 띄운다
docker run -d --name demo --restart always alpine sleep 3600
docker stop demo
sudo systemctl restart docker
docker ps --filter name=demo # 다시 Up 으로 떠 있다
멈춰 둔 always 컨테이너는 데몬 재시작 한 번에 돌아온다
unless-stopped 는 반대로 '멈춘 걸 잊었을 때' 조용히 빠진다
unless-stopped 는 손으로 멈춘 상태를 데몬 재시작 뒤에도 지킨다. 점검용으로 내려 둔 컨테이너가 재부팅 뒤 멋대로 뜨는 일은 없다.
대신 멈춘 사실을 사람이 기억해야 한다. 원인을 찾으려고 docker stop 해 두고 그대로 퇴근하면, 그 컨테이너는 재부팅 뒤에도 내려간 채다. 다른 서비스는 다 올라와 있으니 한동안 아무도 모르고 지나간다.
이 정책에서 '멈춘 상태'를 푸는 방법은 docker start 나 docker restart 로 직접 한 번 띄우는 것이다. 그 뒤로는 다시 평소처럼 죽으면 살리고 재부팅하면 살린다. Compose 로 띄웠다면 docker compose stop 이 같은 효과를 내고, docker compose start 나 up 으로 다시 띄우면 된다.
# 멈춰 있는 컨테이너와 그 정책을 같이 본다
docker ps -a --filter status=exited \
--format '{{.Names}}\t{{.Status}}' |
while IFS=$'\t' read -r name status; do
printf '%s\t%s\t%s\n' "$name" \
"$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$name")" "$status"
done
# 다시 살리면 정책도 평소대로 돌아온다
docker start worker
exited 로 남은 unless-stopped 컨테이너가 있으면 일부러 멈춘 것인지 확인한다
unless-stopped 의 함정은 되살아나는 게 아니라 안 살아나는 쪽이다.
오류로 죽을 때는 둘 다 똑같이 계속 다시 띄운다
프로세스가 스스로 죽은 경우는 두 정책이 다르지 않다. 둘 다 종료 코드를 보지 않고 다시 띄운다. 설정 오류로 시작하자마자 죽는 컨테이너라면 두 정책 모두 계속 다시 띄우려 든다.
도커는 이때 재시작 사이에 간격을 둔다. 100밀리초에서 시작해 매번 두 배로 늘리고, 최대 1분까지 기다린다. 다시 띄운 컨테이너가 10초 이상 버티면 간격은 100밀리초로 돌아간다. 그래서 크래시 루프는 처음 몇 초만 요란하고 그 뒤로는 1분에 한 번씩 조용히 죽는다. docker ps 에 Restarting 이 보이거나 RestartCount 가 계속 오르면 이 상태다.
끝없이 도는 게 싫다면 on-failure:5 처럼 횟수를 건다. 다만 on-failure 는 데몬이 다시 시작될 때 컨테이너를 띄우지 않으니, 재부팅 뒤에도 살아 있어야 하는 서비스에는 맞지 않는다.
# 크래시 루프인지 본다
docker inspect -f '{{.RestartCount}} {{.State.Status}} {{.State.ExitCode}}' api
docker logs --tail 50 api
# 원인을 고치는 동안 재시작을 멈춘다(컨테이너는 그대로 둔다)
docker update --restart no api
docker stop api
루프를 끊을 때는 정책부터 no 로 바꾸고 멈춘다
정책은 컨테이너를 다시 만들지 않고도 바꿀 수 있다
이미 always 로 떠 있는 컨테이너들을 unless-stopped 로 바꾸려고 지우고 다시 만들 필요는 없다. docker update --restart 로 돌고 있는 컨테이너의 정책만 바꾼다. 문서 예시대로 docker ps -q 를 넘기면 돌고 있는 전부를 한 번에 바꾼다.
Compose 로 관리하는 컨테이너라면 compose 파일의 restart 값도 같이 고쳐 둔다. update 로만 바꿔 두면 다음 docker compose up 때 파일에 적힌 값으로 다시 만들어지면서 원래대로 돌아간다.
하나 더, --restart 와 --rm 은 같이 쓰지 못한다. 둘을 함께 주면 docker run 이 오류로 거절한다. 끝나면 지워질 일회용 컨테이너에는 재시작 정책을 걸지 않는다.
# 돌고 있는 컨테이너 전부를 unless-stopped 로
docker update --restart unless-stopped $(docker ps -q)
# compose 파일도 같은 값으로
# services:
# worker:
# image: myapp/worker:2.3
# restart: unless-stopped
update 로 바꾼 값은 compose 파일에도 옮겨 적어야 남는다
언제 어느 쪽을 쓰나
점검이나 배포 중에 컨테이너를 손으로 내려 두는 일이 잦은 서버라면 unless-stopped 가 맞다. 사람이 멈춘 것은 사람이 다시 띄울 때까지 그대로 있어야 재부팅 한 번에 옛 컨테이너가 끼어드는 일이 없다. 웹 앱, 워커, 크론 대신 도는 배치 컨테이너가 대개 여기 들어간다.
always 는 '무슨 일이 있어도 떠 있어야 하고, 손으로 내려 둘 일이 거의 없는' 컨테이너에 쓴다. 로그 수집기, 모니터링 에이전트, 리버스 프록시처럼 하나라도 빠지면 다른 것까지 안 보이는 것들이다. 누가 실수로 멈춰도 다음 재부팅에는 돌아온다.
어느 쪽이든 '멈춘 컨테이너'가 남아 있는 서버는 재부팅 전에 한 번 훑는다. always 라면 다시 뜰 것이고, unless-stopped 라면 계속 내려가 있을 것이다. 둘 중 어느 결과를 원하는지 그 자리에서 정해 두면 된다.
손으로 내릴 일이 있으면 unless-stopped, 절대 빠지면 안 되는 상주 프로세스면 always.
조치 후 확인할 것
- docker inspect 로 컨테이너마다 HostConfig.RestartPolicy.Name 이 무엇인지 확인해 둔다.
- 재부팅·엔진 업데이트 전에 docker ps -a 로 exited 컨테이너를 보고, always 면 다시 뜬다는 것을 감안한다.
- unless-stopped 컨테이너를 멈췄다면 다시 docker start 했는지 확인한다. 안 하면 재부팅 뒤에도 내려가 있다.
- RestartCount 가 계속 오르면 크래시 루프다. docker update --restart no 로 정책을 끊고 원인을 본다.
- docker update 로 바꾼 정책은 compose 파일의 restart 값에도 옮겨 적는다.
always 와 unless-stopped 는 평소엔 구분이 안 돼서 아무거나 적어 두기 쉽다. 차이는 누군가 컨테이너를 손으로 멈춘 뒤 서버가 한 번 다시 뜰 때 드러난다. 그때 되살아나길 원하는지, 멈춘 대로 있길 원하는지를 컨테이너마다 정해 두면 재부팅 다음 날 아침이 조용해진다.
'복사금지 블로그 짜증나서 만든 개발문서' 카테고리의 다른 글
| SSH 공개키 넣었는데 Permission denied (publickey)라면, 홈 디렉터리 권한부터 보세요 (0) | 2026.10.16 |
|---|---|
| 도커 서버 디스크가 꽉 찼다면, 컨테이너 로그가 무제한으로 쌓이고 있을 수 있습니다 (0) | 2026.10.02 |
| sudo 만 붙이면 command not found, 환경변수까지 사라질 때 — sudo 질문 6가지 (0) | 2026.09.25 |
| cron 배치가 겹쳐 돌아 알림이 세 번 나갔던 밤, flock 한 줄이 없어서 (0) | 2026.09.18 |
| 재부팅하면 서비스가 안 올라오는 서버, 내리기 전에 훑는 목록 (0) | 2026.09.08 |