개발자가 알아야 할 도커 필수 명령어 및 활용법 | 실무 CLI 치트시트
이 글의 핵심
개발 현장에서 실제로 매일 쓰는 도커 CLI 명령어를 실전 예제로 정리한 치트시트입니다. docker run 옵션 조합, ps/logs/exec/inspect로 컨테이너 디버깅하는 법, stats로 리소스 모니터링하는 법, prune으로 디스크를 정리하는 법, 태그·레지스트리 push/pull, 빌드 캐시를 활용하는 법, 자주 쓰는 alias까지 복사해서 바로 쓸 수 있게 구성했습니다.
들어가며
도커를 처음 배울 때는 docker run hello-world 한 줄로 시작하지만, 실무에 투입되고 나면 이야기가 달라집니다. 컨테이너가 이유 없이 재시작되고, 디스크 용량이 어느새 가득 차 있고, 이미지를 빌드할 때마다 5분씩 기다려야 하고, 어떤 컨테이너가 CPU를 얼마나 쓰는지 도무지 알 수 없는 상황을 마주하게 됩니다. 이런 문제 대부분은 어려운 개념이 아니라 CLI 명령어를 얼마나 손에 익혔는지에서 갈립니다.
이 글은 도커의 개념이나 Dockerfile 작성법을 처음부터 설명하는 입문 가이드가 아닙니다. pkglog.com에는 이미 Docker 완벽 가이드, Docker Compose 실전 가이드, 멀티스테이지 빌드 최적화 가이드, Docker 네트워킹 심화 가이드가 있고, 각각 개념·Compose 문법·이미지 최적화·네트워크 내부 동작을 깊이 다룹니다. 이 글은 그 글들이 다루지 않는 영역, 즉 터미널에서 매일 타이핑하는 명령어 자체에 집중합니다. docker run의 실전 옵션 조합, ps/logs/exec/inspect로 컨테이너를 디버깅하는 법, stats로 리소스를 모니터링하는 법, 쌓여가는 이미지·볼륨·네트워크를 정리하는 법, 레지스트리에 이미지를 올리고 내리는 법, 그리고 매일 타이핑하는 수고를 줄여주는 alias까지 정리했습니다.
대상 독자
- 도커 기본 개념은 알지만 CLI 명령어 조합이 손에 익지 않은 개발자
- 로컬에서 컨테이너를 자주 실행하지만 매번 옵션을 검색해서 쓰는 개발자
- 컨테이너 디버깅·리소스 모니터링·디스크 정리를 체계적으로 익히고 싶은 개발자
이 글을 읽으면
docker run의 실전 옵션 조합을 상황별로 바로 적용할 수 있습니다.- 컨테이너 문제를
ps→logs→exec→inspect순서로 빠르게 진단할 수 있습니다. prune계열 명령어로 디스크 공간을 안전하게 회수할 수 있습니다.- 빌드 캐시를 활용해 이미지 빌드 시간을 단축할 수 있습니다.
1. docker run 실전 옵션 조합
왜 옵션 조합이 중요한가
docker run은 도커에서 가장 많이 쓰는 명령어이면서, 동시에 옵션이 가장 많은 명령어이기도 합니다. 옵션 하나하나는 단순하지만, 실무에서는 여러 옵션을 조합해야 원하는 동작을 얻을 수 있습니다. 예를 들어 백그라운드로 띄우면서 포트를 열고, 이름을 지정하고, 환경변수를 주입하고, 볼륨을 마운트하고, 재시작 정책까지 지정하는 식입니다. 옵션을 하나씩 검색해서 붙이다 보면 시간이 오래 걸리므로, 자주 쓰는 조합을 아예 패턴으로 외워두는 것이 효율적입니다.
# 가장 흔한 실전 조합: 백그라운드 + 포트 + 이름 + 환경변수 + 볼륨 + 재시작 정책
docker run -d \
--name my-api \
-p 8080:3000 \
-e NODE_ENV=production \
-e DATABASE_URL="postgres://user:pass@db:5432/app" \
-v my-api-data:/app/data \
--restart unless-stopped \
my-api:1.0
각 옵션의 의미를 짚어보면, -d는 컨테이너를 백그라운드(detached)로 실행해 터미널을 점유하지 않게 합니다. --name으로 컨테이너 이름을 지정해두면 이후 docker logs my-api처럼 무작위로 생성되는 해시 대신 사람이 읽기 쉬운 이름으로 참조할 수 있습니다. -p 호스트포트:컨테이너포트는 포트 매핑이며, 순서를 헷갈려서 반대로 쓰는 실수가 흔하니 “왼쪽이 내 컴퓨터, 오른쪽이 컨테이너 안”으로 외워두면 편합니다. -e는 환경변수 주입이고, -v는 볼륨 마운트로 컨테이너가 삭제되어도 데이터를 보존합니다. --restart unless-stopped는 컨테이너가 비정상 종료되었을 때 자동으로 재시작하되, 사용자가 명시적으로 멈춘 경우에는 재시작하지 않는 정책입니다.
자주 쓰는 추가 옵션들
# 컨테이너 종료 시 자동 삭제 (일회성 테스트 실행)
docker run --rm -it ubuntu:22.04 bash
# 리소스 제한 (메모리 512MB, CPU 1개)
docker run -d --memory=512m --cpus=1 my-worker:latest
# 로컬 소스코드를 실시간으로 반영하고 싶을 때 (개발 환경 바인드 마운트)
docker run -d -p 3000:3000 -v $(pwd):/app -v /app/node_modules my-app:dev
# 호스트 네트워크를 그대로 사용 (포트 매핑 없이 호스트와 동일한 네트워크 스택)
docker run --network host my-tool:latest
# 헬스체크 결과를 컨테이너 실행 시점부터 확인하고 싶을 때
docker run -d --health-cmd="curl -f http://localhost:3000/health || exit 1" \
--health-interval=10s --health-retries=3 my-api:1.0
--rm은 컨테이너가 멈추는 즉시 자동으로 삭제해주는 옵션으로, 잠깐 도구를 테스트하거나 셸을 열어볼 때 매우 유용합니다. --rm을 안 쓰면 멈춘 컨테이너가 계속 쌓여서 나중에 docker ps -a를 열었을 때 이름 모를 컨테이너가 수십 개씩 보이는 상황이 벌어집니다. --memory와 --cpus는 컨테이너가 호스트 리소스를 과도하게 점유하지 못하도록 제한하는 옵션으로, 로컬에서 여러 컨테이너를 동시에 띄울 때 팬이 갑자기 돌아가는 상황을 방지하는 데 도움이 됩니다. -v $(pwd):/app 조합은 로컬 디렉터리를 컨테이너 안에 그대로 마운트해서 코드를 수정하면 즉시 컨테이너에 반영되게 하는 패턴이며, 뒤에 -v /app/node_modules를 추가로 붙이면 호스트의 node_modules가 컨테이너 안의 것을 덮어쓰지 않도록 분리할 수 있습니다. 이 트릭을 모르면 “로컬에서는 되는데 컨테이너 안에서는 모듈을 못 찾는다”는 흔한 문제에 부딪히게 됩니다.
언제 쓰는가: --rm은 일회성 명령 실행이나 도구 테스트에, --memory/--cpus는 로컬 리소스 관리와 프로덕션 격리에, 바인드 마운트 조합은 로컬 개발 환경 핫 리로드에 각각 적합합니다. --network host는 성능이 중요하거나 포트 매핑 자체가 번거로운 리눅스 환경에서 주로 쓰이며, macOS·Windows의 Docker Desktop에서는 동작이 다르게 취급되므로(가상화 계층 때문에 host 네트워크가 완전히 동일하게 동작하지 않음) 주의가 필요합니다.
2. ps·logs·exec·inspect로 컨테이너 디버깅하기
컨테이너 상태부터 확인한다
문제가 생겼을 때 가장 먼저 할 일은 컨테이너가 지금 어떤 상태인지 파악하는 것입니다. docker ps는 실행 중인 컨테이너만 보여주고, -a 옵션을 붙이면 멈춘 컨테이너까지 모두 보여줍니다.
# 실행 중인 컨테이너만
docker ps
# 멈춘 것까지 전부, 원하는 컬럼만 보기 좋게
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
STATUS 컬럼에 Restarting (1) 5 seconds ago처럼 표시되면 컨테이너가 계속 죽었다가 재시작되고 있다는 뜻이고, Exited (137)처럼 표시되면 이미 종료된 상태입니다. 종료 코드 137은 대체로 SIGKILL을 의미하며, 메모리 한도를 초과해 OOM Killer가 강제 종료시켰을 가능성이 높은 신호입니다. 이처럼 docker ps의 STATUS 컬럼만 잘 읽어도 문제의 큰 방향을 잡을 수 있습니다.
로그로 원인을 좁힌다
# 최근 로그 100줄
docker logs --tail=100 my-api
# 실시간으로 로그를 계속 따라가기
docker logs -f my-api
# 타임스탬프를 포함해서 확인 (언제부터 이상해졌는지 파악할 때 유용)
docker logs -f --since=10m --timestamps my-api
docker logs는 컨테이너의 표준 출력(stdout)과 표준 에러(stderr)를 그대로 보여줍니다. -f(follow)는 tail -f처럼 새 로그가 쌓일 때마다 실시간으로 출력하며, --since는 특정 시점 이후의 로그만 필터링합니다. 애플리케이션이 특정 시각부터 이상해졌다는 것을 알고 있다면 --since=2026-09-09T10:00:00처럼 정확한 시각을 지정해 노이즈를 줄일 수 있습니다. 로그에서 스택 트레이스나 명확한 에러 메시지를 찾았다면 문제의 8할은 해결된 셈입니다.
컨테이너 내부로 직접 들어가기
로그만으로 원인을 알 수 없을 때는 컨테이너 내부를 직접 들여다봐야 합니다.
# 실행 중인 컨테이너에 셸로 접속
docker exec -it my-api sh
# (bash가 설치된 이미지라면)
docker exec -it my-api bash
# 셸을 열지 않고 명령 하나만 즉시 실행
docker exec my-api env
docker exec my-api ls -al /app/dist
# 특정 사용자 권한으로 접속 (권한 문제를 재현할 때)
docker exec -it -u root my-api sh
docker exec는 이미 실행 중인 컨테이너 안에서 새 프로세스를 실행합니다. -it는 인터랙티브 터미널을 여는 조합으로, -i는 표준 입력을 유지하고 -t는 pseudo-TTY를 할당합니다. 이 두 옵션을 함께 써야 셸 프롬프트가 정상적으로 뜹니다. Alpine 기반 이미지에는 bash가 없고 sh(ash)만 있는 경우가 많으니, bash: not found 에러가 나면 sh로 바꿔서 시도하면 됩니다. 컨테이너 안에 들어가서 ls로 파일이 실제로 있는지, env로 환경변수가 의도한 대로 주입되었는지, cat /etc/resolv.conf로 DNS 설정이 맞는지 등을 직접 확인하는 것은 “빌드는 성공했는데 런타임에 파일을 못 찾는다” 같은 문제를 잡는 가장 확실한 방법입니다.
주의할 점은, docker exec는 컨테이너가 이미 실행 중이어야 동작한다는 것입니다. 컨테이너가 계속 재시작되며 죽어서 exec를 걸 틈조차 없다면, 같은 이미지로 새 컨테이너를 하나 더 띄우되 시작 커맨드를 셸로 덮어써서 진입하는 방법이 유용합니다.
# 이미지는 같지만 원래 CMD/ENTRYPOINT 대신 셸로 진입해서 파일 구조부터 확인
docker run --rm -it --entrypoint sh my-api:1.0
inspect로 메타데이터 전체를 확인한다
# 컨테이너의 모든 설정을 JSON으로 출력
docker inspect my-api
# 필요한 필드만 뽑아보기 (Go 템플릿 문법)
docker inspect -f '{{.State.ExitCode}}' my-api
docker inspect -f '{{.State.OOMKilled}}' my-api
docker inspect -f '{{json .NetworkSettings.Networks}}' my-api | jq
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' my-api
docker inspect는 컨테이너(또는 이미지, 네트워크, 볼륨)의 모든 메타데이터를 JSON으로 덤프합니다. 정보량이 매우 많기 때문에 그냥 실행하면 화면을 가득 채우게 되는데, -f(또는 --format) 옵션에 Go 템플릿 문법을 넣으면 필요한 필드만 뽑아낼 수 있습니다. 특히 State.OOMKilled가 true로 나온다면 메모리 부족으로 강제 종료된 것이 확실하므로 바로 --memory 한도를 늘리거나 애플리케이션의 메모리 누수를 의심하면 됩니다. NetworkSettings.Networks는 컨테이너가 어떤 네트워크에 붙어 있고 IP가 무엇인지 보여주므로, 컨테이너 간 통신이 안 될 때 같은 네트워크에 있는지 확인하는 용도로 씁니다. 네트워크 내부 동작 자체(브리지, iptables, DNS 해석 순서 등)를 더 깊이 알고 싶다면 별도로 작성된 Docker 네트워킹 심화 가이드를 참고하시기 바랍니다.
파일 시스템 변경 사항 확인하기
# 이미지 대비 컨테이너 내부에서 무엇이 바뀌었는지 확인
docker diff my-api
docker diff는 이미지가 만들어진 시점 대비 컨테이너 안에서 추가(A)·변경(C)·삭제(D)된 파일 목록을 보여줍니다. 예상치 못한 파일이 런타임에 계속 쌓이고 있는지, 혹은 애플리케이션이 컨테이너 안의 파일을 의도치 않게 수정하고 있는지 확인할 때 유용합니다. 자주 쓰는 명령은 아니지만, “컨테이너를 재시작하면 데이터가 사라진다”는 문제를 조사할 때 이 명령으로 실제 어떤 경로가 변경되고 있는지 확인해두면 볼륨 마운트가 필요한 지점을 정확히 찾을 수 있습니다.
3. docker stats로 리소스 모니터링하기
컨테이너가 여러 개 떠 있을 때 어떤 컨테이너가 CPU나 메모리를 많이 쓰는지 한눈에 보고 싶다면 docker stats가 가장 빠른 방법입니다.
# 실행 중인 모든 컨테이너의 실시간 리소스 사용량 (top처럼 계속 갱신됨)
docker stats
# 한 번만 스냅샷으로 출력하고 종료 (스크립트에서 활용하기 좋음)
docker stats --no-stream
# 특정 컨테이너만 지정
docker stats my-api my-db
# 원하는 컬럼만 포맷팅
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
docker stats는 리눅스의 top 명령어와 비슷하게 CPU 사용률, 메모리 사용량과 한도, 네트워크 I/O, 블록 I/O를 실시간으로 갱신해서 보여줍니다. 기본적으로는 화면이 계속 갱신되는 인터랙티브 모드로 동작하는데, CI 스크립트나 로그로 남기고 싶을 때는 --no-stream을 붙여서 한 번만 스냅샷을 찍고 종료하게 만들 수 있습니다. MEM USAGE / LIMIT 컬럼이 한도에 가깝게 붙어 있다면 곧 OOM으로 죽을 가능성이 높다는 신호이므로, docker run 단계에서 지정한 --memory 값을 조정하거나 애플리케이션 코드의 메모리 사용 패턴을 점검해야 합니다.
디스크 공간이 궁금할 때는 docker system df를 함께 씁니다.
# 이미지/컨테이너/볼륨/빌드 캐시가 디스크를 얼마나 차지하는지 요약
docker system df
# 항목별 상세 내역까지
docker system df -v
docker system df는 df 명령어처럼 이미지, 컨테이너, 로컬 볼륨, 빌드 캐시가 각각 얼마나 디스크를 점유하고 있는지, 그중 실제로 사용 중인(active) 용량은 얼마인지를 요약해 보여줍니다. “디스크가 꽉 찼다”는 알림을 받았을 때 이 명령을 가장 먼저 실행하면, 다음 절에서 다룰 prune 명령 중 어떤 것을 써야 할지 판단할 수 있습니다.
4. 이미지 관리: 태그·빌드 캐시·레지스트리
이미지 목록과 태그
# 로컬에 있는 이미지 목록
docker images
docker image ls
# 특정 저장소 이름으로 필터링
docker images my-api
# 이미지에 새 태그 붙이기 (레지스트리 주소 포함)
docker tag my-api:1.0 registry.example.com/team/my-api:1.0
docker tag my-api:1.0 registry.example.com/team/my-api:latest
docker tag는 이미지를 복제하는 것이 아니라 같은 이미지에 별칭을 하나 더 붙이는 작업입니다. 그래서 실행이 즉시 끝나고 디스크 용량도 추가로 차지하지 않습니다. 사설 레지스트리나 Docker Hub, ECR, GCR 같은 클라우드 레지스트리에 이미지를 올리려면 반드시 레지스트리주소/조직또는사용자/이미지명:태그 형식으로 태그를 다시 붙여야 합니다. latest 태그는 편의를 위한 관례일 뿐 자동으로 “최신 버전”을 가리키지 않으므로, 프로덕션 배포에서는 latest 대신 커밋 해시나 시맨틱 버전(예: 1.4.2)을 태그로 사용하는 것이 사고를 예방하는 데 훨씬 안전합니다. 배포 후 문제가 생겼을 때 정확히 어떤 이미지가 떠 있었는지 추적할 수 없다면 롤백 자체가 어려워지기 때문입니다.
레지스트리에 push·pull
# 레지스트리 로그인
docker login registry.example.com
# 이미지 업로드
docker push registry.example.com/team/my-api:1.0
# 다른 서버에서 이미지 내려받기
docker pull registry.example.com/team/my-api:1.0
# 로그아웃 (CI 러너처럼 공유 환경에서는 습관적으로 실행)
docker logout registry.example.com
push와 pull은 각각 로컬 이미지를 레지스트리로 업로드하고, 레지스트리에서 로컬로 다운로드하는 명령입니다. 둘 다 이미지의 레이어 단위로 동작하기 때문에, 이전에 이미 올린 적 있는 레이어는 다시 전송하지 않고 건너뜁니다. 그래서 베이스 이미지를 통일하고 자주 바뀌지 않는 레이어를 앞쪽에 배치해두면 push/pull 속도가 눈에 띄게 빨라집니다. CI 파이프라인에서 매 빌드마다 몇 분씩 이미지 업로드에 시간을 쓰고 있다면, 레이어 구성 순서를 점검해볼 가치가 있습니다. 이 레이어 순서 최적화와 관련된 Dockerfile 작성 전략은 멀티스테이지 빌드 최적화 가이드에서 더 깊이 다루고 있습니다.
빌드 캐시를 실전에서 활용하기
# 기본 빌드
docker build -t my-api:1.0 .
# 캐시를 완전히 무시하고 처음부터 다시 빌드 (의심스러울 때 확실히 검증)
docker build --no-cache -t my-api:1.0 .
# 특정 스테이지까지만 빌드 (멀티스테이지에서 build 스테이지만 디버깅할 때)
docker build --target build -t my-api:debug .
# 원격 캐시를 활용해 CI에서도 캐시 재사용 (BuildKit)
docker build --cache-from=registry.example.com/team/my-api:latest \
-t my-api:1.0 .
# 빌드 시점의 변수 주입
docker build --build-arg NODE_ENV=production -t my-api:1.0 .
도커는 Dockerfile의 각 명령을 레이어로 캐시하고, 명령과 그 이전까지의 컨텍스트가 바뀌지 않았다면 캐시를 그대로 재사용합니다. 문제는 캐시가 예상과 다르게 무효화되는 경우인데, 대표적인 원인은 소스 코드 전체를 먼저 COPY한 뒤 의존성을 설치하는 순서입니다. 이렇게 작성하면 코드를 한 줄만 고쳐도 그 아래의 의존성 설치 레이어까지 전부 캐시가 깨져서 다시 실행됩니다. package.json이나 go.mod 같은 의존성 정의 파일만 먼저 COPY하고 의존성을 설치한 다음, 그 뒤에 나머지 소스 코드를 COPY하는 순서로 바꾸면 코드만 바뀌었을 때는 의존성 설치 레이어의 캐시가 유지됩니다. --target은 멀티스테이지 Dockerfile에서 특정 스테이지까지만 빌드하고 멈추는 옵션으로, 최종 이미지가 아니라 빌드 중간 단계의 파일 구조를 확인하고 싶을 때 유용합니다. CI 환경처럼 로컬 캐시가 매번 초기화되는 곳에서는 --cache-from으로 이전에 push된 이미지를 캐시 소스로 지정해 빌드 시간을 단축할 수 있습니다.
이미지를 파일로 저장하고 불러오기
# 이미지를 tar 파일로 내보내기 (오프라인 서버로 옮길 때)
docker save -o my-api.tar my-api:1.0
# tar 파일을 이미지로 불러오기
docker load -i my-api.tar
# 실행 중인 컨테이너를 새 이미지로 스냅샷 (디버깅 목적, 프로덕션 배포용 X)
docker commit my-api my-api:debug-snapshot
save/load는 레지스트리 없이 이미지를 파일 형태로 주고받을 때 씁니다. 인터넷이 차단된 폐쇄망 서버에 이미지를 옮기거나, 동료에게 빠르게 이미지를 전달할 때 유용합니다. docker commit은 실행 중인 컨테이너의 현재 상태를 그대로 이미지로 굳히는 명령인데, 이는 재현 가능성을 해치기 때문에 어디까지나 디버깅 스냅샷 용도로만 쓰고 프로덕션 이미지를 만드는 정식 방법으로는 사용하지 않는 것이 원칙입니다. 정식 이미지는 항상 Dockerfile을 통해 재현 가능하게 빌드해야 나중에 같은 이미지를 다시 만들 수 있습니다.
5. prune 명령으로 디스크 정리하기
왜 디스크가 계속 찰까
도커는 컨테이너를 멈춰도 기본적으로 삭제하지 않고, 빌드할 때마다 이전 레이어를 캐시로 남겨둡니다. 이런 설계는 재실행 속도와 빌드 캐시 재사용 측면에서는 유리하지만, 시간이 지날수록 로컬 디스크에 쓰지 않는 이미지·컨테이너·볼륨·네트워크가 쌓이는 부작용이 있습니다. 특히 CI 러너처럼 빌드를 반복적으로 수행하는 환경에서는 디스크가 며칠 만에 가득 차는 경우도 흔합니다. docker system df로 현재 상태를 확인한 뒤, 아래 명령으로 필요한 범위만 골라 정리하면 됩니다.
# 멈춘 컨테이너만 정리
docker container prune
# 사용하지 않는(어떤 컨테이너도 참조하지 않는) 이미지만 정리
docker image prune
# 사용하지 않는 이미지를 태그 여부와 상관없이 모두 정리 (더 공격적)
docker image prune -a
# 사용하지 않는 볼륨 정리 (주의: 데이터 유실 가능)
docker volume prune
# 사용하지 않는 네트워크 정리
docker network prune
# 컨테이너 + 이미지 + 네트워크 + 빌드 캐시를 한 번에 (볼륨은 기본 제외)
docker system prune
# 볼륨까지 포함해서 전부 정리 (가장 공격적인 옵션)
docker system prune -a --volumes
각 명령의 범위를 정확히 이해하는 것이 중요합니다. docker image prune은 기본적으로 어떤 컨테이너도 참조하지 않는 댕글링 이미지(태그가 없는, <none>으로 표시되는 이미지)만 지웁니다. 반면 -a 옵션을 붙이면 태그가 붙어 있더라도 현재 실행 중인 컨테이너가 쓰고 있지 않은 이미지는 전부 삭제 대상이 되므로 훨씬 공격적입니다. docker volume prune은 볼륨을 지우기 때문에 데이터베이스 데이터처럼 볼륨에 저장된 실제 데이터가 있다면 실행 전에 반드시 어떤 볼륨이 지워지는지 docker volume ls로 확인해야 합니다. 실무에서는 docker system prune -a --volumes처럼 가장 넓은 범위를 한 번에 지우는 명령을 습관적으로 별칭(alias)에 등록해두고 정기적으로 실행하는 경우가 많은데, 운영 서버에서는 이 명령을 실수로 실행하지 않도록 로컬 개발 환경에서만 쓰는 것이 안전합니다.
빌드 캐시만 따로 정리하고 싶을 때는 다음 명령을 씁니다.
# BuildKit 빌드 캐시만 정리
docker builder prune
# 특정 기간보다 오래된 캐시만 정리
docker builder prune --filter until=168h
docker builder prune은 BuildKit이 관리하는 빌드 캐시를 대상으로 하며, --filter until=으로 특정 시간 이상 지난 캐시만 골라서 지울 수 있습니다. 매번 전체를 지우기보다 오래된 캐시만 주기적으로 정리하면 최근 빌드의 캐시 이점은 유지하면서 디스크 공간도 회수할 수 있습니다.
6. docker cp로 파일 주고받기
# 컨테이너 안의 파일을 호스트로 복사
docker cp my-api:/app/logs/error.log ./error.log
# 호스트 파일을 컨테이너 안으로 복사
docker cp ./config.json my-api:/app/config.json
# 디렉터리 전체 복사도 가능
docker cp my-api:/app/dist ./dist-backup
docker cp는 컨테이너와 호스트 사이에 파일을 복사하는 명령으로, 볼륨을 마운트하지 않은 컨테이너에서 급하게 로그 파일 하나를 꺼내오거나, 설정 파일 하나를 컨테이너 안에 넣어 재시작 없이 테스트해보고 싶을 때 유용합니다. 다만 이 명령으로 넣은 파일은 컨테이너가 삭제되면 함께 사라지므로 어디까지나 임시방편이며, 지속적으로 필요한 설정이라면 처음부터 -v로 볼륨이나 바인드 마운트를 구성하는 것이 올바른 방법입니다. 운영 환경 컨테이너에 docker cp로 파일을 몰래 밀어넣고 재현 과정을 기록하지 않으면, 나중에 컨테이너를 재배포했을 때 “분명 어제까지 되던 설정이 사라졌다”는 문제로 이어지기 쉽습니다.
7. 자주 쓰는 alias와 셸 설정
매일 반복하는 명령은 shell의 .bashrc나 .zshrc에 alias로 등록해두면 타이핑 시간을 크게 줄일 수 있습니다.
# ~/.zshrc 또는 ~/.bashrc에 추가
# 실행 중인 컨테이너를 보기 좋게
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
# 멈춘 것까지 전부
alias dpsa='docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
# 가장 최근 컨테이너에 바로 로그 붙기
alias dlog='docker logs -f --tail=100'
# 컨테이너 이름 하나로 셸 접속 (fallback으로 sh 시도)
dsh() {
docker exec -it "$1" bash 2>/dev/null || docker exec -it "$1" sh
}
# 리소스 사용량 스냅샷
alias dstat='docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"'
# 멈춘 컨테이너 + 댕글링 이미지 + 미사용 네트워크 한 번에 정리
alias dclean='docker system prune -f'
# 모든 컨테이너 강제 정지 (개발 환경 초기화용)
alias dstopall='docker stop $(docker ps -aq)'
이런 alias는 팀 전체가 같은 것을 쓸 필요는 없지만, 자신이 자주 반복하는 패턴을 발견했다면 바로 함수나 alias로 만들어두는 습관을 들이면 좋습니다. 특히 dsh처럼 bash가 있으면 bash로, 없으면 sh로 자동 전환되는 함수는 Alpine 기반 이미지와 Debian/Ubuntu 기반 이미지를 오가며 작업할 때 매번 어떤 셸이 있는지 기억할 필요가 없어 편리합니다. Docker Compose를 쓰는 프로젝트라면 docker compose logs -f, docker compose exec app sh 같은 명령에도 동일한 방식으로 alias를 만들 수 있으며, 자세한 Compose 명령 활용은 Docker Compose 실전 가이드를 참고하시기 바랍니다.
8. 트러블슈팅: 자주 겪는 상황과 해결 순서
컨테이너가 계속 재시작된다
docker ps # STATUS가 Restarting인지 확인
docker logs --tail=100 <이름> # 마지막 에러 메시지 확인
docker inspect -f '{{.State.OOMKilled}}' <이름> # 메모리 부족 여부
docker inspect -f '{{.State.ExitCode}}' <이름> # 종료 코드 확인
ExitCode가 0이면 정상 종료인데 재시작 정책 때문에 계속 뜨는 것이고, 1이면 애플리케이션 자체 에러, 137이면 대체로 SIGKILL(메모리 부족 또는 강제 종료)입니다. 로그에 아무 단서가 없다면 --entrypoint sh로 진입해 애플리케이션을 수동으로 한 번 실행해보면 표준 출력에 찍히지 않았던 초기화 에러를 잡을 수 있는 경우가 많습니다.
포트가 이미 사용 중이라는 에러가 난다
# 이미 8080 포트를 쓰고 있는 컨테이너 찾기
docker ps --filter "publish=8080"
# 호스트에서 어떤 프로세스가 포트를 물고 있는지 확인 (리눅스/맥)
lsof -i :8080
같은 포트로 여러 컨테이너를 띄우려다 실패하는 경우가 흔합니다. docker ps --filter "publish=포트번호"로 이미 그 포트를 점유한 컨테이너를 바로 찾을 수 있으며, 도커 밖의 프로세스가 포트를 쓰고 있는지까지 확인하려면 OS의 lsof나 netstat을 함께 씁니다. 이 부분은 도커에 국한되지 않는 일반적인 네트워크 디버깅 영역이라 리눅스·맥 명령어 실전 가이드에서 더 폭넓게 다루고 있습니다.
이미지를 빌드했는데 코드 변경이 반영되지 않는다
캐시가 잘못 재사용되고 있을 가능성이 높습니다. docker build --no-cache로 처음부터 다시 빌드해서 같은 문제가 재현되는지 먼저 확인하고, 재현되지 않는다면 Dockerfile의 COPY 순서를 점검합니다. 재현된다면 캐시 문제가 아니라 애플리케이션 자체나 실행 중인 컨테이너가 이전 이미지를 그대로 쓰고 있는 경우이니 docker ps로 실제 실행 중인 이미지 태그를 확인해야 합니다.
디스크 공간이 부족하다는 에러가 난다
docker system df # 무엇이 공간을 차지하는지 확인
docker system prune -a --volumes # 필요한 데이터가 없는지 확인 후 실행
앞서 다룬 대로 docker system df로 원인을 먼저 파악하고, 볼륨에 중요한 데이터가 없는지 확인한 뒤 정리 명령을 실행하는 순서를 지키는 것이 중요합니다. 급한 마음에 확인 없이 prune부터 실행했다가 필요한 볼륨까지 날리는 사고는 실무에서 실제로 자주 발생합니다.
마치며
도커를 능숙하게 다룬다는 것은 결국 몇 가지 명령어 조합을 몸에 익히는 일에 가깝습니다. docker run의 옵션 조합으로 원하는 형태의 컨테이너를 한 번에 띄우고, ps → logs → exec → inspect 순서로 문제를 좁혀가고, stats와 system df로 리소스를 관찰하고, prune으로 정기적으로 디스크를 정리하는 루틴만 몸에 익어도 하루하루의 개발 속도가 눈에 띄게 빨라집니다. 이 글에서 다룬 명령어들은 북마크해두고 필요할 때마다 다시 찾아보시길 권합니다. Dockerfile 최적화, Compose 구성, 네트워크 내부 동작처럼 더 깊은 주제는 이 글에서 링크한 다른 가이드들을 이어서 참고하시기 바랍니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Docker 완벽 가이드 2026 | 컨테이너 기반 개발 환경 구축
- Docker Compose 실전 가이드 | 멀티 컨테이너·네트워크·볼륨·프로덕션 배포
- Docker 멀티스테이지 빌드 최적화 | 이미지 크기 90% 감소 실전 가이드
- Docker 네트워킹 심화 가이드 — CNI·veth·브리지·iptables·서비스 디스커버리
- 개발자를 위한 리눅스·맥 명령어 실전 가이드 | 네트워크·파일·프로세스·디버깅