AI Disclosure: AI helped turn live debugging notes into a polished write-up. The problem, commands, and final fix came from the actual session.
Sometimes the internet fails before you even get to the internet.
I was on an ICE train in Germany, connected my Ubuntu laptop to WIFIonICE,
and waited for the login page to appear.
Nothing.
The WiFi was there. The laptop associated with the access point. But the captive portal did not open, and going to the usual login page did not help either.
Then I remembered a problem I had seen before: Docker and train WiFi can fight over private IP ranges.
The Symptom
The normal path should be simple:
nmcli dev wifi connect WIFIonICE
xdg-open http://login.wifionice.de
If the captive portal does not appear automatically, Deutsche Bahn tells you to
open the WIFIonICE login page directly. After that,
the ICE WiFi service is normally available as well.
But captive portals often live on private network ranges. And Docker also likes private network ranges.
On my laptop the route table showed Docker-owned networks like this:
172.17.0.0/16 dev docker0
172.18.0.0/16 dev br-...
172.19.0.0/16 dev br-...
That is the interesting part. If the train expects traffic for something in
172.18.x.x to go to the train router, but Linux already has a Docker bridge
claiming 172.18.0.0/16, the packet never leaves the laptop. It goes to Docker.
Means: you are connected to the train WiFi, but the login page is behind a route your laptop has accidentally reserved for itself.
How To Confirm It
First look at the IPv4 routes:
ip -4 route show
Then look at the Docker networks:
docker network ls
And inspect the bridge networks:
docker network inspect bridge
For Docker Compose-created networks, inspect the specific network names you see
in docker network ls:
docker network inspect <network-name>
If you see Docker owning 172.17.0.0/16, 172.18.0.0/16, or adjacent ranges,
you have a plausible explanation for the broken captive portal.
You can also ask Linux where a portal-looking address would go:
ip route get 172.18.0.1
If that resolves to a local Docker bridge or to lo, your laptop is not sending
that traffic to the train network.
The Quick Fix
When you are on the train and just need the login page to work, stop Docker:
sudo systemctl stop docker
nmcli dev wifi connect WIFIonICE
xdg-open http://login.wifionice.de
You can also try a plain HTTP site to trigger the captive portal:
xdg-open http://neverssl.com
This is the fix I would use while traveling. It is easy to understand and easy to undo:
sudo systemctl start docker
If no containers are running, this is usually harmless.
The More Surgical Fix
If Docker is running but the conflicting networks are old Compose networks you do not currently need, remove those networks:
docker network rm <old-compose-network>
Then reconnect to WIFIonICE and open the login page again.
This can work, but it is less obvious under pressure. You need to know which Docker networks are safe to remove. Docker Compose will usually recreate its project networks later, but I would still rather stop Docker first when sitting on a train with flaky connectivity.
The Permanent Fix
The better long-term fix is to move Docker away from the address ranges that commonly collide with VPNs, captive portals, office networks, trains, hotels, and other shared networks. Docker exposes this through its daemon configuration.
Create or edit /etc/docker/daemon.json:
{
"bip": "10.255.0.1/24",
"default-address-pools": [
{
"base": "10.250.0.0/16",
"size": 24
}
]
}
Validate the config:
sudo dockerd --validate --config-file=/etc/docker/daemon.json
Then restart Docker:
sudo systemctl restart docker
One important detail: Docker’s default address pools apply to newly created networks. Existing networks keep their old subnets.
So after changing the daemon config, remove old Compose networks that were created in the conflicting ranges:
docker network rm <old-compose-network>
The next time Compose creates them, Docker should allocate them from the new pool.
The Takeaway
If an Ubuntu laptop connects to WIFIonICE but the login page does not load,
do not only debug the browser. Check the route table.
If Docker owns the same private IP range that the train portal wants to use, the browser is not the problem. The packet is taking the wrong local route.
Stop Docker for the quick travel fix. Move Docker’s default address pools for the permanent fix.