| tags |
|
|---|
--data-root is the path where persisted data such as images, volumes, and cluster state are stored. The default value is /var/lib/docker. To avoid any conflict with other daemons, set this parameter separately for each daemon.
https://docs.docker.com/engine/reference/commandline/dockerd/
then:
$ sudo systemctl restart docker.socket
$ sudo systemctl restart docker
A container is not a VM — it is a regular Linux process isolated by namespaces (what the process can see), limited by cgroups (what it can use), running on a layered overlay filesystem (what it can store), wired into the host via virtual networking.
| Namespace | Isolates | Effect in a container |
|---|---|---|
pid |
Process IDs | Container's main process is PID 1; can't see host processes |
net |
Network stack | Own interfaces, IP, routing table, iptables |
mnt |
Mount points | Own filesystem tree (rootfs from image layers) |
uts |
Hostname, domain | hostname returns the container ID / --hostname value |
ipc |
SysV IPC, POSIX queues | No shared memory with host processes |
user |
UID/GID mapping | Root in container can map to unprivileged host UID (off by default — that's why UIDs match the host, see UID and GID) |
cgroup |
Cgroup root view | Container sees only its own cgroup subtree |
# list namespaces on the host
lsns
# namespaces of a container's main process
docker inspect --format '{{.State.Pid}}' <containerId>
ls -l /proc/<pid>/ns
# enter a container's namespaces without docker exec
nsenter -t <pid> -n ip addr # only net ns — e.g. debug networking of a distroless container
nsenter -t <pid> -m -u -i -n -p sh # "manual docker exec"
# weaken isolation on purpose (debugging)
docker run --pid=host --net=host --privileged ...Note: docker exec = start a new process inside the existing namespaces of a container. kubectl debug / sidecars work the same way (pods share net and ipc namespaces).
Control groups meter and limit CPU, memory, IO, and PIDs per container. Modern distros use cgroup v2 (unified hierarchy, default since Docker 20.10 on kernels ≥ 5.2 with systemd).
docker run -d --memory=512m --cpus=1.5 --pids-limit=256 nginx
# live usage per container (reads cgroup stats)
docker stats
# where the limits actually live (cgroup v2 + systemd driver)
cat /sys/fs/cgroup/system.slice/docker-<containerId>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<containerId>.scope/cpu.maxGotchas:
- Exceeding
--memory⇒ the kernel OOM-kills the process (exit code 137). For the JVM make sure it is container-aware (-XX:MaxRAMPercentage, default since JDK 10; older JVMs saw the host memory). --cpusis enforced via CFS quota — the app sees all host cores in/proc/cpuinfo; useRuntime.availableProcessors()on a container-aware JDK.- cgroups limit, namespaces hide —
free -min a container shows host memory unless something like LXCFS is used.
Image = ordered set of read-only layers. Container = those layers + one writable layer on top, merged by the overlay2 driver (copy-on-write):
merged ← what the container sees (mount at runtime)
upperdir ← container's writable layer (lost on container removal)
lowerdir ← image layers (read-only, shared between containers)
# see the overlay directories of a container
docker inspect --format '{{json .GraphDriver.Data}}' <containerId> | jq
# all of it lives under data-root (default /var/lib/docker)
ls /var/lib/docker/overlay2- Copy-on-write: modifying a file from an image layer copies it up to
upperdirfirst — cheap for small writes, slow for heavy IO ⇒ keep databases and hot data out of the container layer. - Persistence options:
| Type | Flag | Use case |
|---|---|---|
| Volume | -v myvol:/data |
Persistent data, managed by Docker under data-root |
| Bind mount | -v /host/path:/data |
Sharing host files (dev setups, config) |
| tmpfs | --tmpfs /tmp |
Secrets/scratch in RAM, never touches disk |
- This is also why layer order matters for Dockerfile Caching — each instruction produces one immutable layer.
Default bridge mode: each container gets its own net namespace connected to the host by a veth pair — one end inside the container (eth0), the other attached to the docker0 bridge. Outbound traffic is masqueraded (SNAT), published ports are DNAT-ed by iptables rules that Docker manages.
container eth0 ── veth pair ── docker0 bridge ── iptables NAT ── host eth0
# the plumbing
ip link show type veth # host side of the pairs
brctl show docker0 # or: ip link show master docker0
iptables -t nat -L DOCKER -n # DNAT rules created by -p 8080:80
# per-network details, connected containers, subnet
docker network inspect bridgeNetwork drivers:
| Driver | What it does | When |
|---|---|---|
bridge |
Private subnet (172.17.0.0/16 by default) + NAT | Default, single host |
host |
No net namespace — container shares host stack |
Max performance, no port mapping possible |
none |
Only loopback | Manual/custom networking |
overlay |
VXLAN across hosts | Swarm / multi-host |
macvlan |
Container gets own MAC on the LAN | Container must look like a physical host |
- On user-defined bridge networks containers resolve each other by name via Docker's embedded DNS (
127.0.0.11inside the container); the default bridge has no name resolution — one more reason to alwaysdocker network create. -p 8080:80works from outside, but two containers on the same bridge talk directly tocontainerIP:80— no NAT involved.
Docker overview — the underlying technology
Docker storage drivers — overlay2
Containers from scratch (Liz Rice, live-coding a container in Go)
docker run -it ubuntu bash
docker run -d -t ubuntu
docker run accepts explicit port mappings as parameters or you can specify -P to map all ports automatically. The latter has the advantage of preventing conflicts and looking up the assigned ports can be done as follows:
docker port <containerId> <portNumber>
# or
docker inspect --format '{{.NetworkSettings.Ports}}' <containerId>
Each container has it's IP in a private subnet (which is 172.17.0.0/16 by default). The IP can change with restart but can be looked up should you need it:
docker inspect --format '{{.NetworkSettings.IPAddress}}' <containerId>
# command keeps looking for new changes in the file to display. It never ends as long as the file exists
docker run ubuntu tail -f /dev/null
# infinite loop
docker run ubuntu while true; do sleep 1; done
# command keeps the container idle and does nothing
docker run ubuntu sleep infinity
#!/bin/sh
while ! nc -z config-server 8888 ; do
echo "Waiting for upcoming Config Server"
sleep 2
done
java -jar /opt/spring-cloud/lib/config-client.jar
docker run centos:latest sh -c "whoami && date"
...
ENTRYPOINT ["sh", "-c", "whoami && date"]
https://github.com/andrzejsydor/devops/tree/master/_security
docker run -d ubuntu:latest sleep infinity
ps aux | grep sleep
echo $UID
FROM ubuntu:latest
RUN useradd -r -u 1001 -g appuser appuser
USER appuser
ENTRYPOINT [“sleep”, “infinity”]
docker run -d --user 1001 ubuntu:latest sleep infinity
ps aux | grep sleep
docker cp <containerId>:/file/path/in/container/file /host/local/path/file
docker cp /host/local/path/file <containerId>:/file/path/in/container/file
By default docker runs everything as root but you can use USER in Dockerfiles. There's no user namespacing in docker so the container sees the users on the host but only uids hence you need the add the users in the container.
Use caching properly for your Dockerfiles: docker containers are built very quickly as long as you make use of the caching capability. A quick set of gotchas and advices:
- ADD & VOLUMES are cache invalidators.
- RUN commands are cached while unchanged.
- Dockerfile execution is sequential, hence: order matters and a changed step deprecate the next caches.
- Group your RUN commands (shell sequences) together per type (e.g. ssh related). This allows you to tune and forget a "feature" of your container and focus on the next one without ever blowing up your cache.
- Maintain common command orders in between your various Dockerfiles. This allow to use "common" caching from one container to another for as long as they share common features