DEV Community

Frank Zhang
Frank Zhang

Posted on Originally published at docs.opshome.run

Same Docker Network Name, But Containers Still Can’t Communicate? Here’s Why

Recently, I ran into a Docker networking issue that looked simple at first.

Two independent Docker Compose projects were running on the same host:

  • a frontend service
  • a backend API

Both Compose files declared a network called:

networks:
  app-net:
Enter fullscreen mode Exit fullscreen mode

At first glance, the setup looked straightforward.

If both containers use app-net, they should be able to communicate with each other.

But they could not.

The reason is an important Docker Compose detail:

The network name written in compose.yaml is not always the actual Docker network name.


The Example

Imagine two independent projects:

Docker Host
│
├── portal/
│   └── compose.yaml
│
└── backend/
    └── compose.yaml
Enter fullscreen mode Exit fullscreen mode

The frontend project contains:

services:
  portal-web:
    image: example/portal-web:latest
    networks:
      - app-net

networks:
  app-net:
Enter fullscreen mode Exit fullscreen mode

The backend project contains almost the same configuration:

services:
  core-api:
    image: example/core-api:latest
    networks:
      - app-net

networks:
  app-net:
Enter fullscreen mode Exit fullscreen mode

Looking only at the YAML files, it seems like both containers are connected to the same network:

portal-web ─── app-net ─── core-api
Enter fullscreen mode Exit fullscreen mode

But Docker Compose normally creates resources within the scope of a Compose project.

That changes what actually happens.


What Docker Really Creates

Compose projects are usually named after their project directories unless a project name is explicitly configured.

In this example, the projects may be called:

portal
backend
Enter fullscreen mode Exit fullscreen mode

When Compose creates the networks, it may prefix the network names with the project name.

So this:

networks:
  app-net:
Enter fullscreen mode Exit fullscreen mode

inside the portal project may become:

portal_app-net
Enter fullscreen mode Exit fullscreen mode

while the same configuration inside the backend project may become:

backend_app-net
Enter fullscreen mode Exit fullscreen mode

The real structure is therefore closer to this:

Docker Host
│
├── portal-web
│      │
│      └── portal_app-net
│
└── core-api
       │
       └── backend_app-net
Enter fullscreen mode Exit fullscreen mode

They are not connected to the same Docker network.

The network key is identical in both Compose files, but the actual network objects are different.


Verify Instead of Guessing

The easiest way to confirm this is:

docker network ls
Enter fullscreen mode Exit fullscreen mode

You may see something like:

NETWORK ID     NAME
a12bc34de567   portal_app-net
b98dc76fe543   backend_app-net
Enter fullscreen mode Exit fullscreen mode

That immediately explains why the containers cannot communicate directly.

You can also inspect a network:

docker network inspect portal_app-net
Enter fullscreen mode Exit fullscreen mode

The output includes the containers attached to it.

If portal-web appears there but core-api does not, the two services are isolated from each other.

This is why checking the actual Docker network is often more useful than repeatedly reviewing the Compose YAML.


The Cleaner Solution: A Shared Network

If two independent Compose projects intentionally need to communicate, they can share a network managed outside either project.

First, create the network:

docker network create app-shared-net
Enter fullscreen mode Exit fullscreen mode

Then configure the frontend project:

services:
  portal-web:
    image: example/portal-web:latest
    networks:
      - app-shared-net

networks:
  app-shared-net:
    external: true
Enter fullscreen mode Exit fullscreen mode

Use the same network in the backend project:

services:
  core-api:
    image: example/core-api:latest
    networks:
      - app-shared-net

networks:
  app-shared-net:
    external: true
Enter fullscreen mode Exit fullscreen mode

Now both containers attach to the same Docker network:

Docker Host
│
├── portal-web
│       │
│       ├──── app-shared-net
│       │
│       └─────────────┐
│                     │
└── core-api ─────────┘
Enter fullscreen mode Exit fullscreen mode

The important part is:

external: true
Enter fullscreen mode Exit fullscreen mode

This tells Compose:

Do not create a project-specific network. Use an existing Docker network.

That makes the relationship between the two independent projects explicit.


Why Docker DNS Matters

