Docker containers, when they’re running, are just tiny virtual machines with their own network interfaces. By default, Docker creates a bridge network for your containers, and each container gets an IP address from that bridge’s subnet. You’ll often need these IP addresses to connect containers to each other, or to external services that need to reach your containers.

Let’s say you have a web application running in a container (my-webapp) and a database in another (my-database). If your web app needs to connect to the database using its IP address, you’d first need to find that IP.

Here’s a simple setup to demonstrate. First, create a custom bridge network. This gives you more control over the subnet.

docker network create --subnet=172.20.0.0/16 my-custom-network

Now, run your containers on this network. For this example, let’s just run simple Alpine Linux containers:

docker run -d --name my-database --network my-custom-network alpine ash -c "while true; do sleep 3600; done"
docker run -d --name my-webapp --network my-custom-network alpine ash -c "while true; do sleep 3600; done"

To find the IP address of my-database, you can use the docker inspect command. This command provides detailed low-level information about Docker objects.

docker inspect my-database | grep IPAddress

This will output something like:

            "IPAddress": "172.20.0.2",

So, my-database is accessible at 172.20.0.2 on the my-custom-network. Your my-webapp container can now use this IP to connect.

However, relying solely on IP addresses within Docker is generally discouraged for inter-container communication. IP addresses can change if a container is stopped and restarted, or if the Docker daemon restarts. A much more robust and idiomatic Docker approach is to use container names for service discovery. When containers are on the same user-defined bridge network, Docker’s embedded DNS server automatically resolves container names to their IP addresses.

To illustrate, if you try to ping my-database from my-webapp using its IP address:

docker exec my-webapp ping -c 3 172.20.0.2

This will work. But if the IP were to change (e.g., after recreating my-database), this ping would fail.

Now, let’s try pinging by name. First, ensure you are on the same user-defined network:

docker exec my-webapp ping -c 3 my-database

This command will likely succeed, even if my-database’s IP address changes. Docker’s DNS resolves my-database to its current IP on my-custom-network. This is why using container names is the preferred method for communication between containers on the same network.

If you must use IP addresses, for example, if you’re connecting from a host machine or a container on a different network, you’ll again use docker inspect. For my-database, the command docker inspect my-database | grep IPAddress gave us 172.20.0.2. If you were on the host machine and wanted to access a service inside my-database (assuming it’s listening on a port), you’d use this IP.

If you’re using the default bridge network, finding IP addresses is similar, but the subnet will be managed by Docker. You can inspect the default bridge network itself:

docker network inspect bridge

Look for the Containers section within the network inspection. Each container attached to this network will have its IP address listed there.

The most surprising thing about Docker networking, and what trips many people up when they first start using it, is that containers on the default bridge network cannot easily communicate with each other by name. Docker’s DNS resolution for container names is only enabled for containers attached to user-defined bridge networks. So, if you run two containers without specifying a network, they’ll both default to the bridge network, and container-a won’t be able to ping container-b by name, even though they are both "on the bridge."

The fundamental problem Docker solves here is abstracting away the complexities of network configuration for ephemeral services. Instead of manually assigning IPs and managing routes, you define networks and attach containers, letting Docker handle the underlying plumbing. This makes it much easier to build distributed applications where services can be scaled, moved, and restarted without constant network reconfigurations.

When you run a container without specifying a network, it defaults to the bridge network. This is a single, global bridge network managed by the Docker daemon. While convenient for simple cases, it lacks the isolation and robust DNS resolution that user-defined networks provide. If you attach multiple containers to the default bridge network, they will all be on the same subnet, but Docker’s internal DNS service will not resolve their hostnames to each other. You’d have to resort to finding their IP addresses via docker inspect and using those, which, as discussed, is brittle.

The next concept you’ll likely grapple with is how to expose container ports to the host machine or to other networks.

Want structured learning?

Take the full Docker course →