SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor

실무에서 꼭 필요한 Docker Compose 명령어 정리

서버 운영 중 매일 사용하는 Docker Compose V2 핵심 명령어를 정리했습니다. 서비스 배포, 로그 확인, 컨테이너 관리 등 상황별 필수 명령어와 함께 up -d --wait 옵션 등 실무에서 자주 발생하는 오류를 방지하는 팁을 확인해 보시기 바랍니다.

실무에서 자주 사용하는 Compose 명령어

Docker Compose는 수십 개의 하위 명령어를 제공합니다. 서버 운영 시 매일 사용하는 명령어는 12개 내외입니다. 이 페이지에서는 작업 목적에 따라 명령어를 분류하고, 각 명령어의 사용 이유를 명확히 설명하며, 주의가 필요한 경우 심층 분석 문서를 안내합니다.

이 문서의 모든 내용은 Compose V2 또는 V5를 기준으로 합니다. 즉, docker compose와 같이 공백을 포함한 형태를 사용하며, 구형 docker-compose 스크립트는 사용하지 않습니다. 두 방식 모두 Go 플러그인이며, Linux 환경에서 Compose 플러그인은 Docker Engine과 별도로 설치됩니다. Ubuntu 24.04 LTS는 여전히 universe 저장소에서 1.29.2-6ubuntu1 버전의 레거시 Python V1 도구를 docker-compose라는 이름으로 제공합니다. docker compose version 명령어로 확인하십시오. 만약 명령어를 사용할 수 없다면, Docker 공식 저장소를 설정한 뒤 docker-compose-plugin 패키지를 설치하십시오.

아래의 모든 명령어는 compose.yaml 파일이 위치한 디렉터리에서 실행해야 합니다. 별도로 지정하지 않으면 Compose는 해당 디렉터리 이름을 기본 프로젝트 이름으로 사용합니다. -f 또는 COMPOSE_FILE 옵션을 사용하지 않는 경우, Compose는 작업 디렉터리와 상위 디렉터리를 탐색하여 설정 파일을 찾습니다. 따라서 한 단계 상위 디렉터리에서 실행하면 설정 파일이 해당 탐색 경로 내에 있을 때만 스택을 인식합니다. 파일 형식이 생소하다면 VPS에서 첫 Compose 파일 작성하기를 먼저 읽고 돌아와 명령어를 확인하십시오.

수명 주기: 실행을 위한 네 가지 명령어와 컨테이너를 제거하는 명령어

docker compose up -d
docker compose up -d --wait
docker compose stop
docker compose start
docker compose restart web
docker compose down

up -d은 네트워크를 생성하고 컨테이너를 생성한 뒤 실행하고 종료됩니다. 이 명령어는 애플리케이션의 준비 상태를 기다리지 않고 컨테이너를 시작한 직후 반환되므로, 뒤이어 curl 프로브를 실행하는 배포 스크립트는 첫 시도에서 실패하는 경우가 많습니다. up -d --wait는 헬스체크가 설정된 서비스가 정상 상태가 될 때까지, 그리고 헬스체크가 없는 서비스는 실행될 때까지 기다립니다. 서비스가 비정상 상태이거나 --wait-timeout 시간이 초과되면 0이 아닌 종료 코드가 반환됩니다. 이 플래그는 기반이 되는 체크 설정만큼만 유효하므로, 자동화에 의존하기 전에 Compose가 신뢰할 수 있는 헬스체크를 먼저 작성하십시오.

stop은 컨테이너를 중지하지만 삭제하지는 않으므로, start를 실행하면 동일한 쓰기 가능 레이어를 가진 컨테이너가 다시 시작됩니다. down은 컨테이너를 중지한 뒤 컨테이너와 프로젝트 네트워크를 모두 제거합니다. 볼륨 외부의 컨테이너 내부에 기록된 모든 데이터는 함께 삭제됩니다. 이는 Compose에서 가장 비용이 큰 오해이며, down과 stop의 전체적인 차이점에서 이러한 문제가 발생하는 상황을 다룹니다.

restart는 설정 다시 불러오기(reload)가 아닙니다. 이 명령어는 기존 설정 그대로 컨테이너를 중지했다가 다시 시작할 뿐이므로, 변경된 환경 변수, 새로운 이미지 태그, 수정된 포트 매핑은 전혀 적용되지 않습니다. 파일 변경 사항을 적용하려면 up -d를 다시 실행해야 합니다. Compose는 각 서비스와 실행 중인 컨테이너를 비교하여 설정이나 이미지가 변경된 컨테이너만 다시 생성합니다.