Once both containers share a user-defined Docker network, Docker provides internal DNS-based name resolution.

For example, the frontend can usually access the backend using the service name:

http://core-api:8080
Enter fullscreen mode Exit fullscreen mode

instead of using a container IP such as:

http://172.22.0.5:8080
Enter fullscreen mode Exit fullscreen mode

That is important because container IP addresses can change when containers are recreated.

Service names are much better identifiers.

You can test DNS resolution directly:

docker exec portal-web-1 getent hosts core-api
Enter fullscreen mode Exit fullscreen mode

A successful result may look like:

172.22.0.3    core-api
Enter fullscreen mode Exit fullscreen mode

Then test the application itself:

docker exec portal-web-1 curl http://core-api:8080/health
Enter fullscreen mode Exit fullscreen mode

If the health endpoint responds, you have confirmed:

Shared Network  ✓
Docker DNS      ✓
TCP Connection  ✓
Application     ✓
Enter fullscreen mode Exit fullscreen mode

Port Publishing Is a Different Thing

Another common source of confusion is Docker port mapping.

For example:

ports:
  - "8080:8080"
Enter fullscreen mode Exit fullscreen mode

means the service is exposed through the Docker host:

Host:8080
   │
   ▼
core-api:8080
Enter fullscreen mode Exit fullscreen mode

It does not automatically mean another container can resolve core-api through Docker DNS.

Host port publishing and container-to-container networking are separate concepts.

For internal service communication, sharing a Docker network and using service names is usually cleaner.


A Simple Troubleshooting Workflow

When two Docker containers cannot communicate, these checks usually find the problem quickly.

First, confirm the containers are running:

docker ps
Enter fullscreen mode Exit fullscreen mode

Then check the real networks:

docker network ls
Enter fullscreen mode Exit fullscreen mode

Inspect the intended network:

docker network inspect app-shared-net
Enter fullscreen mode Exit fullscreen mode

Make sure both containers appear in the network.

Then test DNS:

docker exec portal-web-1 getent hosts core-api
Enter fullscreen mode Exit fullscreen mode

Finally, test the application port:

docker exec portal-web-1 curl http://core-api:8080
Enter fullscreen mode Exit fullscreen mode

This sequence helps separate different types of failures:

Docker Network
      ↓
Docker DNS
      ↓
TCP Port
      ↓
Application
Enter fullscreen mode Exit fullscreen mode

If DNS resolution fails, debugging the application itself is probably too early.


The Key Lesson

The main lesson from this issue is simple:

Two Docker Compose files can use the same network key without actually sharing the same Docker network.

This:

networks:
  app-net:
Enter fullscreen mode Exit fullscreen mode

may result in:

portal_app-net
backend_app-net
Enter fullscreen mode Exit fullscreen mode

Those networks are isolated.

If independent Compose projects need direct communication, check the actual Docker network objects and deliberately attach both projects to the same shared network.

The commands worth remembering are:

docker network ls
docker network inspect <network>
docker exec <container> getent hosts <service>
Enter fullscreen mode Exit fullscreen mode

Docker networking becomes much easier to troubleshoot once you stop looking only at compose.yaml and start checking what Docker actually created.

Top comments (2)

Collapse
 
raknaos profile image
Raknaos

The trap is that the network key is a name inside a project, not an address, so two projects declaring app-net are declaring two different things that happen to look identical in the YAML. My addition to your workflow: docker inspect <container> --format '{{json NetworkSettings.Networks}}' beats docker network inspect for this specific bug, because it answers the question you actually have - which network object is this container attached to - instead of listing a network and hoping you spot the container in it.

Worth noting the one legitimate shortcut too: networks: app-net: external: true in both compose files makes the sharing explicit and fails loudly if the network doesn't exist, which is a better default than silently getting two prefixed networks and debugging DNS for an hour. Your getent hosts step is the right first check for the same reason - it separates "not attached" from "attached but misconfigured" before the app gets blamed.

Collapse
 
frankzhang profile image
Frank Zhang

Good point — especially using docker inspect to verify the container’s actual network attachment. That’s more direct than starting from docker network inspect.
And yes, external: true is a clean way to make cross-project network sharing explicit. Thanks for adding this.