Docker DNS and Service Discovery Bugs in Self-Hosted AI Stacks
Many self-hosted AI outages begin with something simple: one container cannot find another. Docker networking is straightforward enough to get you into trouble. Containers on the same user-defined network can reach each other by service name. Containers not on the same network usually cannot.
The most important thing to understand: localhost inside a container means that container, not the host machine, not the database, not the inference engine three lines away in your compose file.
This mistake appears constantly in AI stacks because these setups combine many services: web UI, API server, embeddings worker, database, cache, and maybe a model server like Ollama. Each service assumes it can reach the others, but the network topology determines whether that assumption is true.
Common failure pattern: your app connects to localhost:5432 for Postgres, but Postgres runs in a separate container. The app starts, fails the database connection immediately, and crashes. The fix is to use the Docker Compose service name instead:
DATABASE_URL: postgres://user:pass@db:5432/mydb
Where db is the service name in your compose file. Docker's internal DNS resolves service names to container IPs automatically, but only within shared networks.
Shared networks matter. By default, Docker Compose puts all services in a project-level network. If you run separate docker run commands without specifying a network, containers land on the default bridge and cannot resolve each other by name.
To check network membership:
docker network ls
docker network inspect <network_name>
docker inspect <container_name> | grep -A 20 Networks
If two services that need to communicate are on different networks, add them explicitly:
services:
app:
networks:
- internal
db:
networks:
- internal
networks:
internal:
Port publishing confusion causes related problems. Publishing a port with -p 8080:8080 exposes it on the host. That is useful for reaching the service from outside Docker. It is not necessary for container-to-container communication. Internal traffic should stay on the Docker network without crossing the host.
DNS caching is another subtle issue. Docker's embedded DNS resolves service names dynamically, but some applications cache DNS aggressively and do not re-resolve after a container restarts. If a dependency restarts and gets a new internal IP, the app may stay stuck on the old address until it also restarts. For production AI deployments, design for dependency restarts or use health checks that trigger re-initialization.
A minimal debugging sequence: check that both containers are on the same network, verify service names match what the app expects, test connectivity with a temporary container, and confirm the target service is actually listening on the expected port inside the container, not just binding to localhost.
Service discovery in self-hosted AI stacks is rarely magic. It is usually a naming problem. The network is already there. The question is whether the names match and the services share it.
Debugging DNS resolution in a running stack
When a container cannot resolve another service by name, the first step is to confirm they share a network. docker network inspect <network_name> lists all connected containers. If a service is absent, add it to the correct network in your Compose file or with docker network connect.
The second step is to test DNS resolution from inside the failing container. Use docker exec -it <container_name> sh to open a shell, then try nslookup db or ping db. If the name does not resolve, the container either lacks DNS access or is on the wrong network.
The third step is to check whether the target service is actually listening. A service can be on the right network and resolvable by name, but still reject connections if it is binding to 127.0.0.1 instead of 0.0.0.0. Check the application's listen configuration, not just the Docker network.
Multi-stack scenarios
When services span multiple Compose files — for example, a model server defined in one project and a web UI in another — they land on separate networks by default and cannot resolve each other by name. The clean solution is to create an external Docker network shared by both stacks.
Define it once: docker network create shared-ai. Reference it in each Compose file under networks.shared-ai.external: true. Both stacks can now reach each other using service names on that shared network without needing to know each other's IP addresses.
Keeping it maintainable
DNS and networking issues compound quickly in AI stacks that grow organically. A model server added as a quick experiment, a vector database spun up separately, a web UI from a different project — each addition can introduce new networking assumptions.
Document your network topology as you build. A simple diagram or a README note listing which services are on which network takes ten minutes and saves hours later. The debugging cost of a misconfigured network is always higher than the documentation cost of a well-understood one.
To connect this topic to the rest of a small self-hosted stack, continue with Why Your Self-Hosted AI Container Keeps Restarting and Self-Hosted Uptime Monitoring: A Practical Guide for Small Infrastructure.
Sources
This article is for general information only and should not be treated as professional advice.