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·도커 공식 문서.
에러에 찍힌 주소와 포트를 읽고, 그 칸이 컨테이너인지 호스트인지부터 가릅니다. 노드 문서는 연결 거절(ECONNREFUSED)을 대상 기계가 연결을 적극적으로 밀어낸 줄이라고 적습니다. 그 칸에 듣는 프로세스가 없거나, 있어도 그 주소·포트가 아닐 때 납니다.
컨테이너의 로컬호스트는 호스트가 아닙니다. 앱이 컨테이너 안에 있으면 127.0.0.1은 그 컨테이너뿐입니다. 호스트 맥에서 5432가 열려 있어도, 컨테이너는 그 소켓을 보지 못합니다. 카카오 콜백 앱을 컴포즈로 올리고 로컬 포스트그레스만 호스트에 둔 1인 구성에서 자주 만납니다.
메시지에 찍힌 숫자가 갈립니다. 127.0.0.1:5432면 컨테이너 자기를 두드린 것이고, 서비스 이름 뒤 5432가 거절이면 이름은 맞는데 디비 칸이 아직 안 열린 쪽입니다. 주소를 안 읽고 포트만 바꾸면 같은 줄이 반복됩니다. 혼자 돌리는 새벽 작업도 호스트 터미널에서 되는 주소를 컨테이너 env에 넣으면 바로 이 줄이 납니다.
먼저 기억할 것 | 거절은 "그 주소·포트에 듣는 이가 없다"는 뜻입니다. 호스트에서 lsof가 초록이어도, 컨테이너 cwd의 localhost는 다른 기계입니다.
컨테이너의 localhost는 호스트 맥이 아니라 그 컨테이너 자신이다
거절과 타임아웃, 포트 점유는 다른 줄
거절은 그 칸에 듣는 프로세스가 없다는 뜻이고, 타임아웃·점유와 칸이 다릅니다. 세 줄이 콘솔에 비슷해 보여도 시스템 호출과 코드가 갈립니다.
코드
무슨 뜻인가
먼저 볼 칸
연결 거절
대상이 바로 밀어냄. 그 포트에 듣는 이가 없음
호스트 이름, 컨테이너 포트, 디비가 켜졌는지
연결 시간 초과
응답이 안 옴. 패킷이 버려지거나 경로가 막힘
보안 그룹, 아이피 버전, 함수 한도
포트 이미 사용 중
내가 그 포트를 열려다 이미 점유됨
듣기 쪽 프로세스. 연결 시도가 아님
표 없음
연결은 됐는데 표가 없음
마이그레이션. 거절과 자리가 다름
배포 함수에서만 끊기는 줄은 시간 초과입니다. 3000번을 내가 못 여는 줄은 포트 점유입니다. 프리뷰 디비에 표가 없는 줄은 테이블 없음입니다. 여기서는 연결이 거절된 칸만 봅니다.
먼저 확인할 네 칸
프로세스 위치, 호스트 이름, 컨테이너 포트, 디비가 켜졌는지 네 칸이면 갈립니다. 표로 먼저 나눕니다.
칸
증상
먼저 할 일
앱이 어디 있나
호스트 터미널은 되고 컨테이너 실행만 거절
디비 주소를 컨테이너 안에서 환경변수로 확인
호스트 이름
에러가 루프백 주소
서비스 이름 또는 호스트 게이트 이름으로 교체
포트 숫자
호스트는 5433인데 주소는 5432
컨테이너 포트와 바깥에 연 포트를 분리
디비가 열렸나
앱이 먼저 뜨고 거절
준비 검사, 헬스체크, 의존 조건
아이피6 로컬호스트
아이피6만 거절, 아이피4는 됨
주소에 127.0.0.1을 명시
다섯 번째 칸은 호스트에서 앱을 직접 돌릴 때 많습니다. 노드가 로컬호스트를 아이피6로 풀고, 포스트그레스는 아이피4만 듣고 있으면 거절입니다. 컨테이너 안 거절과 증상이 닮아서, 에러의 주소 칸을 반드시 읽으세요. 맥은 둘 다 열려 보이는 경우가 있어, 리눅스 액션에서만 드러납니다.
호스트에서 도는 서비스에만 호스트 게이트 이름을 붙입니다. 도커 데스크톱은 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인으로 여러 사이트를 돌릴 때 사이트마다 컴포즈 프로젝트 이름이 겹치면 네트워크도 꼬이니, 폴더 이름이나 프로젝트 이름을 분리하세요.
복붙 함정 | 호스트에서 되는 주소를 이미지 환경 변수에 넣지 마세요. 빌드 시점 로컬호스트는 이미지 안의 로컬호스트가 됩니다.