BlueByte
port is already allocatedFixed

Docker: Bind failed — port is already allocated

작성 Haneul Seo2026년 8월 13일 업데이트10 min

안녕하세요, BlueByte입니다. docker run -p 8080:80 ...(또는 docker compose up)을 실행했는데 컨테이너 대신 port is already allocated로 끝나는 빨간 줄이 뜹니다. 깨진 건 아무것도 없습니다 — Docker가 커널에 컨테이너용 호스트 포트 8080을 잡아 달라고 요청했는데 이미 무언가가 그 포트를 쥐고 있는 것뿐입니다. 오늘은 메시지 변형, 포트가 왜 이미 잡혀 있는지, 정확히 무엇이 쥐고 있는지 찾는 법, 포트를 비우거나 컨테이너를 옮기는 법, 그리고 재발을 막는 법까지 하나씩 짚어보겠습니다.

port is already allocated가 실제로 뜻하는 것

-p HOST_PORT:CONTAINER_PORT로 포트를 게시하면 Docker는 트래픽이 컨테이너에 닿도록 그 호스트 포트를 예약합니다. 호스트 포트가 사용 중이면 컨테이너가 시작되기도 전에 실행이 실패합니다. 최신 Docker(29.6에서 확인)는 누가 포트를 쥐고 있느냐에 따라 데몬이 두 가지 형태 중 하나를 출력합니다. 다른 컨테이너가 이미 그 포트를 게시 중이면:

docker: Error response from daemon: failed to set up container networking:
driver failed programming external connectivity on endpoint web (...):
Bind for 0.0.0.0:8080 failed: port is already allocated

Docker가 아닌 호스트 프로세스가 쥐고 있으면:

docker: Error response from daemon: failed to set up container networking:
driver failed programming external connectivity on endpoint web (...):
failed to bind host port 0.0.0.0:8080/tcp: address already in use

userland proxy를 쓰던 옛 Docker는 두 번째를 Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use로 표현했습니다. 어느 쪽이든 뜻은 같습니다 — 호스트 포트가 비어 있지 않다는 것입니다.

호스트 포트가 이미 잡혀 있는 이유

bind는 몇 가지 구체적 이유 중 하나로 실패합니다:

  • 실행 중인 다른 컨테이너가 이미 그 호스트 포트를 게시 중.
  • --restart 정책이 붙은 정지됐지만 제거되지 않은 컨테이너가 되살아나 포트를 다시 잡음, 또는 크래시한 실행이 매핑을 남김.
  • 로컬 Postgres, 다른 개발 서버, 잊고 있던 node나 python 같은 평범한 호스트 프로세스가 리스닝 중.
  • 하나의 compose.yaml에서 같은 호스트 포트를 두 번 게시(두 서비스가 모두 8080: 매핑).
  • 스크립트 안 두 개의 docker run이 모두 8080을 요청.

무엇이 포트를 쥐고 있는지 정확히 찾기

추측하지 말고 물어보세요. 먼저 컨테이너가 쥐고 있는지 확인합니다:

docker ps --filter publish=8080 --format '{{.Names}}\t{{.Ports}}'
api   5432/tcp, 0.0.0.0:8080->80/tcp

컨테이너 이름이 나오면 그게 범인입니다. 한 컨테이너의 매핑을 읽으려면:

docker port api
# 80/tcp -> 0.0.0.0:8080

일치하는 컨테이너가 없으면 호스트 프로세스입니다. Linux/macOS에서는 ss(또는 lsof)가 소유자와 PID를 알려줍니다:

ss -ltnp | grep :8080
# LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* users:(("python3",pid=693238,fd=3))

Windows에서는 netstat가 같은 일을 합니다:

netstat -ano | findstr :8080

마지막 열이 PID이고, 그게 처리해야 할 대상입니다.

macOS에서는 lsof가 같은 답을 더 빨리 줍니다:

lsof -nP -iTCP:8080 -sTCP:LISTEN

