DEV Community

jun liu
jun liu

Posted on

Docker Container Can't Reach the Internet: 5 Fixes That Actually Work

Docker Container Can't Reach the Internet: 5 Fixes That Actually Work

You pull a fresh container, run it, and immediately hit a wall: curl times out, apt-get update fails, your app can't call an API. The host machine is fine. The container is isolated. What went wrong?

This happened to me last week while setting up a staging environment. Three containers, three different networking symptoms, one root cause I'd seen too many times. Here's the systematic checklist that got everything working again — no guesswork, just the five fixes that cover 95% of cases.

The symptom first: what exactly is broken?

Before you touch any config, confirm the failure mode:

# Test from inside the container
docker exec <container> ping -c 3 8.8.8.8        # ICMP to public DNS IP
docker exec <container> nslookup google.com      # DNS resolution only
docker exec <container> curl -v https://example.com  # Full HTTP to external
Enter fullscreen mode Exit fullscreen mode
Result Likely cause
ping 8.8.8.8 works, nslookup fails DNS resolver misconfiguration
ping fails, nslookup fails Network stack / bridge / NAT issue
ping works, curl times out on HTTPS Firewall or MTU issue
Only some containers fail Container-specific network config

Fix each layer in order. Don't jump to iptables before checking DNS.


Fix 1: Set explicit DNS servers (most common fix)

Docker's built-in DNS resolver sometimes can't reach your upstream DNS, especially on Linux hosts with systemd-resolved or cloud VPS providers that use private DNS ranges.

Symptom: containers can ping IPs but can't resolve hostnames.

Fix: add DNS servers explicitly in docker-compose.yml:

services:
  web:
    image: nginx:alpine
    dns:
      - 8.8.8.8
      - 1.1.1.1
    # or use the host's resolved DNS
    dns_search: local.domain
Enter fullscreen mode Exit fullscreen mode

Or set it globally in /etc/docker/daemon.json:

{
  "dns": ["8.8.8.8", "1.1.1.1"]
}
Enter fullscreen mode Exit fullscreen mode

Then restart: sudo systemctl restart docker.

Why this works: Docker defaults to the host's resolv.conf, which on many systems points to 127.0.0.53 (systemd-resolved). Containers can't always reach that loopback address. Pointing directly to public DNS bypasses the issue entirely.


Fix 2: Check the bridge network MTU

If ping works but curl hangs on large responses, you're likely hitting an MTU mismatch. Docker creates a bridge (docker0) with MTU 1500 by default, but many cloud providers and VPNs use jumbo frames or tunnel encapsulation that reduces the effective MTU.

Test:

# From host
ip link show docker0

# From container (try pinging with small packets)
docker exec <container> ping -c 3 -s 1472 8.8.8.8   # 1500 - 28 header
docker exec <container> ping -c 3 -s 1400 8.8.8.8   # conservative
Enter fullscreen mode Exit fullscreen mode

Fix: set MTU on the daemon:

// /etc/docker/daemon.json
{
  "mtu": 1400
}
Enter fullscreen mode Exit fullscreen mode

Or per-network at creation:

docker network create -o com.docker.network.driver.mtu=1400 mynet
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: if you're on AWS, GCP, Azure, or using WireGuard/Cloudflare Tunnel, start with MTU 1400. It's the safe default for tunneled environments.


Fix 3: Verify iptables / firewalld isn't blocking outbound

Linux firewalls sometimes drop forwarded traffic from Docker's bridge subnet. This is rare but painful because the symptom looks identical to a DNS problem.

Check:

# See if Docker's rules are present
sudo iptables -L DOCKER -n -v
sudo iptables -L FORWARD -n -v

# Check for explicit DROP/REJECT rules
sudo iptables -L FORWARD -n -v | grep -i drop
Enter fullscreen mode Exit fullscreen mode

Fix: ensure the FORWARD chain allows established connections and Docker traffic:

sudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A FORWARD -s 172.16.0.0/12 ! -d 172.16.0.0/12 -j ACCEPT
Enter fullscreen mode Exit fullscreen mode

If you're using firewalld:

sudo firewall-cmd --zone=trusted --add-interface=docker0
sudo firewall-cmd --runtime-to-permanent
Enter fullscreen mode Exit fullscreen mode

Fix 4: Disable IPv6 if it's causing silent failures

Some hosting providers and home routers have broken IPv6 routing. Docker enables IPv6 by default on the bridge, and containers may try IPv6 first — silently failing instead of falling back to IPv4.

Check:

docker exec <container> cat /etc/gai.conf 2>/dev/null
docker exec <container> ip -6 addr show
Enter fullscreen mode Exit fullscreen mode

Fix: disable IPv6 in Docker:

// /etc/docker/daemon.json
{
  "ipv6": false
}
Enter fullscreen mode Exit fullscreen mode

Or force IPv4-only at runtime:

docker run --platform linux/amd64 <image>
# or add to docker-compose
network_mode: bridge
ip_stack: ipv4
Enter fullscreen mode Exit fullscreen mode

Fix 5: The --network=host escape hatch (and when to use it)

If none of the above work and you need something running now, --network=host bypasses the bridge entirely — the container shares the host's network stack directly.

docker run --network=host <image>
# or in compose
services:
  web:
    network_mode: host
Enter fullscreen mode Exit fullscreen mode

Pros: instant connectivity, no NAT overhead, no MTU/DNS worries.
Cons: port conflicts (container binds directly to host ports), reduced isolation, some security-sensitive workloads shouldn't use it.

Use this for: batch jobs, one-off tools, debugging. Don't use it for: production web servers, databases exposed to the internet, or anything handling sensitive data.


My actual debugging story (the one that brought all five together)

Last Tuesday I spun up a Python scraper container on a Linode VPS. curl google.com from inside returned curl: (6) Could not resolve host. Ping to 8.8.8.8 worked. So it was DNS.

Fix 1 didn't work — I'd already set dns: [8.8.8.8] in compose and it was still failing. Checked the host's systemd-resolved status: the DNS proxy at 127.0.0.53 was running but refusing external queries (expected). The explicit 8.8.8.8 in compose should have worked though.

Turns out Fix 4 was the real culprit: the VPS had IPv6 enabled globally, and Docker was creating an IPv6-capable bridge. The container preferred IPv6 for google.com (AAAA record), got a routable IPv6 address, but the VPS provider's IPv6 router was dropping packets silently. Forcing ip_stack: ipv4 in the network config made it use IPv4 exclusively, and everything worked.

Lesson: when multiple fixes seem to apply, check the one that causes "silent" failures (IPv6 routing) before the noisy ones (DNS).


Quick reference: what to try first

  1. Can it ping an IP but not a hostname? → Fix 1 (DNS)
  2. Can it ping small packets but curl hangs? → Fix 2 (MTU)
  3. Only specific containers fail? → Fix 4 (IPv6 per-container)
  4. Everything fails after a host firewall change? → Fix 3 (iptables)
  5. Need it working in 30 seconds and isolation doesn't matter? → Fix 5 (host network)

Full guide with screenshots and edge cases: https://jiunotes.top/en/articles/docker-container-no-internet-fixes

More DevOps debugging notes: ops.jiunotes.top

Top comments (0)