변경 사항 적용: recreate, pull, 또는 rebuild

docker compose up -d --force-recreate
docker compose pull && docker compose up -d
docker compose build --no-cache web
docker compose up -d --build web

up -d은 설정과 이미지가 변경되지 않았을 때 실행 중인 컨테이너를 재사용합니다. 이미지 pull과 빌드는 서비스의 pull 정책을 따릅니다. --force-recreate는 해당 비교 과정을 무시하고 설정이 동일하더라도 모든 컨테이너를 교체하므로, 컨테이너 내부의 비정상적인 상태를 제거하는 가장 빠른 방법입니다.

명시적인 이미지 업데이트 루틴은 서로 다른 두 가지 작업을 수행하기 위해 두 개의 명령어를 사용합니다. pull는 파일에 명시된 각 태그의 최신 이미지를 다운로드합니다. 그 후 up -d는 서비스의 이미지 ID가 실행 중인 컨테이너와 일치하지 않음을 감지하고 컨테이너를 다시 생성합니다. 기본 missing pull 정책에서는 latest를 제외한 캐시된 태그가 재사용되며, latest는 항상 새로 pull합니다. 다중 서비스 스택에서는 모든 서비스의 latest을 한꺼번에 pull할 경우 10초 전까지 정상 작동하던 앱이 중단될 위험이 있습니다. 이것이 바로 자체 호스팅 AFFiNE 워크스페이스에서 애플리케이션과 마이그레이션 이미지를 동일한 다이제스트(digest)로 고정할 것을 권장하는 이유입니다. 고정(pinning)을 사용하면 업그레이드는 태그를 의도적으로 수정한 뒤 동일한 pull 및 recreate 과정을 거치는 작업이 됩니다. 부팅 시 데이터베이스 마이그레이션을 수행하는 스택의 경우, 두 명령어 중 하나를 실행하기 전에 덤프를 확보해 두는 것이 좋습니다. 이는 자체 호스팅 Chatwoot 지원 데스크가 모든 버전 업데이트 시 따르는 루틴입니다.

build은 build: 섹션을 선언한 서비스에 적용되며, image:을 함께 선언한 서비스도 포함됩니다. up -d --build은 빌드와 시작을 한 번에 처리하며, 코드를 수정하는 동안 사용하는 일반적인 루프입니다. --no-cache는 모든 레이어를 처음부터 다시 빌드하므로, 캐시된 레이어가 명확히 오래된 경우에만 사용하십시오. 일부 프로젝트는 하나의 복제된 저장소에서 두 가지 경로를 모두 제공하며, 이때 두 루프는 대안적인 업데이트 경로가 됩니다. 예를 들어 자체 호스팅 openGym 운동 추적기는 docker compose up -d --build을 사용하여 고정된 체크아웃을 빌드합니다. 해당 프로젝트의 latest 이미지를 pull하면 체크아웃된 버전보다 최신 릴리스가 실행될 수 있기 때문입니다.

실행 중인 서비스 확인하기

docker compose ps
docker compose ps -a
docker compose logs -f --tail=100
docker compose logs --since 15m --timestamps db
docker compose top
docker compose ls

ps은 실행 중인 컨테이너만 나열합니다. 시작 과정에서 충돌한 서비스는 -a 옵션을 추가하기 전까지는 보이지 않습니다. 따라서 ps에는 나타나지 않지만 ps -a에서는 Exited (1) 상태로 표시되는 컨테이너는 일반적인 시작 실패 사례입니다. 종료 코드를 확인한 뒤 로그를 읽으십시오.

logs -f는 모든 서비스를 동시에 추적하며 각 줄 앞에 서비스 이름을 표시합니다. 서비스 간 통신이 이루어지고 이벤트 순서가 중요할 때 유용한 보기 방식입니다. 특정 서비스만 보려면 이름을 지정하십시오. 한 달 이상 실행된 컨테이너에서는 --tail=100 옵션이 중요합니다. 기본값은 전체 기록을 출력하여 터미널을 가득 채우기 때문입니다. --since 15m은 방금 수행한 재시작 중에 무슨 일이 일어났는지 확인하려는 일반적인 의문에 답을 줍니다.

