
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.
Last updated: January 30, 2025
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.
When DNS issues occur in containers during pip installations, one of many errors may occur.
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).
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.
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.
Before proposing solutions, we must inspect and verify several key factors:
One of these factors is typically a root cause of pip DNS issues within containers.
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.
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.
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.
Given the possible issues identified in the preceding section, let’s investigate potential solutions:
The above solutions all address the issue of DNS resolution within Docker containers when installing pip packages.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.