Volumes
A named volume is storage Docker manages for you, independent of any one container's lifecycle. It's the right choice for anything you need to keep: a database's data directory, uploaded files, a cache that should survive a redeploy.
docker volume create db-data
docker volume ls
docker volume inspect db-data
# Mount it into a container at /var/lib/postgresql/data
docker run -d \
-v db-data:/var/lib/postgresql/data \
postgres:16-alpine
docker volume rm db-data # fails while a container still uses itBacking up and restoring a volume
A volume isn't a file you can just cp — it's managed storage your containers mount by name. The standard way to get data in or out is a disposable container that mounts the volume on one side and a host directory on the other, then runs tar.
# Back up: write the volume's contents to a tarball on the host
docker run --rm \
-v db-data:/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/db-data.tar.gz -C /data .
# Restore: extract that tarball back into a (fresh) volume
docker volume create db-data
docker run --rm \
-v db-data:/data \
-v "$(pwd)":/backup \
alpine tar xzf /backup/db-data.tar.gz -C /dataNothing here is volume-specific beyond the mount — it's just a throwaway Alpine container with tar, which is exactly why it works the same way for any volume, regardless of what's storing data in it.
Bind mounts
A bind mount maps an exact host path into the container. It's the standard way to get live local source code into a container during development, so edits on the host are visible inside immediately without rebuilding the image.
docker run -d \
-v "$(pwd)":/app \
-w /app \
node:22-alpine npm run devVolumes and bind mounts use the same -v flag; Docker tells them apart by whether the left side looks like a volume name or a filesystem path.
tmpfs mounts
For data that should never touch disk at all — secrets staged briefly in memory, scratch space — mount tmpfs instead:
docker run -d --tmpfs /app/cache:rw,size=64m docktionary/app:1.4.0Networks
By default every container lands on the default bridge network, where containers can reach each other only by IP. A user-defined bridge network additionally gives you DNS: containers can resolve each other by name.
docker network create app-net
docker run -d --name db --network app-net postgres:16-alpine
docker run -d --name web --network app-net -p 8080:80 docktionary/app:1.4.0
# from inside "web", http://db:5432 resolves correctly
docker network ls
docker network inspect app-netNetwork drivers, at a glance
- bridge — the default for a single host; an isolated virtual network with NAT out to the host's network.
- host — no isolation at all; the container shares the host's network stack directly. Fastest, but you lose port mapping and isolation.
- overlay — spans multiple Docker hosts, used by Swarm (and conceptually similar to what a CNI plugin provides for Kubernetes Pods).
- none — no networking at all, for workloads that shouldn't have any.
Publishing vs. exposing
EXPOSE in a Dockerfile is documentation. Publishing with -p host:container on docker run is what actually opens a path from the host's network into the container. Without -p, a container on a bridge network is reachable from other containers on that network, but not from the host's outside world.