top은 각 컨테이너 내부의 프로세스를 나열합니다. 이를 통해 "컨테이너가 실행 중인 상태"와 "내부 프로세스가 실행 중인 상태"를 구분할 수 있습니다. ls는 현재 디렉터리에서 벗어나 호스트에서 실행 중인 Compose 프로젝트와 그 상태를 나열합니다. 중지된 프로젝트까지 포함하려면 -a을 추가하십시오.

서비스 내부에서 셸 실행하기

docker compose exec web sh
docker compose exec -u root web sh
docker compose run --rm web env
docker compose run --rm --no-deps web sh

exec은 이미 실행 중인 컨테이너 내부에서 명령을 수행합니다. run는 동일한 서비스 정의를 사용하여 새로운 컨테이너를 시작하는데, 서비스가 짧게 실행되고 종료되어 exec 명령을 사용할 수 없을 때 유용합니다. run을 사용할 때는 반드시 --rm를 함께 사용해야 합니다. 그렇지 않으면 명령을 실행할 때마다 중지된 컨테이너가 남게 되며, 이것이 쌓이면 docker compose ps -a의 내용을 읽기 어려워집니다.

bash을 사용하기 전에 sh을 먼저 시도하십시오. 일부 Alpine 기반 이미지에는 bash이 포함되어 있으며, 공식 PostgreSQL Alpine 이미지도 이에 해당합니다. 셸이 없는 경우 exec: "bash": executable file not found in $PATH라는 오류 메시지가 나타납니다. run에 --no-deps 옵션을 추가하면 서비스의 의존성을 건너뜁니다. 이를 통해 간단한 설정 확인을 위해 전체 데이터베이스를 부팅하는 상황을 방지할 수 있습니다.

run --rm web env는 모든 .env 파일, environment: 블록, 셸 변수가 병합된 후 서비스가 실제로 적용받는 환경을 확인하는 가장 빠른 방법입니다. 값이 올바르지 않다면 병합 순서가 원인인 경우가 많으며, Compose가 환경 파일과 비밀 값을 해석하는 방법에서 어떤 소스가 우선순위를 갖는지 확인할 수 있습니다.

네트워크, 포트 및 이름 확인

docker compose port web 80
docker compose exec web getent hosts db
docker compose config --networks

기본적으로 Compose는 모든 서비스를 하나의 프로젝트 네트워크에 배치하며, 각 서비스 이름은 해당 네트워크 내의 DNS 이름이 됩니다. 이미지가 getent을 포함하는 경우 web 내부에서 getent hosts db를 실행하면 이름 확인 여부를 검사할 수 있습니다. 이는 db이 연결을 수락하는지 여부를 테스트하는 것은 아닙니다. 이름은 확인되지만 연결이 거부된다면, 서버가 실행 중인지 그리고 요청된 주소와 포트에서 수신 대기 중인지 확인하십시오. 127.0.0.1에만 바인딩하는 것이 원인일 수 있습니다. 동일한 경계 원리로 인해 docker run으로 시작했거나 별도의 스택으로 실행된 컨테이너는 jellyfin과 같은 서비스 이름을 확인하기 위해 공유 네트워크가 필요합니다. Jellyfin 라이브러리를 위한 Halcyon 프런트엔드가 서버에 연결하지 못할 때 가장 먼저 확인해야 할 사항입니다. 이 모델의 나머지 부분은 Compose 네트워크와 서비스 DNS의 작동 방식에서 다룹니다.

port web 80는 컨테이너 포트가 게시된 호스트 주소와 포트를 출력하므로, 변수를 통해 매핑이 이루어진 경우 추측할 필요가 없습니다. 포트를 게시하면 Docker가 자체적으로 관리하는 방화벽 규칙이 작성되는데, 이 규칙은 사용자가 설정한 규칙보다 우선합니다. 따라서 비공개라고 생각했던 서비스가 인터넷에 노출될 수 있습니다. 이 사례는 게시된 Docker 포트가 ufw를 우회하는 이유에서 다룹니다. 포트를 게시하지 않고 프로젝트 네트워크 내의 인증 프록시를 서비스 앞단에 두는 것이 더 안전한 구성이며, 이는 단일 로그인 계층으로 Authentik 실행하기를 통해 구현할 수 있습니다.

볼륨 및 데이터

docker compose config --volumes
docker compose cp db:/etc/postgresql/pg_hba.conf ./pg_hba.conf
docker compose down -v

