---
name: docker-networking-troubleshooting
description: Fix Docker container networking issues including host.docker.internal failures, port conflicts, and Tailscale Funnel coexistence
category: devops
---

# Docker Networking Troubleshooting

## Trigger
Container can't reach other services, `host.docker.internal` fails, port conflicts, or networking mode mismatches.

## Pitfalls

### `host.docker.internal` doesn't resolve in all Docker setups
On some Debian/Ubuntu setups, `host.docker.internal` fails inside containers with "Name or service not known".

**Fix:** Use the Docker bridge gateway IP instead:
```bash
docker inspect <network_name> --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'
```
Typically `172.17.0.1` or `172.19.0.1`. Use this in env vars like `OLLAMA_BASE_URL=http://172.19.0.1:9292`.

### `network_mode: host` conflicts with port-binding services
If two services need the same port (e.g., Open WebUI on 8080, SearXNG on 8080), `network_mode: host` will fail with "address already in use".

**Fix:** Use bridge networking with explicit port mapping:
```yaml
ports:
  - "127.0.0.1:3081:8080"
```
This maps the container's internal port to a different host port, avoiding conflicts.

### Tailscale Funnel owns port 443
Tailscale Serve+Funnel listens on the Tailscale IP (`100.x.x.x:443`) and proxies to local services. **Do not** configure Nginx to listen on 443 for the same host — Tailscale will conflict.

**Pattern:**
- Tailscale: `100.x.x.x:443` → `127.0.0.1:<port>` (HTTPS, public via Funnel)
- Nginx: `0.0.0.0:80` → `127.0.0.1:<port>` (HTTP, LAN access)
- They coexist because Tailscale binds to the Tailscale IP, not `0.0.0.0`

### `docker compose up -d` triggers terminal long-lived process detection
The terminal tool flags `docker compose up -d` as a long-lived process.

**Fix:** Use background mode:
```bash
terminal(command="cd /path && docker compose up -d", background=true, notify_on_complete=true)
```

## Steps to diagnose container networking
1. Check container network mode: `docker inspect <container> --format '{{.HostConfig.NetworkMode}}'`
2. Check port bindings: `docker inspect <container> --format '{{range $p, $c := .NetworkSettings.Ports}}{{$p}} -> {{(index $c 0).HostIP}}:{{(index $c 0).HostPort}} {{end}}'`
3. Find bridge gateway: `docker inspect <network> --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'`
4. Test connectivity from inside container: `docker exec <container> curl -v http://<target>:<port>`
5. Check what's listening on host: `ss -tlnp | grep <port>`

### UFW blocks Docker → Host traffic

When UFW is active with default `deny (routed)`, Docker containers on compose bridge networks cannot reach services on the host. The container resolves the IP correctly but packets are dropped by UFW's FORWARD chain.

**Diagnosis:** `docker exec <container> curl -v --connect-timeout 5 http://host:port` → connection timeout after `Trying <IP>...`

**Don't use bridge-interface rules** — Docker creates a new bridge with a random suffix every `docker compose down` + `up`, breaking interface-name-based rules.

**Use subnet-based INPUT rules instead** (stable across recreates):
```bash
sudo ufw allow proto tcp from 172.18.0.0/16 to any port <port>
sudo ufw allow proto tcp from 172.19.0.0/16 to any port <port>
sudo ufw allow proto tcp from 172.20.0.0/16 to any port <port>
sudo ufw allow proto tcp from 172.21.0.0/16 to any port <port>
```

These go through INPUT chain, not FORWARD, so `deny (routed)` doesn't apply.

### `docker compose create` + `start` bypasses pull timeout
If `docker compose up -d` times out because the image pull is slow:
```bash
# Pull first (in background)
docker pull ghcr.io/some/image:tag

# Create + start containers
docker compose create <service>
docker compose start <service>

# Or pull then up
docker pull ghcr.io/some/image:tag 2>&1
docker compose up -d  # Fast now
```
