• Home
  • About
    • lahuman photo

      lahuman

      열심히 사는 아저씨

    • Learn More
    • Facebook
    • LinkedIn
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Docker에서 curl은 되는데 Node.js fetch만 실패할 때 (feat. undici)

20 Oct 2025

Reading time ~3 minutes

개발을 하다 보면 Docker 컨테이너 내부에서 호스트 머신(PC)의 API를 호출해야 하는 경우가 종종 있습니다.

일반적으로 Docker for Mac/Windows에서는 host.docker.internal을 사용하지만, Linux 환경에서는 Docker 브리지의 기본 게이트웨이 주소인 172.17.0.1을 사용하는 경우가 많습니다.

그런데 이 주소로 API를 호출하는 과정에서 매우 이상한 문제를 겪게 되어 트러블슈팅 과정을 공유해보고자 합니다.

증상: curl은 되는데 fetch는 실패

먼저, 컨테이너 내부에서 호스트(172.17.0.1)로 네트워크 연결이 되는지 확인하기 위해 curl 명령어를 사용했습니다.

eb7b0d89f82b:/app# curl http://172.17.0.1:6000/webhook/.... -v

* Trying 172.17.0.1:6000...
* Connected to 172.17.0.1 (172.17.0.1) port 6000
...
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Basic realm="Webhook"
...
Authorization is required!

401 Unauthorized 응답이 왔습니다. 이는 서버가 인증을 요구한다는 뜻으로, 네트워크 연결 자체는 성공했다는 의미입니다.

그런데 동일한 주소를 Node.js의 fetch API를 사용하여 호출하면 다른 결과가 나옵니다.

// test.js
// (인증 로직은 편의상 생략)
const url = 'http://172.17.0.1:6000/webhook/....';

try {
  const response = await fetch(url, { method: 'GET' });
  console.log(await response.text());
} catch (error) {
  console.error('Error calling webhook:', error.message);
}
eb7b0d89f82b:/app# node test.js

Error calling webhook: fetch failed

분명 curl로는 연결이 되는데, 왜 Node.js의 fetch만 fetch failed라는 네트워크 연결 실패 오류를 반환하는 것일까요?

원인: curl과 fetch는 다르다

이 문제의 핵심은 curl과 Node.js의 fetch가 동일하게 작동하지 않는다는 점입니다.

  1. curl: C언어로 작성된, 매우 표준적이고 검증된 저수준(low-level) HTTP 클라이언트입니다.
  2. Node.js fetch: Node.js 18 버전부터 내장된 fetch API는 undici라는 이름의 별도 HTTP 클라이언트 라이브러리를 기반으로 합니다.

즉, curl이 성공했다는 것은 “네트워크 경로, IP, 포트, 방화벽”은 모두 정상이란 뜻입니다.

fetch failed가 발생한 이유는, Node.js의 undici 구현체가 특정 Docker 네트워크 환경(이 경우 172.17.0.1 게이트웨이)과 통신하는 과정에서 호환성 문제를 일으켰기 때문입니다. (주로 IPv6/IPv4 처리 방식 차이나 커넥션 풀링 방식의 차이로 인해 발생하곤 합니다)

axios는 어떨까요?

흥미롭게도, 이 상황에서 axios 라이브러리를 사용하면 대부분 성공합니다.

import axios from 'axios';
// const response = await axios.get(url, { auth: { ... } });

axios가 성공하는 이유는 undici와 다르게, Node.js의 내장 http 모듈을 기반으로 작동하기 때문입니다. 이 http 모듈은 curl과 유사하게 더 고전적이고 단순한 방식으로 통신하므로 호환성 문제를 피할 수 있었던 것입니다.

해결: undici를 우회하기

원인을 알았으니 해결 방법은 명확합니다. undici 기반의 fetch 대신, http 모듈 기반의 클라이언트를 사용하면 됩니다.

방법 1. axios 라이브러리 사용 (권장)

가장 간단하고 현대적인 방법입니다. npm이나 yarn으로 axios를 설치하고 코드를 수정합니다.

# ESM 환경에서 require 오류가 났던 것을 기억합니다.
npm install axios
// test.js
import axios from 'axios'; // require 대신 import 사용

const url = 'http://172.17.0.1:6000/webhook/....';

// 401 오류를 해결하기 위해 인증 정보 추가
const auth = {
  username: 'YOUR_USERNAME',
  password: 'YOUR_PASSWORD'
};

try {
  // axios는 auth 객체를 Basic 인증 헤더로 자동 변환해 줍니다.
  const response = await axios.get(url, { auth: auth });
  
  console.log('Webhook GET call successful!');
  console.log('Status Code:', response.status);
  console.log('Server response:', response.data);

} catch (error) {
  // ... (Axios 오류 처리) ...
  console.error('Error calling webhook:', error.message);
}

방법 2. Node.js 내장 http 모듈 사용

axios 같은 외부 라이브러리를 설치하기 어려운 환경이라면, Node.js에 100% 내장된 http 모듈을 직접 사용할 수 있습니다. curl과 거의 동일하게 작동합니다.

// test.js (Node.js 내장 http 모듈 사용)
import http from 'http'; // ESM 방식
import { URL } from 'url'; // URL 파싱을 위해 추가

const urlString = 'http://172.17.0.1:6000/webhook/....';

// 인증 정보
const credentials = 'YOUR_USERNAME:YOUR_PASSWORD';
const encodedCredentials = Buffer.from(credentials).toString('base64');

const { hostname, port, pathname } = new URL(urlString);

const options = {
  hostname: hostname,
  port: port,
  path: pathname,
  method: 'GET',
  headers: {
    'Authorization': `Basic ${encodedCredentials}`
  }
};

const req = http.request(options, (res) => {
  console.log(`Status Code: ${res.statusCode}`);
  let data = '';
  res.on('data', (chunk) => { data += chunk; });
  res.on('end', () => {
    console.log('Server response:', data);
  });
});

req.on('error', (error) => {
  // 이 오류는 'fetch failed'와 동일한 네트워크 오류입니다.
  console.error('Error calling webhook:', error.message);
});

req.end();

마무리

fetch failed라는 단순한 오류 메시지를 만났을 때, 무조건 네트워크 방화벽이나 IP 주소 문제라고 단정 짓기 쉽습니다.

하지만 curl과 같은 기본 도구로 교차 검증을 해보니, 문제는 네트워크가 아니라 Node.js의 fetch 구현체인 undici와 특정 Docker 네트워크 환경 간의 호환성 문제임을 알 수 있었습니다.

비슷한 문제를 겪으신다면, fetch 대신 axios나 http 기본 모듈을 사용해 보시는 것을 추천해 드립니다.

레퍼런스 (References)

  • GitHub Issue: 172.17.0.1 주소에서 fetch failed 발생 사례 (Mintplex-Labs/anything-llm)
  • n8n Community: Node.js HTTP 노드는 실패하지만, wget은 성공하는 사례
  • GitHub Issue: undici의 TypeError: fetch failed (nodejs/undici)


fetchcurlundicinetwork Share Tweet +1