TechFeedTechFeed
Cloud & DevOps

ECONNREFUSED, localhost, 도커 네트워크 | 컨테이너 안에서 5432가 거절되면?

컨테이너 안에서 localhost:5432가 거절되면 그 주소는 호스트가 아니라 컨테이너입니다. compose는 서비스 이름, 호스트 디비는 host.docker.internal, 리눅스는 extra_hosts를 맞춥니다. ECONNREFUSED, Docker, PostgreSQL 5432, DATABASE_URL, Node.js, 넥스트, Prisma, 1인 개발자 맥 데스크톱 기준. 2026년 9월 Node.js·도커 공식 문서.

by

컨테이너 안에서 localhost:5432가 거절되면, 그 주소는 호스트 맥이 아니라 컨테이너 자기 자신입니다. 같은 컴포즈면 서비스 이름과 컨테이너 포트로 바꾸고, 호스트에서 도는 디비에만 host.docker.internal을 씁니다.


맥 도커 데스크톱에서 넥스트만 올리고 포스트그레스는 호스트에 둔 채 디비 주소를 복붙하면, 노드가 connect ECONNREFUSED 127.0.0.1:5432를 던집니다. 호스트에 포트가 열려 있어도 컨테이너 안에서는 그 문을 못 봅니다.


타임아웃이나 포트 점유와 줄을 섞지 말고, 에러에 찍힌 주소와 포트부터 읽으세요. 근거는 Node.js 공통 시스템 에러도커 데스크톱 네트워킹에 있습니다.


컨테이너 안 localhost가 거절되면 어디를 보나

에러에 찍힌 주소와 포트를 읽고, 그 칸이 컨테이너인지 호스트인지부터 가릅니다. 노드 문서는 연결 거절(ECONNREFUSED)을 대상 기계가 연결을 적극적으로 밀어낸 줄이라고 적습니다. 그 칸에 듣는 프로세스가 없거나, 있어도 그 주소·포트가 아닐 때 납니다.


컨테이너의 로컬호스트는 호스트가 아닙니다. 앱이 컨테이너 안에 있으면 127.0.0.1은 그 컨테이너뿐입니다. 호스트 맥에서 5432가 열려 있어도, 컨테이너는 그 소켓을 보지 못합니다. 카카오 콜백 앱을 컴포즈로 올리고 로컬 포스트그레스만 호스트에 둔 1인 구성에서 자주 만납니다.


메시지에 찍힌 숫자가 갈립니다. 127.0.0.1:5432면 컨테이너 자기를 두드린 것이고, 서비스 이름 뒤 5432가 거절이면 이름은 맞는데 디비 칸이 아직 안 열린 쪽입니다. 주소를 안 읽고 포트만 바꾸면 같은 줄이 반복됩니다. 혼자 돌리는 새벽 작업도 호스트 터미널에서 되는 주소를 컨테이너 env에 넣으면 바로 이 줄이 납니다.


먼저 기억할 것 | 거절은 "그 주소·포트에 듣는 이가 없다"는 뜻입니다. 호스트에서 lsof가 초록이어도, 컨테이너 cwd의 localhost는 다른 기계입니다.


컨테이너 네트워크에서 localhost와 호스트 포트가 서로 다른 칸인 개념 이미지
컨테이너의 localhost는 호스트 맥이 아니라 그 컨테이너 자신이다

거절과 타임아웃, 포트 점유는 다른 줄

거절은 그 칸에 듣는 프로세스가 없다는 뜻이고, 타임아웃·점유와 칸이 다릅니다. 세 줄이 콘솔에 비슷해 보여도 시스템 호출과 코드가 갈립니다.


코드무슨 뜻인가먼저 볼 칸
연결 거절대상이 바로 밀어냄. 그 포트에 듣는 이가 없음호스트 이름, 컨테이너 포트, 디비가 켜졌는지
연결 시간 초과응답이 안 옴. 패킷이 버려지거나 경로가 막힘보안 그룹, 아이피 버전, 함수 한도
포트 이미 사용 중내가 그 포트를 열려다 이미 점유됨듣기 쪽 프로세스. 연결 시도가 아님
표 없음연결은 됐는데 표가 없음마이그레이션. 거절과 자리가 다름

배포 함수에서만 끊기는 줄은 시간 초과입니다. 3000번을 내가 못 여는 줄은 포트 점유입니다. 프리뷰 디비에 표가 없는 줄은 테이블 없음입니다. 여기서는 연결이 거절된 칸만 봅니다.


먼저 확인할 네 칸

프로세스 위치, 호스트 이름, 컨테이너 포트, 디비가 켜졌는지 네 칸이면 갈립니다. 표로 먼저 나눕니다.


증상먼저 할 일
앱이 어디 있나호스트 터미널은 되고 컨테이너 실행만 거절디비 주소를 컨테이너 안에서 환경변수로 확인
호스트 이름에러가 루프백 주소서비스 이름 또는 호스트 게이트 이름으로 교체
포트 숫자호스트는 5433인데 주소는 5432컨테이너 포트와 바깥에 연 포트를 분리
디비가 열렸나앱이 먼저 뜨고 거절준비 검사, 헬스체크, 의존 조건
아이피6 로컬호스트아이피6만 거절, 아이피4는 됨주소에 127.0.0.1을 명시

