The private API opens in your desktop browser and fails in the development container. That is not a contradiction, and it is rarely the VPN itself. The browser request and the failing request were made by different processes, with different resolvers, proxies and routes, and often from different network namespaces entirely. "On my laptop" can mean the Windows host, a WSL distribution, the Docker Desktop virtual machine and a container inside that VM, four contexts that can each answer the same hostname differently. Find out which one the failing process uses before you touch a single setting.

Locate the process before you change a setting

Start with the container engine rather than the VPN client. Ask which context the command line is talking to, and which operating system that engine runs on, because a Linux container managed by a desktop VM is a different path from one managed by a local daemon:

docker context ls
docker info --format '{{.OperatingSystem}} / {{.Name}}'
docker run --rm alpine ip route

On Windows, wsl -l -v tells you whether the distribution is running under WSL 2, which puts it behind its own virtual switch. That detail decides whether the container shares the host's routing table or sits behind a NAT that needs a forwarded port or a host name to reach the host.

Keep one request constant and compare

Pick one harmless endpoint on the private host and use it for every comparison, from the browser, from a host terminal, from the WSL distribution and from inside the container. Record four things for each attempt: the resolved address, whether a proxy was used, the failure class and the elapsed time. Name lookup, connection establishment, TLS validation and an HTTP status are four different problems, and mixing them into "it does not work" wastes a morning.

Run the comparison inside the container that is failing, with a timeout so a silent firewall does not hold the terminal for minutes. A fresh container reproduces the engine's network context but not the application container's namespace, proxy variables or resolver, so docker exec into the real one is the authoritative test and a sidecar is only a way to isolate whether the difference is the engine or the container:

docker exec -it app /bin/sh -c "getent hosts api.internal; curl -v --max-time 8 https://api.internal/ 2>&1 | tail -20"
docker run --rm --network container:app curlimages/curl:latest -sv --max-time 8 https://api.internal/ 2>&1 | tail -20

The alpine base image carries busybox and no curl, which is exactly the kind of detail that makes a copied diagnostic block fail: pick an image with the client, or add it inside the same run. A comparison against unrelated public sites isolates nothing. If https://example.com works from the container, you have learned that the container has internet access and nothing about whether it can reach a private address through the tunnel. Equally, opening a new terminal proves only that the new terminal works, not that it shares a runtime with the application that failed.

Docker Desktop can work with a VPN

Docker's own documentation on VPN and host connectivity describes this as a supported setup, and the honest summary is that it works rather than being automatically broken. Do not assume the desktop VM bypasses the host tunnel as a matter of design; on many setups the traffic follows the host's tunnel correctly, which is why the "Docker ignores the VPN" folklore produces confident and wrong advice.

What still matters is the backend in use, the client's split-tunnel policy, and the resolver configuration the VM received. A VPN client that installs routes but not DNS, or that pushes a DNS server the VM cannot reach, produces a name lookup failure inside the container while the host works because the host has an extra resolver or a cached answer. That is a resolver problem wearing a container costume. It survives every VPN reconnect you attempt, because the tunnel is already carrying the traffic you asked it to carry.

Address overlaps and MTU are different failures

Two failures deserve their own diagnosis because they are commonly mistaken for access policy. The first is an address overlap: if the VPN pushes a range that collides with a container network's default bridge subnet, the container's route for that range points at the local bridge and never reaches the tunnel. Inspect the actual subnets rather than guessing:

docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
ip route get 10.20.30.40

The second is an MTU problem. If the connection establishes and TLS completes but larger responses stall, you have a packet-size issue, not an authorization issue, and replacing DNS records will never fix it. Test with a small request and a large one before you change anything, and record both outcomes.

The fix for an overlap is a different container subnet or a scoped route, chosen by whoever owns the address plan. The fix for an MTU stall is clamping or reducing the tunnel or interface MTU to a value that survives the path, applied at the layer you control.

Proxy configuration is per process, not per machine

Inside a container, localhost names that container's own network context, so a proxy running on the host is not reachable there under that name. Docker Desktop provides host.docker.internal for host access, and on Linux you add it explicitly with --add-host=host.docker.internal:host-gateway. Either way, that name gives you a route to the host; it does not create a proxy service or authorize you to use one.

Use the proxy address and port supplied for the actual environment, and check who is reading it. Variables exported in an interactive shell do not automatically configure every editor extension, language server, build tool and background daemon. The one that matters is the process that made the failing request, so inspect its environment rather than your terminal's.

Finally, make the fix explain itself. A useful resolution names the boundary that was wrong, the setting that changed it, and the set of processes it now affects. Restarting the VPN, the editor and the engine in sequence may change the symptom, but it leaves nothing behind that the next person can use.

সম্পৰ্কিত প্ৰবন্ধ