✦ For everyone, free.

Practical knowledge for real and everyday life

Home

17.3.1.3 Explicit Compose Networks

A focused guide to Explicit Compose Networks, connecting core concepts with practical Docker and container operations.

Explicit Compose networks means deliberately defining and naming a stack's networks rather than relying on the implicit default network Compose creates automatically, a best practice that turns network topology into visible, intentional, version-controlled architecture rather than an invisible, default behavior a reader has to already know about to even realize exists.

The implicit default is invisible to a reader

A Compose file with no networks section at all still has networking, every service shares one automatically created, unnamed default network, but nothing in the file itself indicates this; a reader unfamiliar with Compose's default behavior would have no way to know from the file alone that every service is in fact networked together:

services:
  api:
    image: my-api
  db:
    image: postgres
services:
  api:
    image: my-api
    networks:
      - app-network
  db:
    image: postgres
    networks:
      - app-network

networks:
  app-network:

The second version makes the networking relationship explicit and visible directly in the file, which is more verbose but considerably more self-documenting, especially for anyone reading the file without already knowing Compose's implicit default behavior by heart.

Segmenting public-facing and internal-only services deliberately

Defining more than one network and deliberately scoping which services attach to which is a best practice baseline worth applying even for stacks that do not currently have any pressing security requirement driving it, since it establishes a clear, intentional boundary that is easy to extend later as the stack's actual security needs grow:

services:
  proxy:
    networks:
      - public
      - internal
  api:
    networks:
      - internal
  db:
    networks:
      - internal

networks:
  public:
  internal:

This structure makes explicit, directly in the file, that db has no path to the public-facing network at all, which is a meaningful, self-documenting architectural statement rather than something that happens to be true only because no one has published a port for it yet.

Naming networks to reflect their actual purpose

Just as service names should reflect role, network names should reflect their actual purpose within the architecture, public-facing, internal, a specific data tier, rather than a generic or technically-derived name that conveys no information about what the network boundary actually represents:

networks:
  network1:
  network2:
networks:
  public:
  data-tier:

The second set of names immediately communicates the architectural intent behind each network's existence, which the first set does not, requiring a reader to trace through every service's network attachments to reconstruct the same understanding the names themselves could have conveyed directly.

Documenting the network topology for complex stacks

For a stack with several networks and a non-trivial segmentation strategy, a brief comment or accompanying diagram summarizing the overall network topology, which services can reach which others, and why that boundary exists, provides considerably faster understanding than reconstructing the topology by tracing through every individual service's network attachments:

# Network topology:
#   public: internet-facing traffic only, reaches proxy
#   internal: service-to-service and data tier, no internet access
networks:
  public:
  internal:

Avoiding unnecessary network proliferation

While explicit, deliberate network segmentation is valuable, defining a separate network for every individual pair of services that need to communicate, rather than a smaller number of networks reflecting genuine, meaningful security or architectural boundaries, adds configuration complexity without a corresponding benefit:

networks:
  api-to-db:
  api-to-cache:
  worker-to-db:

This level of granularity, a separate network for every individual communication pair, is generally excessive; a smaller number of networks reflecting actual, meaningful trust boundaries, public-facing versus internal, or separate networks per genuinely distinct subsystem, achieves the real security and architectural benefit with considerably less configuration overhead.

External networks for cross-project communication

When a network genuinely needs to be shared across separate Compose projects, defining it as external, created independently outside of any single project's own lifecycle, makes that cross-project relationship explicit and intentional rather than an accidental, fragile dependency on one specific project's automatically generated network name:

docker network create shared-services-network
networks:
  shared:
    external: true
    name: shared-services-network

This explicit, deliberately created external network is a clearer, more robust foundation for genuine cross-project communication than relying on one project's internal, automatically generated network being reachable by another.

Common mistakes

  • Relying on Compose's implicit default network without ever defining and naming networks explicitly, leaving the stack's actual networking relationships invisible to anyone reading the file without prior knowledge of Compose's default behavior.
  • Not segmenting public-facing and internal-only services even when doing so would establish a clear, low-cost architectural boundary worth having from the outset.
  • Naming networks generically or technically rather than according to their actual architectural purpose.
  • Not documenting network topology for a stack complex enough that tracing it manually through every service's attachments becomes genuinely difficult.
  • Defining an excessive number of narrowly scoped networks rather than a smaller number reflecting genuine, meaningful trust boundaries.

Explicit Compose networks turn an otherwise invisible, implicit default into visible, intentional, version-controlled architecture, and the practice of deliberately defining, naming, and segmenting networks according to genuine trust boundaries, even before any pressing security requirement specifically demands it, establishes a clearer, more self-documenting foundation that is considerably easier to extend than retrofitting segmentation onto a stack that has always relied on one undifferentiated, implicit network.