어느 도구를 쓰든 목표는 같습니다: 무언가를 건드리기 전에 "무언가가 쥐고 있다"를 이름과 PID로 바꾸는 것입니다.

포트를 비우거나 컨테이너를 옮기기

찾은 것에 맞는 해결을 고르세요:

컨테이너가 쥐고 있음 — 정지 후 제거하거나 스택 전체를 내립니다:

docker stop api && docker rm api
# 또는 Compose 프로젝트 전체:
docker compose down

호스트 프로세스가 쥐고 있음 — 그 프로세스나 서비스를 멈춥니다. ss/netstat에서 얻은 PID로:

kill 693238            # Linux/macOS
taskkill /PID 693238 /F   # Windows

8080을 두고 다툴 필요가 없을 때 — 다른 호스트 포트로 게시하거나(컨테이너 포트는 그대로), 한 인터페이스에만 바인딩하거나, -P로 Docker가 빈 포트를 고르게 하세요:

docker run -p 8081:80 nginx              # 다른 호스트 포트
docker run -p 127.0.0.1:8080:80 nginx    # loopback 전용
docker run -P nginx                       # 무작위 호스트 포트; docker port로 확인

Windows에서는 아무도 리스닝하지 않아도 예약된 범위가 bind를 막습니다

Windows에는 찾을 프로세스가 없는 원인이 있습니다. NAT 서비스(winnat)와 동적 포트 범위가 포트 블록 전체를 예약할 수 있어, netstat에 그 포트에 아무것도 없어도 Docker의 bind가 거부됩니다. 메시지는 보통 Error response from daemon: ports are not available: exposing port TCP 0.0.0.0:8080 -> ...: bind: An attempt was made to access a socket in a way forbidden by its access permissions로 뜹니다 — "이미 사용 중"이 아니라 Winsock 오류 10013입니다. 유령 프로세스를 쫓기 전에 예약된 범위를 먼저 나열하세요:

netsh interface ipv4 show excludedportrange protocol=tcp

호스트 포트가 나열된 범위 안에 있으면 그게 원인의 전부입니다. 모든 범위 밖의 포트를 고르거나, NAT 서비스를 껐다 켜 예약을 풀어 주세요(Docker Desktop 재시작도 같은 리셋을 합니다):

net stop winnat
net start winnat

아무것도 포트를 "쓰고" 있지 않은데 bind가 실패하는 유일한 경우입니다 — Windows에서 docker ps와 netstat가 모두 비어 있으면 excluded 범위부터 확인하세요.

실제 사례: 크래시한 컨테이너가 아직 8080을 쥐고 있음

docker compose up을 했는데 부팅 중 죽었고, 설정을 고쳐 다시 실행했더니 Bind for 0.0.0.0:8080 failed: port is already allocated가 뜹니다. docker ps에는 아무것도 없어 호스트 프로세스처럼 보입니다. 그런데 docker ps -a를 보면 이전 컨테이너가 restart: unless-stopped로 Exited 상태였고, 두 명령 사이에 되살아나 포트를 다시 잡은 것입니다. docker compose down(또 한 번의 up이 아니라)이 그 컨테이너를 제거하고 8080을 풀어 주며, 다음 up은 깨끗하게 bind됩니다. 교훈: docker ps에 보이지 않는 매핑도 docker ps -a에서는 여전히 컨테이너일 수 있습니다.

컨테이너가 실제로 포트를 잡았는지 확인

고친 뒤 매핑이 존재하고 응답하는지 봅니다:

docker ps --filter publish=8080
# api ... 0.0.0.0:8080->80/tcp   Up 6 seconds
curl -sSf http://localhost:8080/ >/dev/null && echo ok

docker ps에 0.0.0.0:8080-> 행이 보이고 curl이 2xx/3xx를 받으면 포트가 bind됐고 트래픽이 컨테이너에 닿는 것입니다.

충돌이 다시 일어나지 않게 하기

