Fixing Python requests.exceptions.ConnectTimeout in Docker — DNS Resolution and Container Networking

Environment Context: Dockerized Python Application on Custom Bridge Network

AttributeValue
ComponentPython 3.9 application using requests library
Deployment MethodDocker Compose (v3) with custom bridge network
Host OSLinux (kernel 5.x) / Docker Desktop (macOS/Windows)
Critical Config Pathsdocker-compose.ymlDockerfilerequirements.txt
Network ConfigurationCustom network mynetwork attached to the application service
Port Mapping5040:5040 (host → container)

The application is a minimal Python script that performs an outbound HTTPS GET request to an external endpoint. The container runs with default network settings, attached to a user-defined bridge network (mynetwork).


The Symptom: Outbound HTTPS Requests Time Out Inside the Container

From the operator’s perspective, the container starts successfully, but the Python script fails to complete the outbound request. The application logs show a connection timeout error consistently, while the same code executes without issue when run directly on the host (outside Docker).

A quick check of the container logs revealed the following pattern:

Hello
Traceback (most recent call last):
  ...
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='google.com', port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at ...>, 'Connection to google.com timed out. (connect timeout=2)'))
Python requests module HTTPSConnectionPool connect timeout error log

The timeout is set to 2 seconds, which is sufficient for the host but consistently insufficient inside the container — indicating a networking issue rather than a slow endpoint.


Raw Stack Trace: requests.exceptions.ConnectTimeout

Below is the representative error output captured from the container’s stdout:

Hello
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/urllib3/connection.py", line 174, in _new_conn
    conn = connection.create_connection(
  File "/usr/local/lib/python3.9/site-packages/urllib3/util/connection.py", line 95, in create_connection
    raise err
  File "/usr/local/lib/python3.9/site-packages/urllib3/util/connection.py", line 85, in create_connection
    sock.connect(sa)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/urllib3/connectionpool.py", line 715, in urlopen
    httplib_response = self._make_request(
  File "/usr/local/lib/python3.9/site-packages/urllib3/connectionpool.py", line 404, in _make_request
    self._validate_conn(conn)
  File "/usr/local/lib/python3.9/site-packages/urllib3/connectionpool.py", line 1061, in _validate_conn
    conn.connect()
  File "/usr/local/lib/python3.9/site-packages/urllib3/connection.py", line 363, in connect
    self.sock = conn = self._new_conn()
  File "/usr/local/lib/python3.9/site-packages/urllib3/connection.py", line 186, in _new_conn
    raise ConnectTimeoutError(
urllib3.exceptions.ConnectTimeoutError: (<urllib3.connection.HTTPSConnection object at 0x7f8a2c1b3d90>, 'Connection to google.com timed out. (connect timeout=2)')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/requests/adapters.py", line 487, in send
    resp = conn.urlopen(
  File "/usr/local/lib/python3.9/site-packages/urllib3/connectionpool.py", line 799, in urlopen
    retries = retries.increment(
  File "/usr/local/lib/python3.9/site-packages/urllib3/util/retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='google.com', port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7f8a2c1b3d90>, 'Connection to google.com timed out. (connect timeout=2)'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/app/app.py", line 9, in main
    response = requests.get(url, timeout=2)
  File "/usr/local/lib/python3.9/site-packages/requests/api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/requests/adapters.py", line 504, in send
    raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='google.com', port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7f8a2c1b3d90>, 'Connection to google.com timed out. (connect timeout=2)'))

Key observation: The error occurs at the TCP connection stage (socket.connect), not at the TLS or HTTP layer. This narrows the root cause to network reachability or DNS resolution from within the container.

Python Traceback showing requests.exceptions.ConnectTimeout in app.py

Failed Attempts: Common Missteps That Did Not Resolve the Issue

AttemptResultWhy It Failed
Increasing timeout from 2 to 30 secondsTimeout still triggered, just laterThe issue is not latency — the connection never establishes at all
Removing the custom network and using the default bridgeSame timeoutDefault bridge has the same DNS and routing constraints
Adding network_mode: hostWorked, but defeated container isolationHost mode bypasses Docker’s network stack entirely — not a viable long-term solution
Pinging the target IP from inside the containerping: bad address 'google.com'Confirmed DNS resolution failure inside the container
Using --dns 8.8.8.8 in docker runPartial success, but not portableManual DNS override works but does not address the underlying Compose configuration gap

We noticed that the container could reach external IPs when using raw IP addresses, but domain name resolution consistently failed. This pointed squarely at DNS configuration rather than routing or firewall rules.


The Solution: Explicit DNS Configuration in Docker Compose

The root cause is that Docker’s default bridge network does not automatically inherit the host’s DNS settings in all environments. When using a custom network in Docker Compose, the container may fall back to an empty or incorrect /etc/resolv.conf, preventing name resolution for external domains.

Permanent Fix: Specify DNS Servers in docker-compose.yml

Add the dns directive under the service definition:

version: '3'
services:
  myapp:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "5040:5040"
    networks:
      - mynetwork
    dns:
      - 8.8.8.8
      - 1.1.1.1
    # Optional: also set search domains if needed
    # dns_search:
    #   - internal.corp

networks:
  mynetwork:

Why this works: The dns field explicitly populates /etc/resolv.conf inside the container with the specified nameservers. The container will use these servers for all outbound DNS queries, resolving google.com to an IP address before attempting the TCP connection.

Alternative: Use Host’s DNS Resolvers

If you prefer to inherit the host’s DNS configuration (more portable across networks), use:

dns:
  - 192.168.1.1   # Replace with your host's DNS server

To find your host’s DNS server on Linux:

systemd-resolve --status | grep "DNS Servers"
# or
cat /etc/resolv.conf | grep nameserver

Verify the Fix

After applying the change, rebuild and restart the container:

docker-compose down
docker-compose up --build

Inside the container, verify DNS resolution:

docker-compose exec myapp cat /etc/resolv.conf
# Should show the specified DNS servers
docker-compose exec myapp nslookup google.com
# Should return an IP address

The application should now complete the request successfully within the configured timeout.


Prevention: Monitoring and Configuration Hardening

To avoid recurrence in production or development environments:

1. Network Health Checks

Add a simple connectivity test as part of the container’s health check:

healthcheck:
  test: ["CMD", "curl", "-f", "https://google.com", "||", "exit", "1"]
  interval: 30s
  timeout: 5s
  retries: 3

2. Structured Logging for Network Errors

Wrap outbound requests with detailed error logging that captures DNS and connection diagnostics:

import socket
import logging

try:
    socket.gethostbyname('google.com')
except socket.gaierror as e:
    logging.error(f"DNS resolution failed: {e}")

3. Environment-Specific DNS Configuration

Use Docker Compose environment variables or .env files to set DNS servers dynamically:

dns:
  - ${DNS_SERVER:-8.8.8.8}

4. Prometheus Metrics for Outbound Connectivity

Export a simple gauge metric indicating the success/failure of periodic external reachability tests. Alert on sustained failures.

5. Use network_mode: host Only as a Last Resort

Host networking bypasses Docker’s network isolation and can cause port conflicts. Reserve it for debugging or specific low-level use cases.


References


(Last verified: 28 May 2026 — Docker Compose v3, Python 3.9, requests 2.22.0