Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Introduction

DNS resolution issues sometimes prevent pip from downloading Python packages when installing them inside Docker containers. Common symptoms include timeout errors when reaching PyPI, name resolution errors, extremely slow package downloads, and connection refused errors.

In this tutorial, we’ll explore several approaches and possible solutions to troubleshooting DNS issues in Docker containers associated with installing pip packages. All commands have been tested on Ubuntu 22.04 with Docker engine version 27.5.0.

2. Common DNS Issue Errors

When DNS issues occur in containers during pip installations, one of many errors may occur.

2.1. Connection Errors

A usual error when connecting may show a fairly comprehensive message:

Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by
'NewConnectionError('<pip._vendor.urllib3.connection.VerifiedHTTPSConnection object at 0x7f6d5d343d30>:
Failed to establish a new connection: [Errno -3] Temporary failure in name resolution',)': /simple/django/

This error text indicates that the container was unable to establish a connection to the PyPI repository due to a temporary DNS resolution failure (indicated by the [Errno -3] Temporary failure in name resolution message).

2.2. SSL Certificate Errors

When dealing with SSL security, fetching the URL might result in a NameResolutionError:

Could not fetch URL https://pypi.org/simple/requests/: There was a problem confirming the ssl certificate:
HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url:
/simple/requests/ (Caused by NameResolutionError("<urllib3.connection.HTTPSConnection object at 0x7f8c1c9b6d90>:
Failed to resolve 'pypi.org' ([Errno -2] Name or service not known)")) - skipping

This error suggests that the container could not resolve the hostname pypi.org, preventing pip from verifying the SSL certificate of the PyPI repository.

Both messages above indicate that the Docker container cannot resolve domain names, making pip unable to reach the package repositories.

3. Troubleshooting Environment

To begin troubleshooting, let’s create a testing environment using DNS troubleshooting tools like dig, nslookup, and ping.

First, we create a directory named pip-dns-demo and navigate to it to store demo files:

$ mkdir -p pip-dns-demo && cd pip-dns-demo

Next, using a Dockerfile with the python-3.9-slim as the base image, we can install networking tools as part of the image creation process:

$ cat Dockerfile
FROM python:3.9-slim

# Install networking tools
RUN apt-get update && apt-get install -y \
    iputils-ping \
    dnsutils \
    curl \
    net-tools \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

CMD ["bash"]

Then we build an image named app with the docker build command:

$ docker build -t app .

Finally, we create and run a container in interactive mode using the app image:

$ docker run -it app

Once inside the container, we can begin troubleshooting.

4. Step-by-Step Diagnostics

Before proposing solutions, we must inspect and verify several key factors:

  • verify basic network connectivity within the container
  • inspect DNS configuration
  • inspect Docker DNS settings

One of these factors is typically a root cause of pip DNS issues within containers.

4.1. Verify Basic Network Connectivity Within the Container

From within the container, we can check if it has access to external networks.

To begin, let’s attempt to ping one of the public IP addresses of a Google DNS server by sending four packets:

$ ping -c 4 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=9790 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=8795 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=61 time=7774 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=61 time=6752 ms

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3053ms
rtt min/avg/max/mdev = 6752.463/8278.023/9790.048/1133.019 ms, pipe 4

Next, we check whether DNS resolution works for known domains like pypi.org:

$ ping -c 4 pypi.org
PING pypi.org (151.101.192.223) 56(84) bytes of data.
64 bytes from 151.101.192.223: icmp_seq=1 ttl=61 time=1229 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=2 ttl=61 time=732 ms
64 bytes from 151.101.192.223: icmp_seq=3 ttl=61 time=233 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=4 ttl=61 time=210 ms

--- pypi.org ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 12800ms
rtt min/avg/max/mdev = 209.781/600.898/1228.683/418.113 ms, pipe 2

As seen above, connectivity to external networks is possible for both cases. If a different result is observed, the container may be unable to reach external networks.

4.2. Inspect DNS Configuration

In Docker containers, the /etc/resolve.conf file is the file that defines how the container resolves domain names to IP addresses. By default, the /etc/resolv.conf file inherits the DNS settings from the host machine:

$ cat /etc/resolv.conf
# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.

nameserver 10.0.2.3
search ...

# Based on host file: '/run/systemd/resolve/resolv.conf' (legacy)
# Overrides: []

It’s crucial to confirm whether DNS resolution works through the given nameserver (10.0.2.3). We can do this with the dig utility that uses the container’s DNS resolver library. Let’s test it out with pypi.org:

$ dig pypi.org

; <<>> DiG 9.18.28-1~deb12u2-Debian <<>> pypi.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30103
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;pypi.org.			IN	A

;; ANSWER SECTION:
pypi.org.		7152	IN	A	151.101.128.223
pypi.org.		7152	IN	A	151.101.0.223
pypi.org.		7152	IN	A	151.101.64.223
pypi.org.		7152	IN	A	151.101.192.223

;; Query time: 0 msec
;; SERVER: 10.0.2.3#53(10.0.2.3) (UDP)
;; WHEN: Thu Jan 23 12:20:05 UTC 2025
;; MSG SIZE  rcvd: 101

The output confirms that the container can resolve external domains like pypi.org. Again, if the output shows otherwise, this confirms an issue with the container DNS resolution.

4.3. Inspect Docker DNS Settings

To investigate potential DNS issues, we can examine the Docker daemon configuration file, daemon.json (if it exists), for any custom DNS settings that might be interfering with container networking:

$ cat /etc/docker/daemon.json

Furthermore, as the container is launched in bridge mode, we can examine the bridge network to verify its internet connectivity:

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "cdb5c8b9e5602516517a2b664f4467199149f6da17fa265161b80c5dd54ee89c",
        ...
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

Notably, as observed in the output above, the enable_ip_masquerade option being set to true signifies that Network Address Translation (NAT) is enabled for the bridge network. This way, containers can access external networks by translating their private IP addresses to the host machine’s public IP address. As previously noted, any deviation from the expected output suggests that the container may lack external network access.

5. DNS Solutions

Given the possible issues identified in the preceding section, let’s investigate potential solutions:

  • configuring Docker daemon DNS
  • implementing container-level DNS configuration
  • using host network mode

The above solutions all address the issue of DNS resolution within Docker containers when installing pip packages.

5.1. Configuring Docker Daemon DNS

As we previously noted, the daemon.json file serves as the core configuration file for the Docker daemon.

Within daemon.json, we can explicitly define the DNS servers to utilize, such as setting them to Google’s public IP addresses:

$ cat /etc/docker/daemon.json
{
    "dns": ["8.8.8.8", "8.8.4.4"]
}

The Google DNS servers are usually reliable and operate with high performance. By specifying stable DNS servers within the daemon.json file, we provide relative assurance that Docker containers can resolve hostnames accurately, even if the default DNS servers on the host machine are malfunctioning.

Restarting Docker should apply these changes:

$ sudo systemctl restart docker

Thus, any containers created or restarted after the Docker daemon restart inherit these DNS settings.

5.2. Implementing Container-Level Daemon DNS

Alternatively, the DNS can be set at container runtime. Using the app image, let’s specify the public Google DNS IP addresses (8.8.8.8, 8.8.4.4) as the DNS servers during container execution:

$ docker run -it --dns 8.8.8.8 --dns 8.8.4.4 app 

Within the container, we can verify that it uses the specified DNS servers by viewing the contents of the resolv.conf file:

$ cat /etc/resolv.conf 
# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.

nameserver 8.8.8.8
nameserver 8.8.4.4
...

Thus, Google’s DNS servers have been set successfully.

Furthermore, if a docker-compose.yml file is employed to orchestrate container startup, DNS can also be configured within this file. Inside the demo directory, let’s create a docker-compose.yml file that uses the previously created image (app) and incorporates the necessary DNS settings:

$ cat docker-compose.yml 
services:
  demo:
    image: app
    dns:
      - 8.8.8.8
      - 8.8.4.4
    command: tail -f /dev/null

This way, we define a service named demo utilizing the app image.

Let’s initiate a container in detached mode for this service:

$ docker-compose up -d
[+] Running 2/2
 ✔ Network pip-dns-demo_default   Created                  0.1s 
 ✔ Container pip-dns-demo-demo-1  Started                  0.5s

To confirm the running container has the DNS set correctly, we can use the docker inspect command:

$ docker inspect pip-dns-demo-demo-1 
[
    {
        "Name": "/pip-dns-demo-demo-1",
        "RestartCount": 0,
        ...
        "HostConfig": {
            "Binds": null,
            ...
            "Dns": [
                "8.8.8.8",
                "8.8.4.4"
            ],
            "DnsOptions": null,
            ...
        },
        ...
    }
]

Looking at the output above, we can see the HostConfig.Dns field lists the specified DNS servers.

5.3. Using host Network Mode

Many times, running the container in host network mode resolves DNS issues.

The reason behind this is that the host mode bypasses the default container networking by setting the –network option to host:

$ docker run --network=host -it app

While this is a straightforward solution, it eliminates container isolation, an important security feature.

6. Test With Demo Application

Since we explored the most viable solutions, let’s now Dockerize a Python Flask application running on port 5000 to verify if pip can a resolve DNS name.

6.1. Create Basic Implementation

Within the demo directory, let’s create an app.py file containing code logic that checks whether it’s possible to access a website by its domain name:

$ cat pip-dns-demo/app.py
from flask import Flask
import socket

app = Flask(__name__)

@app.route('/')
def check_dns():
    domains = ['pypi.org', 'google.com']
    results = []
    
    for domain in domains:
        try:
            ip = socket.gethostbyname(domain)
            results.append(f"{domain}: ✓ Resolved to {ip}")
        except socket.gaierror:
            results.append(f"{domain}: ✗ Failed to resolve")
    
    return "<br>".join(results)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

As seen above, we check whether the pypi.org and Google websites are reachable.

6.2. Add Requirements

By design, we need a requirements.txt file to store the necessary libraries to run the application:

$ cat ~/pip-dns-demo/requirements.txt
flask==2.2.5
werkzeug==2.2.3

Since the project is fairly simple, we only rely on two packages.

6.3. Containerization

Next, we modify the Dockerfile to containerize the application:

$ cat ~/pip-dns-demo/Dockerfile
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 5000

CMD ["python", "app.py"]

Finally, we can rebuild the app image:

$ docker build -t app .                                                                         
[+] Building 103.7s (9/10)                                                                                             docker:default 
...                                                                                          0.0s 
 => [4/5] RUN pip install --no-cache-dir -r requirements.txt  
Collecting flask==2.2.5
Downloading Flask-2.2.5-py3-none-any.whl (94 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.8/94.8 kB 351.6 kB/s eta 0:00:00
...

The output above confirms that pip successfully installed the libraries within the image.

6.4. Testing

After building the image, we can run the container in the background and subsequently send a curl request to the Flask server to observe the output:

$ docker run -p 5000:5000 app &
$ curl localhost:5000
pypi.org: ✓ Resolved to 151.101.128.223<br>google.com: ✓ Resolved to 216.58.223.206172.17.0.1 - - [24/Jan/2025 14:57:40] "GET / HTTP/1.1" 200 -

The successful resolution of pypi.org and google.com within the container suggests that future pip installations should proceed without errors.

7. Additional Considerations

Although all previously discussed solutions generally function effectively, we must also consider special scenarios, such as those encountered in corporate networks that utilize proxies. In such cases, we have to configure these proxies within the Dockerfile before executing the RUN pip commands:

$ cat ~/pip-dns-demo/Dockerfile
FROM python:3.9-slim

WORKDIR /app

ENV HTTP_PROXY="http://example:8080"
ENV HTTPS_PROXY="http://example:8080"

COPY requirements.txt .
RUN pip install -r requirements.txt
...

Alternatively, we can incorporate proxy configuration directly within the pip install command:

RUN pip install -r requirements.txt --proxy=http://addr:port

Additionally, if setting Google’s DNS servers within the daemon.json file appears ineffective, we can utilize nmcli to determine the DNS address of the host and subsequently incorporate this address into the daemon.json file:

$ nmcli dev show | grep 'DNS'
IP4.DNS[1]:                             <server_ip>
$ cat /etc/docker/daemon.json
{
    "dns": [<server_ip>, "8.8.8.8", "8.8.4.4"]
}

This configuration prioritizes the host machine DNS server server_ip, utilizing Google’s DNS servers as a fallback mechanism.

8. Conclusion

In this article, we learned how to troubleshoot and fix DNS issues in Docker containers. We started with basic connectivity tests before moving to advanced solutions.

As always, it’s important to prioritize security and consider the potential impact of the host network solution on container isolation. By following the steps outlined in this troubleshooting guide, we can identify and address any pip DNS-related problems.