✦ For everyone, free.

Practical knowledge for real and everyday life

Home

15.2.1.3 Container Network IO

A focused guide to Container Network IO, connecting core concepts with practical Docker and container operations.

Container network I/O is the volume of inbound and outbound traffic flowing through a container's network namespace, measured by Docker through the kernel's per-interface byte and packet counters, providing a per-container breakdown of network activity that host-level network monitoring, which sees only aggregate traffic across every process, cannot offer on its own.

Reading basic network I/O

Docker's stats interface reports cumulative bytes received and transmitted for a container since it started, through its virtual network interface:

docker stats my-api --no-stream
CONTAINER ID   NAME     NET I/O
3f29a8c1d8e2   my-api   1.2GB / 850MB

This figure is cumulative for the life of the container, not a current rate, which means comparing two readings taken some time apart and dividing by the elapsed time is necessary to derive an actual throughput rate rather than relying on the raw cumulative numbers alone.

Per-interface accounting and the virtual ethernet pair

Each container's network traffic passes through a virtual ethernet (veth) interface pair, one end inside the container's network namespace and the other attached to the host's bridge, and the byte and packet counters Docker reports are read directly from this interface:

docker exec my-api cat /sys/class/net/eth0/statistics/rx_bytes
docker exec my-api cat /sys/class/net/eth0/statistics/tx_bytes
ip -s link show veth1234abc

Because this is genuine kernel-level interface accounting, it includes all traffic through that interface, container-to-container traffic on the same Docker network as well as traffic destined outside the host, which is worth keeping in mind when interpreting a sudden volume increase: it does not by itself distinguish internal service-to-service chatter from external traffic.

Distinguishing internal and external traffic

Because Docker's basic network I/O metric aggregates all traffic through the container's interface, attributing a volume increase specifically to external client traffic versus internal calls to other services in the same stack requires additional context, typically from application-level metrics or a service mesh's own per-destination traffic accounting:

const outboundCalls = new promClient.Counter({
  name: 'outbound_requests_total',
  labelNames: ['destination'],
});
outboundCalls.inc({ destination: 'database' });

Instrumenting outbound calls by destination at the application level fills this gap, since the raw interface-level byte counters alone cannot distinguish a spike in legitimate external user traffic from an internal retry storm hitting a downstream dependency repeatedly.

Network I/O as a capacity and cost signal

For services running in cloud environments where network egress is metered and billed, sustained or growing network I/O has direct cost implications beyond pure performance considerations, making it worth tracking over time even for services that are not exhibiting any obvious performance problem:

docker stats --no-stream --format "{{.NetIO}}" my-api
network_transmit_bytes_total{service="my-api"}

A service whose outbound traffic has grown substantially without a corresponding increase in request volume may be serving unexpectedly large response payloads, making more outbound calls per request than intended, or have introduced an inefficient retry or polling pattern, any of which would be worth investigating both for performance and for cost reasons.

Network throttling and bandwidth limits

Docker supports limiting a container's network bandwidth, which is occasionally used to prevent one container from monopolizing shared host network capacity at the expense of others, though this requires additional tooling beyond Docker's own basic flags, since Docker itself does not expose a direct --network-bandwidth option the way it does for CPU and memory:

tc qdisc add dev veth1234abc root tokenbucket rate 10mbit burst 32kbit latency 50ms

Applying traffic control rules directly to a container's veth interface is a lower-level approach that requires identifying the correct interface and applying kernel-level traffic shaping, which is considerably less convenient than Docker's built-in CPU and memory limit flags but is the available mechanism when network bandwidth genuinely needs to be capped per container.

Diagnosing unexpectedly high network I/O

When a container shows unexpectedly high network volume, narrowing down the source typically involves checking which connections are actually open and how much traffic each is responsible for, rather than relying on the aggregate figure alone:

docker exec my-api netstat -an | grep ESTABLISHED
docker exec my-api ss -i

Combining this connection-level view with application logs covering the same time window usually identifies whether the volume is attributable to a specific, identifiable client, an internal retry loop, or a legitimate but unexpectedly large batch operation.

Exporting network metrics for historical analysis

As with CPU and memory, network I/O needs to be exported to a persistent metrics system to be useful for anything beyond immediate, interactive troubleshooting, since docker stats itself retains no history once the command stops running:

scrape_configs:
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']
rate(container_network_receive_bytes_total{name="my-api"}[5m])

Graphing network I/O rate over time, alongside request volume from application metrics, makes it straightforward to spot the cases where network traffic is growing disproportionately to actual request volume, which is usually the first sign of something worth a closer look.

Common mistakes

  • Treating the cumulative byte counters from docker stats as a current rate rather than calculating the rate from two readings over a known time interval.
  • Assuming network I/O metrics distinguish internal service-to-service traffic from external client traffic, when the raw interface counters aggregate both together.
  • Ignoring network I/O trends because no performance problem is currently visible, missing a growing cost or an inefficient traffic pattern before it becomes large enough to cause one.
  • Attempting to limit per-container bandwidth without realizing Docker has no built-in flag for it, and not knowing that lower-level traffic control tooling is required instead.
  • Relying solely on docker stats for network observability without exporting metrics to a system capable of retaining history for trend analysis.

Container network I/O metrics give an accurate, per-container view of traffic volume sourced directly from kernel interface accounting, but interpreting them usefully, distinguishing internal from external traffic, identifying cost-relevant trends, and diagnosing unexpected spikes, generally requires combining them with application-level instrumentation and connection-level inspection rather than relying on the aggregate byte counters alone.