Your VPN connects successfully. An internal hostname resolves on your laptop. The same hostname fails inside your application container.
It is tempting to replace the container’s DNS server and move on. Sometimes that changes the result. Sometimes it breaks internal names or sends queries somewhere you did not intend.
A working host connection does not establish that a container can reach the same DNS resolver or destination. Start by separating three questions:
- Which resolver is being used?
- Can the container reach that resolver?
- Can the container reach the application after resolution succeeds?
This walkthrough focuses on Linux containers. Native Linux Docker Engine and Docker Desktop have different networking paths, so we will distinguish them where it matters.
The commands are diagnostic examples, not results from a tested VPN environment.
1. Confirm where Docker is actually running
Before inspecting your laptop’s routes, check which Docker daemon your commands address:
docker context show docker info --format '{{.OperatingSystem}}' docker version A remote Docker context changes the investigation completely. Your local VPN connection does not automatically provide connectivity to containers running on a remote server.
Record:
- Host operating system.
- Docker Engine or Docker Desktop.
- Docker and VPN versions.
- Local or remote Docker daemon.
- Affected container and Docker network.
- Whether the failure affects public names, internal names or both.
Also distinguish a device-wide VPN from a browser proxy or extension. A browser connection is not a test of container connectivity.
Browser-based IP, DNS and WebRTC checks examine another environment again. They cannot establish which resolver an application container uses.
2. Test the affected container first
Replace app with the actual container name.
docker exec app cat /etc/resolv.conf docker inspect app \ --format '{{json .HostConfig.Dns}}' docker inspect app \ --format '{{json .NetworkSettings.Networks}}' The first command shows the resolver configuration visible inside the container. The others help identify explicit DNS overrides and network membership.
For standard Docker Engine networking:
- Containers on the default bridge receive resolver configuration derived from the host.
- Containers on user-defined networks normally use Docker’s embedded resolver at
127.0.0.11. - External lookups through the embedded resolver are forwarded upstream.
Seeing 127.0.0.11 is therefore not, by itself, an error.
Do not replace it with 127.0.0.1 or the host’s 127.0.0.53 stub address without understanding the network namespace. In a typical bridge-networked container, loopback refers to the container itself.
A host-local DNS listener is not automatically reachable through the same address from a container.
3. Compare the same hostname on both sides
Use the exact hostname that fails in the application.
For a Linux host with getent available:
getent hosts api.corp.example Then, if the container includes the same utility:
docker exec app getent hosts api.corp.example api.corp.example is a placeholder. Replace it with your internal fully qualified hostname.
If nslookup is available, compare direct DNS queries:
nslookup api.corp.example docker exec app nslookup api.corp.example These tools answer related but different questions. getent uses the system’s configured name-service lookup mechanisms. nslookup queries DNS. Application runtimes can also cache answers or implement their own resolution behaviour.
Record the tool alongside the result.
If the application image lacks diagnostic utilities, use an approved toolbox image on the same Docker network. For example:
docker run --rm \ --network project_default \ busybox:1.37 \ nslookup api.corp.example Replace project_default with the actual network.
This is a comparison, not an exact replica of the application container. It does not automatically inherit that container’s DNS overrides or application configuration. A failure to pull the toolbox image is also a separate problem from DNS inside an already-running container.
4. Separate DNS failure from connection failure
A hostname lookup and a connection to the returned address are different operations.
If resolution succeeds, test the actual application port using a known, current address supplied by your infrastructure team.
For example, where nc is available:
docker exec app nc -vz -w 5 10.20.30.40 443 Replace the address and port with your destination. Netcat options vary between implementations.
Interpret the result narrowly:
| Observation | Next area to investigate |
|---|---|
| Lookup fails, but the known destination IP and port are reachable | Resolver selection, DNS reachability or DNS response |
| Lookup succeeds, but the destination port is unreachable | Routing, firewall, service availability or access policy |
| Both fail | Investigate DNS and general connectivity separately |
| Lookup and TCP connection succeed, but the application fails | TLS, proxy settings, authentication or application behaviour |
A successful TCP connection does not validate TLS or application health. A failed ping does not establish that the destination is unreachable; ICMP may be blocked.
5. Query the intended resolver directly
If you know the approved DNS server for the internal domain, test it explicitly:
docker exec app \ nslookup api.corp.example 10.20.0.53 10.20.0.53 is an example address. Use the resolver assigned or approved for your network.
This separates “which resolver was selected?” from “can this resolver answer from this environment?”
Useful distinctions include:
- Timeout: no response arrived within the tool’s timeout. Routing, filtering or resolver availability may be involved.
- NXDOMAIN: the responding DNS system says the name does not exist. Check the queried name, resolver and split-DNS configuration.
- SERVFAIL: resolution failed at the responding DNS system. This is not the same as a network timeout.
- Address returned: the lookup succeeded for that query; application connectivity still needs testing.
If the same resolver answers on the host but times out from the container, investigate the path between the container and that resolver.
Do not treat switching to a public resolver as a universal fix. It may resolve public names while remaining unable to answer private ones.
6. On native Linux Engine, inspect routes and subnet overlap
The following host commands apply to native Linux networking. They are not equivalent checks for Docker Desktop’s internal VM.
ip route get 10.20.0.53 ip route get 10.20.30.40 docker network inspect project_default \ --format '{{json .IPAM.Config}}' If the container includes the ip utility, inspect its view too:
docker exec app ip route docker exec app ip route get 10.20.0.53 A container route pointing at its Docker gateway does not prove that the rest of the journey enters the VPN.
Check whether a Docker subnet overlaps a network reached through the VPN. For example, a container network and a corporate network might both use the same private address range. That creates ambiguity about where traffic should go.
Choose replacement subnets against the actual routes in your environment. There is no private range that is guaranteed to be conflict-free everywhere.
Also inspect VPN and host firewall policies affecting forwarded container traffic. A rule that permits a connection originating on the host may not permit the forwarded path.
Avoid flushing firewall rules or disabling the VPN kill switch as a general troubleshooting step. Those changes alter the conditions you are trying to understand.
Rootless Docker introduces another networking layer; do not assume a rootful bridge-network diagnosis applies unchanged.
7. On Docker Desktop, account for the VM and backend
Docker Desktop runs Linux containers inside a VM. Network traffic reaches the host through Desktop’s networking components.
That means a successful host lookup does not test the whole container-to-resolver path.
Check:
- Whether the VPN or endpoint policy permits Docker Desktop’s networking backend.
- Whether the failure occurs only after connecting or reconnecting the VPN.
- Whether it affects internal domains, public domains or both.
- Whether the configuration depends on IPv4, IPv6 or an HTTP proxy.
- Whether the affected container predates the network change.
On macOS and Windows, do not expect a host-side docker0 interface to behave like native Linux Engine networking.
If restarting Desktop changes the result, record that observation. It suggests a state-dependent interaction worth investigating; it does not establish a permanent fix or identify the responsible component.
8. Check short names and Compose service names separately
Suppose api.corp.example resolves but api does not.
That points toward search-domain behaviour rather than a complete DNS outage.
An explicit Compose configuration can look like this:
services: app: image: your-app:your-tag dns: - "${CORP_DNS_IP}" dns_search: - corp.example Set CORP_DNS_IP to an approved resolver reachable from the container environment. This is a configuration example, not a recommendation to override DNS before diagnosis.
A static resolver list may also be insufficient for domain-specific split-DNS requirements.
Compose service discovery is another case. A service named db normally needs shared network membership with the service querying it. A corporate DNS server should not be expected to resolve Docker-only service names.
Check whether you are debugging:
- An external public hostname.
- An internal corporate hostname.
- A short name requiring a search suffix.
- A Compose service name.
Do not apply the same fix to all four.
9. Retest the application after a controlled change
Once the evidence supports a change, apply it to the affected service and retest.
For Compose configuration changes:
docker compose up -d app This can recreate the service when necessary and may interrupt it. A plain docker compose restart does not apply changes made to the Compose configuration.
Then repeat:
- The hostname lookup.
- The destination-port check.
- The actual application request.
Where permitted, repeat after a VPN reconnect or network change. Record what changed and what remained stable.
Avoid describing a one-time successful lookup as proof that the configuration works across every connection state.
Finish with a finding you can explain
“Docker DNS is broken” is a symptom.
A more useful finding is:
The host resolves the internal hostname through the corporate resolver. The application container times out when querying that same resolver directly. We need to investigate resolver reachability from the container network.
Or:
The fully qualified name resolves inside the container, but the short name fails. We need to check the search-domain configuration.
Or:
DNS resolution succeeds, but the application port is unreachable. The remaining failure is beyond name resolution.
Each statement narrows the next step.
Start with the affected container, separate resolution from connectivity, and change one setting at a time. A successful host connection is valuable evidence—but it is only one part of the path.