Containers see each other: where Docker networks isolation breaks
By default, containers end up on the same network, where each participant resolves the others by service name and can connect to any exposed port.
While everything works normally, this is convenient: the application finds the database and cache without manually configuring addresses.
But Docker networks isolation is not configured at all here — a flat container network turns any compromised service into an entry point to the rest of the infrastructure.
What is actually accessible from inside: a database on its port, cache and task queue, service addresses for metrics and liveness checks, admin panels. Some of them respond without authentication or with a shared password that lies nearby — in the same service description file.
Container isolation at the network level is absent: access between containers is limited by nothing except knowing the name.
Application-level rules do not close this hole. A process inside a container already holds valid tokens and credentials for its service, and it accesses not through the user interface but directly to the network socket.
Roles and permission checks describe what a service is allowed to do with data, but they do not prevent it from connecting to someone else's port and reading what is exposed there.
Container network segmentation is built by dividing services into zones: external web layer, application logic, data.
Only the entry point publishes ports outward, while storage lives on a network with no route outside the host and is accessible only to services that truly need it. Below we analyze how this is configured and where mistakes most often happen.
What segmentation gives: fewer entry points and predictable traffic
Container network segmentation is not cosmetic configuration but explicitly defined trust boundaries for services. While all containers are on one network, each sees the others by name and can open a connection to any: the attack surface equals everything running on the node.
In Docker, the default network unites the entire stack, so the first step is to separate roles by purpose. What we check after separation:
- Attack surface. From a container where someone else's code has landed, only network neighbors are visible: instead of dozens of names and ports, two or three addresses remain. Only a single entry publishes ports outward.
- Predictable traffic. Connections are described explicitly: a service accesses the database by a fixed name, not "wherever it happens to work." You read the communication scheme from configuration rather than reconstructing it from logs.
- Environment separation. The development stand and production contour do not intersect: a test container will not see the production database even if there is an error in environment variables.
- Service trust boundaries. Components with different access requirements are no longer on the same plane — the administrative interface is unreachable from the public contour.
- Incident investigation. An anomaly is localized by network: you can see which pair of services started communicating abnormally, and the search narrows to one or two components.
We verify the rules with connectivity tests: allowed pairs respond, forbidden ones stay silent. Otherwise segmentation remains in the diagram but not in the working system.
Comparison of modes: default, user-defined, host, and overlay network
The network mode is chosen by task, not by habit: the same configuration behaves differently on a developer's stand and in a cluster. Below is a comparison of the four options that Docker provides, by applicability conditions and limitations.
| Option | When it fits | Limitations |
|---|---|---|
| Default | single containers, manual runs | no name resolution between containers, weak segmentation |
| User-defined network | group of application services, access by name | network composition is described in advance, access is configured manually |
| host mode | services sensitive to latency, node monitoring agents | node ports are occupied, network isolation is lost, one instance per port |
| overlay network | services on multiple cluster nodes | traffic encapsulation and the cluster control layer add complexity |
When the mode hurts
host on a node with several applications leads to port conflicts and removes segmentation. overlay for a single node only adds encapsulation overhead without an isolation gain.
Multiple networks per service
A single service can be a member of several networks at once: the external one receives traffic from the reverse proxy, the internal one is only for exchange with the database. The proxy does not see the database, and the application does not publish a port outward.
Work order: from service inventory to handover of diagrams
We structure the work order so that the network does not turn into a tangle: first we record the actual connections between services, then design segments, and only then change configuration on live contours.
-
Inventory of services and connections. We collect a list of containers, ports published outward, and "who talks to whom" requests. Sources are launch files, logs, and a dependency map from developers. The output is a connection table indicating port and direction.
-
Network scheme design. We divide services into segments by role: external layer, application layer, data storage, auxiliary tasks. For each pair of segments, we mark which connections are allowed and which must be closed by default.
-
Configuring container networks. We bring up a separate network for each segment, and connect a service only to the networks it truly needs. Port publication is selective, through the reverse proxy, rather than outward from every container indiscriminately.
-
Access rules between segments. We open inter-network connections by name and where they are justified by the task. Everything else is closed — so a new vulnerability in one service does not grant access to neighbors.
-
Isolation testing on the stand. We run scenarios: try to reach storage from an external container, inspect available ports, check behavior on restart. Every forbidden connection must give an explicit refusal, not "accidentally work."
-
Rollout to the production contour and monitoring. We transfer the scheme step by step, with checks after each step, so as not to catch a service failure halfway through. We remove disabled networks and extra connections.
-
Handover of documentation and regulations. We hand over the network diagram, connection map, procedure for connecting a new service, and diagnostic commands for failures.
A recorded scheme lives longer than team memory: a new service is deployed according to the regulation, not by trial and error, and after each change the composition of segments remains predictable.
What the team retains: network diagrams, policies, regulations
A working stand is not yet the result: six months later the team changes a service, and no one remembers why the frontend sees the database directly rather than through the internal network.
Therefore, along with the environment, we hand over artifacts by which the network configuration can be read, reproduced, and developed without us — including the order in which such changes are made at all.
- Network and connection diagram — which networks are created, which services belong to them, what is published outward, and what is intentionally closed. The diagram is reconciled with the actual state, not the original intent.
- Network configuration files — a declarative description of networks, aliases, and connections, from which the environment comes up with a single command on a clean machine.
- Access policies — a table of the form 'service → service → allowed or denied,' tied to network names: a rule changes in one place and does not spread across machines.
- Infrastructure description — nodes, entry points, external addresses, reverse proxies, and what is critical for the fault tolerance of each contour.
- Network regulation — who approves a change, at what point it is tested on the stand, and how it is rolled back on failure.
- Procedure for adding a new service — a step-by-step list: which network to connect it to, which names to set, which checks to pass before release.
- Diagnostics memo — typical failures such as an unresolved name or occupied port, what to look at first, and how to distinguish a configuration error from a service failure.
Each document is updated together with a change, otherwise it loses meaning: let us agree on who on your team keeps them current.
Service isolation in production: a case with metrics before and after
The client came with an environment of about fifteen services on one default network: frontend, API, background workers, cache, and database all saw each other directly.
The causes of production incidents were searched for blindly in logs — it was impossible to determine which container was accessing the database bypassing the API.
We separated traffic into distinct user-defined networks: one only for the proxy and frontend, a second for the API and workers, a third for the database and cache, to which only the application layer is connected.
Instead of addresses — network aliases; instead of a shared default network — an explicit list of connections for each service.
What the before-and-after measurements showed
| Metric | Before | After |
|---|---|---|
| Share of incidents due to extra network connectivity | 4 out of 10 | 1 out of 10 |
| Time to localize the source of failure | 3–4 hours | 40–60 minutes |
| Service recovery time after rollback | 25–30 minutes | 8–10 minutes |
| Services on one network | 14 | 3 |
Limitations surfaced immediately: a legacy service with a hardcoded local address could not be moved to the isolated network and remained a separate group marked on the diagram.
Changing an alias requires restarting the container, and availability checks now go through the application network — they were rewritten for the new topology.
What to do if containers already run on one flat network?
A flat network is not torn apart in one maintenance window. The working order is to overlay segmentation on top of the current addressing, migrate one service, make sure it responds on the new address, and only then take on the next one.
The production environment continues serving requests: old names resolve until the new contour has been tested on live traffic.
The first step is connection inventory: who actually exchanges data with whom, not who should according to documentation. We look at active connections and access logs for a representative period, including rare scheduled tasks.
Services between which there is no shared data are separated into different networks first — these are the safest migration steps.
The migration order is from periphery to core: first the external proxy, metrics collection, auxiliary tasks, then services with a single dependency, and finally storage and queues, where a broken connection costs the most.
At each step, the container is connected to both networks for some time: neighbors manage to move to new names, and the request flow is not interrupted.
Changing service addressing is the main source of failures. If a neighbor's address is hardcoded in the configuration, after the migration the request goes nowhere, and the service that fails is not the one being migrated.
Therefore, we replace explicit addresses with network aliases in advance, before the switch itself, and describe access rules between segments explicitly rather than inheriting them from the shared space.
Network migration risks include loss of name resolution, forgotten rules between contours, and services that have not been deployed for a long time and live on old settings.
Therefore, we keep the reverse path open until the end of observation: returning a container to its former network is one operation, not an emergency investigation.
Common technical doubts: service names, ports, latency
Inside a user-defined network, containers find each other by service name: the built-in name resolver returns the current address, and there is no need to use IP — when a container is recreated, the name remains and the address changes.
Only entry points are published outward; other services communicate over internal ports and are inaccessible from outside.
Splitting into subnets does not introduce noticeable latency: traffic goes through a bridge on the host, and the increase is fractions of a millisecond compared with database response time.
Below are the things most often clarified before start: the answers determine network configuration and access rules.
How will services find each other if addresses change on restart?
Inside a user-defined network, the built-in resolver works: a service name or network alias always points to a live container. You must not hardcode IPs in configuration — after a container is recreated, the application will wait for a response until it errors out.
What should be published outward and what should remain inside?
Only entry points are exposed outward — the reverse proxy and, during debugging, service interfaces. The database, cache, and queues live on the internal network without port publication: only services connected to the same network have access to them.
Will isolation slow down exchange between services?
No. Isolation is routing rules on the host, not an extra network segment. Latency increases by fractions of a millisecond and is lost against the background of a database query; the difference is visible only in synthetic measurements.
How can you verify that isolation actually works?
With a reverse test: we bring up a temporary container on one network and try to reach a service from another — the connection must not be established. We compare docker network inspect and the list of networks on the container with the design scheme.
Let's discuss your network scheme: what to send for assessment and a plan
Assessing network configuration hinges not on the number of containers but on which connections between them are actually needed. The more complete the input data, the fewer clarification rounds and the faster we will return with a scope of work and an implementation plan. To discuss the task substantively, it is enough to send a few things.
- A list of services and their roles: what runs continuously, what comes up for tasks, and what remains from previous versions.
- Current connections: who communicates with whom, over which ports, which services are exposed outward, and which are already closed.
- Target isolation: which groups must not see each other and what must travel strictly inside the contour.
- Environment requirements: what must be separated between the test and production contours, where a shared network is acceptable and where it is not.
- Work window constraints: how long services can be down, during which hours changes are allowed.
- Expected result: what we consider done — for example, access to databases from outside is closed, and the reverse flow to them is predictable.
- Organizational framework: who approves changes and which security rules apply on your side.
From this set, we will assemble a connection map and propose a segmentation scheme: where a separate bridge network with an explicit list of participants is needed, where an internal network without outbound access is needed, and where a dedicated contour for service traffic is needed.
After discussing the task, we will return with an assessment of the network configuration — stages, rollback points, and the work window we can fit into.