다섯 번째 칸은 호스트에서 앱을 직접 돌릴 때 많습니다. 노드가 로컬호스트를 아이피6로 풀고, 포스트그레스는 아이피4만 듣고 있으면 거절입니다. 컨테이너 안 거절과 증상이 닮아서, 에러의 주소 칸을 반드시 읽으세요. 맥은 둘 다 열려 보이는 경우가 있어, 리눅스 액션에서만 드러납니다.


거절 주소와 컨테이너 URL을 같이 찍기
docker compose exec app printenv DATABASE_URL docker compose exec app getent hosts db || true docker compose exec app getent hosts host.docker.internal || true docker compose exec db pg_isready -U app -d app # 호스트에서 누가 5432를 듣는지 lsof -nP -iTCP:5432 -sTCP:LISTEN

compose 서비스 이름과 퍼블리시 포트

같은 컴포즈 네트워크에서는 서비스 이름과 컨테이너 포트를 씁니다. 도커 컴포즈 네트워킹 문서는 기본 네트워크에서 서비스 이름이 내부 이름으로 잡히고, 서비스 사이 통신은 컨테이너 포트를 쓴다고 적습니다. 호스트 포트는 바깥에서 들어올 때만 필요합니다.


흔한 실수는 바깥 포트 5433을 보고 앱 주소에 로컬호스트 5433을 넣는 것입니다. 호스트 터미널에서는 맞습니다. 같은 파일의 앱 서비스에서는 디비 서비스 이름과 5432가 맞습니다. 5433은 호스트 칸의 숫자입니다.


프로젝트 폴더 이름이 네트워크 접두가 됩니다. 서비스 이름은 파일에 적은 그대로입니다. 컨테이너 아이피를 박지 마세요. 재생성되면 아이피는 바뀌고 이름은 남습니다. 한국에서 맥으로 짜고 리눅스 액션의 컴포즈만 거절이면, 호스트 이름부터 의심하세요.


앱은 db:5432, 호스트만 퍼블리시 포트
services: app: build: . environment: DATABASE_URL: postgresql://app:secret@db:5432/app depends_on: db: condition: service_healthy db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: app ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s timeout: 5s retries: 10
사람 없는 서버 랙 통로. 컨테이너 네트워크와 호스트 포트가 다른 칸임을 보여주는 이미지
서비스 사이는 컨테이너 포트, 호스트에서 붙을 때만 바깥 포트

host.docker.internal은 언제 쓰나

호스트에서 도는 서비스에만 호스트 게이트 이름을 붙입니다. 도커 데스크톱은 host.docker.internal이 호스트 내부 아이피로 풀린다고 적습니다. 포스트그레스가 맥에 직접 깔려 있고 앱만 컨테이너인 자리가 여기입니다.


같은 컴포즈 안의 디비에는 쓰지 마세요. 그때는 서비스 이름입니다. 데스크톱이 아닌 리눅스 엔진은 이 이름이 기본으로 없습니다. 컴포즈 문서는 엑스트라 호스트에 호스트 게이트웨이를 넣으라고 안내합니다. 깃허브 액션 우분투 러너에서 호스트 디비에 붙을 때 이 한 줄이 빠지면 거절입니다.


호스트 디비가 로컬호스트만 듣고 있으면, 컨테이너가 게이트 이름으로 와도 거절될 수 있습니다. 포스트그레스 연결 설정에서 리슨 주소를 확인하고, 개발용이면 도커 브리지에서 들어오는 주소를 허용해야 합니다. 운영에 열어 두는 일과는 다른 칸이니, 방화벽과 접속 허용 목록을 같이 보세요.


리눅스 엔진에서 호스트 디비에 붙기
services: app: extra_hosts: - "host.docker.internal:host-gateway" environment: DATABASE_URL: postgresql://app:secret@host.docker.internal:5432/app # 컨테이너 안에서 이름이 풀리는지 docker compose exec app getent hosts host.docker.internal

5432가 비어 있는지 확인하는 명령

컨테이너 안에서 준비 검사로 그 호스트·포트가 열리는지 확인합니다. 호스트의 포트 조회가 초록이어도 앱 컨테이너에서 거절이면, 아직 주소를 잘못 둔 것입니다. 디비 컨테이너 안에서 로컬호스트는 맞고, 앱 컨테이너의 로컬호스트는 틀립니다.


앱이 디비보다 먼저 뜨면 기동 순간에만 거절이 납니다. 의존 조건만 있으면 컨테이너가 떴다는 뜻이지, 포스트그레스가 듣기 시작했다는 뜻이 아닙니다. 위의 헬스체크와 서비스 준비 완료 조건을 같이 두세요. 재시작만 보고 컴포즈 로그의 디비 칸을 안 보면 원인을 놓칩니다.


바깥에 연 숫자가 다르면 컴포즈 포트 조회로 호스트 쪽 숫자를 확인합니다. 그 숫자는 호스트에서 클라이언트를 붙일 때만 씁니다. 프리즈마 마이그레이션을 호스트에서 돌리면 바깥 포트, 앱 컨테이너에서 돌리면 서비스 이름입니다. 한 주소를 두 칸에 복붙하지 마세요.


