How do you block container-to-container traffic on a Docker bridge network, and how does docker0 differ from a bridge you create?
answer
- Peers on one bridge can reach every port by default
- One knob on the daemon, one on the network
- The network's version is fixed at create time
- Published ports and egress are unaffected
- Names still resolve; the connection is what dies
basics
~20 sDisable inter-container communication. For the default docker0 bridge it is the daemon-wide setting: dockerd --icc=false, or "icc": false in daemon.json. For a bridge you create it is a driver option fixed at creation: --opt com.docker.network.bridge.enable_icc=false.
solid answer
~50 sBy default any two containers attached to the same bridge network can open connections to each other on every port, whether or not it is published. Turning that off is called disabling inter-container communication, and it lives in two different places. For the default bridge it is a daemon setting — `dockerd --icc=false`, or `"icc": false` in `/etc/docker/daemon.json` — which applies engine-wide and needs a restart. For a network you create it is a driver option given at creation: `docker network create --opt com.docker.network.bridge.enable_icc=false`, fixed for that network's life. In both cases the daemon installs a rule dropping traffic bridged from the network back onto itself. What still works is everything else: published ports remain reachable from outside the host, outbound traffic still leaves through NAT, and container names still resolve, because Docker's embedded resolver is not another container on the bridge — which makes the symptom look like a hung connection to a name that resolved perfectly.
code
bash · 8 linesdocker network create --opt com.docker.network.bridge.enable_icc=false digest-jobs
docker run -d --name digest-worker --network digest-jobs \
ruby:3.3-slim-bookworm ruby -run -e httpd . -p 4567
# the name resolves, but the connection does not open
docker run --rm --network digest-jobs alpine sh -c \
'getent hosts digest-worker; wget -T 3 -qO- http://digest-worker:4567 || echo blocked'go deeper
Know the default: two containers on the same Docker bridge network can reach each other on any listening port, published or not. That is why an unpublished database port is not private from its neighbours.
Explain where each knob lives — the daemon-wide icc setting for docker0 versus the com.docker.network.bridge.enable_icc driver option set when you create a network — and that the second one cannot be changed later.
Show the operational picture: what still works after the cut (published ports, egress, name resolution), the confusing symptom that produces, and when network layout is the better answer than a blunt per-network switch.
Own the policy: decide where multi-tenant workloads share a host, what the default network posture is for the fleet, and be honest that Docker's bridge options cannot express per-pair rules — that requirement belongs to a different layer.
## What ICC means "Inter-container communication" is the daemon's term for containers on the same bridge network being able to talk to each other. It is **on** by default, and it is more permissive than people expect: two containers on the same bridge can reach each other on **any** listening port, not only the ones declared with `EXPOSE` or published with `-p`. A container listening on 6379 with no published port is fully reachable from every peer on its bridge. On the default bridge that means every container on the host that did not ask for a network — including unrelated workloads — is a peer that can connect to your service. ## The two knobs **Default bridge (docker0):** a daemon setting. ``` { "icc": false } ``` in `/etc/docker/daemon.json` (equivalently `dockerd --icc=false`), applied at daemon start. It is engine-wide and changing it restarts the daemon, so it is a decision about the host, not about one application. **A bridge you create:** a driver option, set when the network is created and immutable afterwards. ``` docker network create --opt com.docker.network.bridge.enable_icc=false digest-jobs ``` Because it is per network, you can run one locked-down network beside ordinary ones on the same host. Because it is immutable, changing your mind means creating a replacement network and re-attaching the containers. ## How it is enforced, and what survives The daemon installs a rule that drops packets forwarded from the bridge back out of the same bridge — traffic between two containers on that network. That is a narrow cut, and four things deliberately keep working: * **Published ports.** `-p 8080:4567` still maps a host port and still accepts traffic from outside the host. Disabling ICC is not a substitute for not publishing something. * **Outbound traffic.** Containers still reach the internet and the host's other services through the usual masquerading; that is what `--internal` turns off, and it is a different control. * **Name resolution.** On a user-defined network, container names still resolve. Docker's embedded resolver is provided by the daemon inside the container's own namespace, not by a peer on the bridge, so lookups succeed while connections are dropped. * **Anything sharing a namespace.** A container started to share another container's network namespace is not a separate bridge peer at all. That combination produces the diagnostic signature: a name resolves to an address, and the connection to it times out. Engineers who assume "DNS answered, so the network is fine" chase the wrong thing for an hour. ## Where it is genuinely useful A 47-node CI fleet runs jobs from many teams, several containers per node, all on one job network. A Ruby email-digest builder's test job has no business connecting to a Postgres container belonging to another team's job on the same host, and there is nothing to stop it: neither container publishes a port, and both are on the same bridge. Creating the job network with `enable_icc=false` means each job's containers can still be reached by the runner through published ports and can still fetch dependencies outbound, but a job container cannot open a socket to a peer it was never meant to see. It removes the easiest lateral move on a shared build host. It is deliberately blunt: it is all-or-nothing for the network, with no per-port or per-pair allowance. Historically the pairing was `--icc=false` on the daemon plus `--link` to punch an allowance for the linked container's exposed ports; that legacy combination is not how anyone should build this today. ## How it relates to the other isolation controls Three controls are routinely confused: * **Separate networks** decide which containers can address each other at all — the usual, coarse, and most maintainable tool. * **`--internal`** decides whether a network can talk to the outside world. * **Disabling ICC** decides whether members of one network can talk to *each other*. If what you want is "the database is reachable only by the API", the answer is network layout, not ICC. If what you want is "nothing on this shared network may talk to its neighbours", ICC is the control. ## Verifying it After creating the network, start two containers on it and prove both halves: the name resolves, and the connection does not open. And state the limitation honestly in an interview: this is host-level plumbing, it applies to every container on the network equally, and it cannot express "A may reach B on 5432 but nothing else". Once requirements look like that, the answer is a different network layout or a policy layer above Docker, not more bridge options.
- Does disabling inter-container communication also stop those containers reaching the internet?No. Outbound traffic still leaves through the usual masquerading, and published ports are still reachable from outside the host. Cutting a network off from the outside world is a different option — `--internal` on `docker network create` — and the two are often confused. Disabling ICC only stops members of the network from opening connections to each other.
- Why do container names still resolve when inter-container communication is disabled?Because Docker's embedded resolver is provided by the daemon inside each container's own network namespace, not by another container across the bridge. The lookup never crosses the bridge, so it is unaffected by the drop rule. That is why the symptom is a name that resolves to an address followed by a connection that times out, which misleads people into blaming the application.
- Can you disable inter-container communication on a network that already exists?No. `com.docker.network.bridge.enable_icc` is a driver option fixed when the network is created, and there is no command to update it. You create a replacement network with the option set, attach the containers to it, and delete the old one. The daemon-wide `icc` setting for the default bridge is changeable, but only by restarting dockerd, which affects the whole host.
saying these in an interview costs you the question
- Thinks --icc=false applies to every network on the host
- Expects the setting to apply to an already created network
- Believes disabling ICC also blocks published ports
- Says containers on different networks need ICC disabled
- Confuses --internal with disabling inter-container traffic
- Assumes only EXPOSEd ports are reachable between peers