스택마다 고유한 호스트 포트를 주고 적어 두세요 — compose.yaml이 단일 진실 소스라, 그 안의 중복된 8080:은 리뷰에서 바로 잡힙니다. 다른 머신에서 접근할 필요가 없는 것은 127.0.0.1:에 바인딩해 0.0.0.0의 두 프로젝트가 같은 포트를 두고 다투지 않게 하세요. 스택을 내릴 때는 Ctrl-C 대신 docker compose down을 써서 restart 정책 컨테이너가 남아 포트를 되찾지 않게 합니다.

address already in use·EADDRINUSE와 어떻게 다른가

port is already allocated는 Docker 자체 메시지입니다: 장부가 이미 그 호스트 포트를 어떤 컨테이너용으로 예약했다는 뜻입니다. address already in use(및 옛 userland-proxy 줄)는 Docker가 아닌 소켓이 포트를 쥐고 있어 커널이 bind를 거부하는 것입니다 — 소유자만 다른 같은 충돌이며, 그래서 하나는 docker ps로, 다른 하나는 ss로 안내합니다. Node의 맨 EADDRINUSE나 평범한 서버의 bind: address already in use는 Docker가 경로에 없는 같은 커널 오류로, 이때는 호스트 프로세스를 직접 쫓습니다. 셋 다 한 가지를 말합니다: 두 대상이 하나의 포트를 원한다는 것.

관련 질문

docker ps에는 아무것도 없는데 포트가 여전히 잡혀 있습니다.

docker ps -a를 확인하세요. --restart 정책이 붙은 정지된 컨테이너가 명령 사이에 되살아나 포트를 되찾을 수 있습니다. docker rm(또는 프로젝트 전체는 docker compose down)으로 제거한 뒤 다시 실행하세요.

'port is already allocated'와 'address already in use'를 어떻게 구분하나요?

'port is already allocated'는 다른 컨테이너가 호스트 포트를 쥔 것 — docker ps --filter publish=PORT로 찾으세요. 'address already in use'는 Docker가 아닌 프로세스가 쥔 것 — ss -ltnp나 netstat -ano로 찾으세요.

포트를 쥔 걸 그냥 kill해도 되나요?

먼저 소유자를 찾으세요. 컨테이너면 정지 후 제거하고, 호스트 프로세스면 ss/netstat로 PID를 얻어 정상 종료하세요. kill -9나 taskkill /F는 소켓이 달리 풀리지 않을 때만 쓰세요.

Docker가 빈 호스트 포트를 고르게 하려면?

-P(--publish-all)로 노출 포트를 무작위 호스트 포트에 게시한 뒤 docker port <container>로 배정된 포트를 확인하세요.

Docker 데몬을 재시작하면 풀리는데, 안전한가요?

데몬 재시작은 남은 매핑을 지우지만 호스트의 모든 컨테이너도 정지시킵니다. 포트 하나를 고치려 무관한 서비스를 내리지 않도록, 그 포트의 실제 소유자만 찾아 비우세요.

참고 자료

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

같은 카테고리 다른 글

detected dubious ownershipFixed

Git: fatal: detected dubious ownership in repository

Git 2.35.2의 CVE-2022-24765 수정 이후, Git은 작업 트리나 .git 디렉터리의 소유자가 명령을 실행한 사용자와 다르면 저장소를 읽지 않습니다. 컨테이너, CI 작업, sudo 세션, 공유 드라이브에서 주로 나타납니다. 저장소가 내 것이어야 한다면 소유권을 바로잡고, 아니라면 global 설정의 safe.directory에 정확한 경로를 추가하세요 — Git은 이 설정을 저장소 자신의 설정에서는 무시합니다.

Git
failed calling webhookFixed

Kubernetes: Internal error occurred: failed calling webhook