사람 없는 모니터 책상. 디비 주소와 준비 검사를 확인하는 작업 환경
거절이면 앱 컨테이너에서 디비 주소와 준비 검사를 같이 확인한다

DATABASE_URL을 칸마다 나누기

앱이 도는 칸마다 디비 주소의 호스트를 따로 둡니다. 호스트에서 넥스트 개발 서버면 로컬호스트, 앱과 디비가 같은 컴포즈면 서비스 이름, 앱만 컨테이너고 디비는 맥이면 호스트 게이트 이름입니다. 환경 파일 하나 공유가 거절의 원인인 경우가 많습니다.


프리즈마 연결 주소도 같은 규칙을 따릅니다. 연결이 거절되면 클라이언트는 스키마를 보기도 전에 죽습니다. 표가 없다는 줄과 섞지 마세요. 슈퍼베이스 로컬을 쓸 때도 대시보드가 브라우저에서 열려도, 컨테이너 앱의 호스트가 로컬호스트면 같은 거절이 납니다.


버셀 프리뷰는 이 줄이 거의 안 납니다. 거기는 관리형 디비 호스트를 씁니다. 로컬 컴포즈와 프리뷰 환경 변수를 한 파일에 두면, 로컬만 거절되거나 프리뷰만 엉뚱한 호스트를 때립니다. 1인으로 여러 사이트를 돌릴 때 사이트마다 컴포즈 프로젝트 이름이 겹치면 네트워크도 꼬이니, 폴더 이름이나 프로젝트 이름을 분리하세요.


복붙 함정 | 호스트에서 되는 주소를 이미지 환경 변수에 넣지 마세요. 빌드 시점 로컬호스트는 이미지 안의 로컬호스트가 됩니다.


참고 자료


내부 연계: ETIMEDOUT, 포트 점유, P2021 테이블 없음, ENOENT cwd


인용한 동작과 옵션은 2026년 9월 공개 문서 기준입니다.


자주 묻는 질문

호스트에서 클라이언트는 되는데 앱 컨테이너만 거절됩니다.

앱 주소가 로컬호스트로 남아 있는 경우가 대부분입니다. 디비가 같은 컴포즈면 서비스 이름과 컨테이너 포트로 바꾸고, 디비가 호스트에만 있으면 호스트 게이트 이름으로 바꿉니다. 호스트에서 되는 주소는 컨테이너 안 주소가 아닙니다.


바깥 포트에 5432를 열었는데도 거절입니다.

바깥에 연 포트는 호스트가 컨테이너로 들어오는 문입니다. 같은 네트워크의 다른 서비스는 그 문을 거치지 않고 컨테이너 포트로 붙습니다. 컴포즈 포트 조회로 호스트 숫자를 확인한 뒤, 앱 컨테이너 주소에는 그 숫자를 넣지 마세요.


리눅스 통합 환경에서만 호스트 게이트 이름이 실패합니다.

도커 데스크톱이 아닌 엔진에는 그 이름이 기본으로 없습니다. 엑스트라 호스트에 호스트 게이트웨이를 넣으세요. 맥 화면이 나와도 우분투 액션이 초록이어야 끝난 것입니다.


의존 조건을 넣었는데 기동할 때만 거절됩니다.

의존 조건 기본값은 컨테이너가 시작됐다는 뜻이지, 포스트그레스가 듣기 시작했다는 뜻이 아닙니다. 준비 검사 헬스체크와 서비스 준비 완료 조건을 같이 두세요. 재시작이 돌면 디비 로그의 준비 줄을 먼저 봅니다.


시간 초과와 무엇이 다른가요?

거절은 대상이 바로 밀어낸 줄이고, 시간 초과는 응답이 안 와 시계가 끝난 줄입니다. 방화벽이 패킷을 버리면 시간 초과 쪽으로 갑니다. 배포 함수 한도와 컨테이너 로컬호스트 거절을 한 원인으로 묶지 마세요.


프리즈마 테이블 없음과 같이 보면 되나요?

안 됩니다. 테이블 없음은 연결이 된 뒤에 표가 없을 때입니다. 연결 거절은 연결 자체가 안 된 줄입니다. 마이그레이션을 먼저 돌리기 전에 주소의 호스트가 열리는지부터 확인하세요.


연결 거절은 그 주소·포트에 듣는 프로세스가 없다는 뜻입니다. 컨테이너 안 로컬호스트를 호스트로 착각하지 말고, 컴포즈면 서비스 이름, 호스트 디비면 호스트 게이트 이름, 리눅스면 엑스트라 호스트를 맞추세요. 관련 글: 시간 초과, 포트 점유, 테이블 없음.


ECONNREFUSEDlocalhostDocker5432composehost.docker.internalPostgreSQLDATABASE_URLNode.js넥스트클라우드개발자

관련 도구

함께 보면 좋은 문제 해결

EXPLORE / Cloud & DevOps

이어서 읽어보기

전체 토픽 둘러보기