Page 3 of 3
The bridge network from the medium tier isn't magic - here's roughly what's actually happening underneath. Docker creates a virtual network switch on the host, and each container gets its own network namespace - a private, isolated view of network interfaces, routing tables, and ports, so a container's "port 3000" is genuinely separate from the host's own port 3000 unless explicitly connected.
Each container connects to the bridge through a veth pair - a virtual cable with one end inside the container's namespace and the other plugged into the bridge on the host side. When you publish a port with -p, Docker sets up an iptables NAT rule that forwards traffic arriving on the host's port to the container's internal address and port.
docker network inspect bridge # see every container attached to the default bridge, and their internal IPs
iptables -t nat -L DOCKER -n # (Linux hosts) see the actual NAT rules Docker created
Knowing this matters mostly for debugging: "why can't this container reach that one" is very often actually a question about which network they're each attached to, since containers on different bridge networks can't reach each other by default, no matter how correctly configured each container itself is.
Beyond running as a non-root user, a few more security controls are worth knowing, since they come up constantly in production hardening guidance.
Linux capabilities break up what "root" traditionally means into much smaller, individually-grantable permissions (like "can bind to a low port" or "can change file ownership") instead of one all-or-nothing privilege. Docker drops most capabilities by default, but you can drop even more:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-app
seccomp profiles restrict which system calls a container is even allowed to make to the kernel - Docker applies a sensible default profile automatically, blocking a long list of rarely-needed, historically-risky syscalls.
Read-only root filesystem prevents a container from writing to its own filesystem at all, which - if your app doesn't genuinely need to write anything at runtime - closes off an entire category of attack (an attacker who compromises the app can't drop a malicious file onto disk, because nothing can be written anywhere):
docker run --read-only my-app
"Docker" is actually a stack of several distinct pieces, and knowing the layers helps when something breaks and the error isn't coming from where you'd expect:
docker info | grep -i runtime # see which low-level runtime your Docker installation is actually using
A few commands earn their place once you're troubleshooting something that's actually running, not just developing locally.
docker stats # live CPU/memory usage for every running container
docker inspect <container-id> # full JSON detail - resource limits, mounts, network settings, everything
docker inspect <container-id> --format='{{.State.OOMKilled}}' # specifically check if THIS container was killed for exceeding memory
OOMKilled is worth knowing by name specifically - it's what shows up when a container hits its memory limit and gets forcibly terminated. A container that keeps restarting with no obvious error in its logs, right before each restart, is a strong signal to check exactly this before looking anywhere else.
Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.