config --volumes은 프로젝트에 선언된 명명된 볼륨을 한 줄에 하나씩 출력합니다. 이 목록은 백업 인벤토리의 일부일 뿐이며, 바인드 마운트와 설정 파일도 함께 확인해야 합니다. 볼륨에 대체 불가능한 데이터가 저장되어 있다면, 목록만큼이나 정확한 백업 명령어가 중요합니다. 이것이 바로 PhotoPrism과 Immich 비교 문서에서 각 사진 서버에 필요한 덤프 및 복사 명령어를 상세히 설명하는 이유입니다. cp는 컨테이너 측에 service:path 형식을 사용하여 셸을 열지 않고도 컨테이너 내부로 파일을 복사하거나 외부로 가져올 수 있습니다.

down -v은 외부 볼륨을 제외한 선언된 명명된 볼륨과 컨테이너에 연결된 익명 볼륨, 그리고 컨테이너 자체를 삭제합니다. 이 명령어는 테스트 스택을 완전히 제거할 때는 적합하지만, 중요한 데이터가 포함된 경우에는 사용해서는 안 됩니다. 확인 절차가 없으며 되돌릴 수 없기 때문입니다. 바인드 마운트는 호스트 파일 시스템에 위치하므로 이 명령어의 영향을 받지 않고 유지됩니다. 이러한 영향 범위의 차이는 바인드 마운트와 명명된 볼륨 중 하나를 신중하게 선택해야 하는 이유 중 하나입니다.

데이터 손실 없이 디스크 공간을 확보하는 정리 작업

docker compose down --remove-orphans
docker system df
docker image prune -a
docker builder prune

--remove-orphans은(는) 프로젝트에는 속하지만 파일에는 더 이상 나타나지 않는 컨테이너를 삭제합니다. 이는 서비스를 이름을 변경한 후에 발생하는 상황과 정확히 일치합니다. 이 옵션을 사용하지 않으면 해당 컨테이너가 계속 실행되며, docker compose ps은(는) 기본적으로 실행 중인 고아 컨테이너를 포함합니다.

docker system df은(는) 삭제를 수행하기 전에 디스크 공간이 어디에 사용되고 있는지 보여주며, 이미지, 컨테이너, 로컬 볼륨, 빌드 캐시별로 회수 가능한 용량을 나누어 표시합니다. image prune -a은(는) 컨테이너가 사용하지 않는 모든 이미지를 태그 여부와 관계없이 삭제합니다. 여러 버전의 대용량 이미지를 내려받은 서버에서는 보통 이 작업으로 가장 많은 공간을 확보할 수 있습니다. builder prune은(는) 기본적으로 연결되지 않은(dangling) 빌드 캐시를 삭제하며, -a를 추가하면 사용하지 않는 모든 빌드 캐시를 삭제합니다.

위의 명령들은 명명된 볼륨(named volume)에는 영향을 주지 않습니다. docker volume prune --all, docker compose down -v, docker volume rm는 명명된 볼륨을 삭제합니다. 단순히 docker volume prune만 사용하면 익명 볼륨만 삭제됩니다.

변경으로 문제를 일으키기 전에 파일 확인하기

docker compose config --quiet
docker compose config --services
docker compose --dry-run up -d

config --quiet는 성공 시 아무것도 출력하지 않고 파일의 유효성만 검사하므로, 배포 전 단계나 git hook에서 사용해야 한다. 일반적인 config는 병합 및 보간이 모두 적용된 파일을 출력한다. 이 출력으로 변수가 정상적으로 해석되었는지, override 파일이 예상한 방식으로 계층화되었는지 확인할 수 있다. 설정되지 않은 변수는 빈 값으로 표시되며, 그 옆에 The "X" variable is not set. Defaulting to a blank string. 경고가 함께 표시된다.

--dry-run는 하위 명령 플래그가 아니라 전역 플래그이므로 up 앞에 작성해야 한다. 이 명령은 Compose가 수행할 모든 작업을 출력하지만 실제로 변경하지는 않는다. 중요한 스택에서 down하기 전에 30초를 투자할 가치가 있다.

파일, 프로필, 프로젝트 간 작업

docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose --profile debug up -d
docker compose -p staging up -d

여러 개의 -f 플래그는 순서대로 병합되며, 나중에 지정된 파일이 이전 파일의 설정을 키 단위로 덮어씁니다. 이것이 기본 파일 하나와 운영 환경용 오버라이드 파일을 작게 유지하는 표준적인 방법입니다. 단, 리스트와 맵에 대한 병합 규칙은 다르므로 예상치 못한 동작을 방지하려면 Compose의 다중 파일 병합 방식을 먼저 읽어보시기 바랍니다.