admission webhook이 쓰기 요청 앞에 서 있는데 API server가 응답을 받지 못했고, 기본값인 failurePolicy: Fail이 그 침묵을 거부로 바꾼 것입니다. 메시지 끝부분이 곧 진단입니다 — context deadline exceeded는 호출이 도달하지 못한 것, no endpoints available은 떠 있는 게 없는 것, x509 줄은 API server가 webhook 인증서를 신뢰하지 않는 것입니다. 원인마다 해결이 다르고, 어느 것도 매니페스트 문제가 아닙니다.

Kubernetes
1205Fixed

MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

다른 트랜잭션이 쥐고 있는 행 잠금을 innodb_lock_wait_timeout 만큼 기다리다 포기한 것입니다. sys.innodb_lock_waits 가 막고 있는 세션을 지목하고 KILL 문까지 만들어 주며, blocking_query 가 NULL 이면 블로커가 열린 트랜잭션 위에서 놀고 있다는 뜻입니다. 재시도 로직이 가장 자주 틀리는 지점은 따로 있습니다. 기본값에서는 타임아웃된 문장 하나만 롤백되므로, 트랜잭션은 그대로 열린 채 앞서 잡은 잠금을 전부 쥐고 있습니다.

MySQL
MISCONFFixed

Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk

읽기는 되는데 모든 쓰기가 거부됩니다. 마지막 백그라운드 저장이 실패했고 stop-writes-on-bgsave-error 기본값이 yes 이기 때문입니다. 진짜 원인은 로그에 적혀 있습니다 — 디스크 공간 부족, redis 사용자가 쓸 수 없는 dir, rename 시점의 read-only 마운트, 또는 fork 가 Cannot allocate memory 로 실패하는 경우입니다. 원인을 고치고 BGSAVE 한 번만 돌리면 재시작 없이 쓰기가 돌아옵니다. rdb_last_bgsave_status 가 err 에서 ok 로 바뀝니다. stop-writes-on-bgsave-error no 는 쓰기를 즉시 되살리지만 스냅샷은 여전히 실패한 상태로 두므로, 해결이 아니라 의도한 맞교환으로 다뤄야 합니다.

Redis
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryFixed

Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (종료 코드 134)

V8 힙에는 시스템 메모리와 Node 릴리스에서 유도되는 자기만의 천장이 있고, 가진 RAM 보다 훨씬 낮은 경우가 흔합니다. 빌드나 서버가 거기 닿으면 V8 은 FATAL ERROR: Reached heap limit 과 종료 코드 134 로 스스로 중단합니다. v8.getHeapStatistics().heap_size_limit 으로 실제 한도를 읽은 뒤, 큰 작업은 --max-old-space-size(MiB)나 NODE_OPTIONS 로 올리고, 컨테이너 안에서는 cgroup 한도보다 낮게 잡고, 오래 도는 프로세스의 누수는 --heapsnapshot-near-heap-limit 으로 잡으세요. FATAL ERROR 줄 없이 137 로 끝나면 컨테이너 kill 이지 이 오류가 아닙니다.

Node.js
exec /docker-entrypoint.sh: exec format errorFixed

Docker: 컨테이너 시작 직후 "exec format error" — 플랫폼이 다른 이미지, 에뮬레이터 없음, 셔뱅 없는 스크립트

컨테이너가 첫 명령에서 exec format error 로 끝납니다. 커널의 ENOEXEC, 즉 파일은 있지만 여기서는 실행할 수 없다는 뜻입니다. 실제로는 한 CPU 아키텍처에서 빌드한 이미지(Apple 실리콘 Mac은 linux/arm64 를 만듭니다)를 binfmt_misc 에 QEMU 핸들러가 없는 다른 아키텍처(x86_64 서버)에서 돌리거나, 엔트리포인트 스크립트의 첫 줄이 셔뱅이 아닌 경우입니다. uname -m, docker image inspect, ls /proc/sys/fs/binfmt_misc 로 원인을 가르고, docker buildx build --platform 을 명시(또는 두 플랫폼 매니페스트 목록 발행)하거나, 에뮬레이션이 목적이면 QEMU 등록과 --platform, 스크립트라면 #!/bin/sh 한 줄로 해결합니다.

Docker