--profile은 해당 프로필로 태그된 서비스를 태그되지 않은 서비스와 함께 시작하며, 이를 통해 디버깅 도구를 일반적인 up 환경에서 분리할 수 있습니다. -p은 프로젝트 이름을 설정하므로, 동일한 스택의 복사본 두 개를 각각 별도의 네트워크와 볼륨 이름을 사용하여 동시에 실행할 수 있습니다. 재부팅 후 스택을 다시 복구하려면 재시작 정책을 사용하거나 Compose를 자동으로 실행하는 유닛을 구성해야 하며, 자세한 내용은 부팅 시 Compose 스택 시작하기에서 확인할 수 있습니다.

FAQ

하이픈이 포함된 docker-compose은 무엇으로 대체되었습니까?

Compose V2와 V5는 공백을 포함한 docker compose 명령어로 호출합니다. Linux 환경에서 Compose는 별도로 설치하는 플러그인입니다. Ubuntu 24.04 LTS의 universe 저장소는 여전히 V1 Python 도구를 제공합니다. 공백을 포함한 명령어를 사용할 수 없다면 Docker 공식 저장소를 설정한 뒤 docker-compose-plugin 패키지를 설치하십시오. V2에는 V1에 없던 플래그가 존재하므로, 별칭(alias)을 추가하기보다 기존 스크립트를 공백을 사용하는 형식으로 수정하는 것이 좋습니다.

docker compose restart가 설정 변경 사항을 반영하지 않는 이유는 무엇입니까?

restart는 기존 컨테이너를 생성 당시의 설정으로 중지하고 시작할 뿐, compose.yaml 파일을 다시 읽지 않습니다. 환경 변수, 포트, 볼륨, 이미지 태그를 변경했다면 docker compose up -d을 실행해야 합니다. 이 명령어는 각 서비스를 실행 중인 컨테이너와 비교하여 차이가 있는 컨테이너를 다시 생성합니다. 파일 내용에 변화가 없더라도 교체를 강제하려면 --force-recreate 옵션을 추가하십시오.

서비스를 최신 이미지로 업데이트하려면 어떻게 해야 합니까?

docker compose pull를 실행한 뒤 docker compose up -d을 실행하십시오. pull은 파일에 명시된 각 태그의 현재 이미지를 가져오며, up -d은 이미지 ID가 컨테이너와 일치하지 않는 모든 서비스를 다시 생성합니다. up -d만 단독으로 실행하면 pull 정책을 따릅니다. 기본값인 missing 정책은 캐시된 이미지를 재사용하지만, latest 태그가 붙은 이미지는 항상 새로 가져옵니다.

운영 중인 서버에서 안전하게 실행할 수 있는 정리 명령어는 무엇입니까?

docker system df는 아무것도 삭제하지 않고 디스크 사용량만 보고합니다. docker image prune -a과 docker builder prune은 사용하지 않는 이미지와 빌드 캐시를 제거하므로, 실행 중인 서비스는 계속 작동하며 명명된 볼륨(named volume)은 영향을 받지 않습니다. 위험한 명령어는 docker compose down -v입니다. 이 명령어는 프로젝트에 선언된 외부 볼륨이 아닌 명명된 볼륨과 연결된 익명 볼륨을 확인 절차 없이 삭제합니다. docker volume prune --all는 사용하지 않는 로컬 볼륨(명명된 볼륨 포함)을 삭제하며, y/N 확인 절차를 거치거나 -f 옵션을 사용하면 즉시 삭제합니다. 어떤 데이터가 위험한지 확인하려면 먼저 docker compose config --volumes을 실행하십시오.

전체 스택을 시작하지 않고 단일 명령어를 실행할 수 있습니까?

네, 가능합니다. docker compose run --rm --no-deps web sh는 web 서비스 정의를 기반으로 단일 컨테이너를 시작하며, 의존성 서비스는 건너뛰고 종료 시 컨테이너를 삭제합니다. 컨테이너가 이미 실행 중이라면 exec를 사용하십시오. exec는 실행 중인 컨테이너 내부에서 명령어를 수행하므로 서비스의 실제 상태를 확인할 수 있습니다.

이 글이 도움이 되었나요?