Containers are ephemeral by default — when you remove a container, its data is gone. Volumes solve this by providing persistent storage. Networks let containers communicate with each other securely.
Why Volumes?
A container's filesystem is a writable layer on top of the image. This layer is:
- Temporary — deleted when the container is removed
- Tied to the container — not easily shared between containers
- Slower — the writable layer uses a storage driver with overhead
Volumes solve all three problems.
Types of Storage in Docker
| Type | Description | Use Case |
|---|---|---|
| Volumes | Managed by Docker, stored in Docker's directory | Databases, persistent app data |
| Bind mounts | Map a host directory into the container | Development (live code reload) |
| tmpfs mounts | Stored in memory only | Sensitive data, temp files |
Docker Volumes
# Create a named volume
docker volume create my-data
# List volumes
docker volume ls
# Inspect a volume
docker volume inspect my-data
# Remove a volume
docker volume rm my-data
# Remove all unused volumes
docker volume pruneUsing Volumes with Containers
# Mount a named volume
docker run -d \
--name db \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# The volume persists even after the container is removed
docker stop db
docker rm db
# Start a new container with the same volume — data is still there
docker run -d \
--name db-new \
-v pgdata:/var/lib/postgresql/data \
postgres:16Bind Mounts for Development
Bind mounts map a directory on your host machine directly into the container. Changes on either side are reflected immediately:
# Mount the current directory into the container
docker run -d \
--name dev-app \
-v $(pwd):/app \
-p 3000:3000 \
node:20-alpine \
sh -c "cd /app && npm run dev"This is essential for development — you edit code on your host, and the container picks up changes instantly.
# Read-only bind mount (container cannot modify host files)
docker run -d \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
nginxBind Mount vs Volume Syntax
Docker supports two syntaxes for mounts:
# Short syntax (-v)
docker run -v myvolume:/app/data my-app
docker run -v $(pwd):/app my-app
# Long syntax (--mount) — more explicit
docker run --mount type=volume,source=myvolume,target=/app/data my-app
docker run --mount type=bind,source=$(pwd),target=/app my-appThe --mount syntax is more verbose but clearer, especially for complex configurations:
docker run --mount \
type=volume,\
source=mydata,\
target=/app/data,\
readonly \
my-appPractical Volume Patterns
Database persistence:
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16Sharing data between containers:
# Container 1 writes logs
docker run -d --name writer -v shared-logs:/logs alpine \
sh -c "while true; do date >> /logs/app.log; sleep 5; done"
# Container 2 reads logs
docker run --rm -v shared-logs:/logs alpine tail -f /logs/app.logDocker Networks
By default, Docker creates three networks:
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# abc123 bridge bridge local
# def456 host host local
# ghi789 none null local| Network | Description |
|---|---|
| bridge | Default network for containers. Isolated from host. |
| host | Container shares the host's network stack |
| none | No networking |
Creating Custom Networks
Custom bridge networks provide automatic DNS resolution — containers can find each other by name:
# Create a custom network
docker network create my-network
# Run containers on the same network
docker run -d --name web --network my-network nginx
docker run -d --name api --network my-network node-api
# Containers can reach each other by name
docker exec web curl http://api:3000/health
On the default bridge network, containers can only communicate by IP address. On custom networks, they use container names as hostnames.
Network Commands
# Create a network
docker network create app-net
# List networks
docker network ls
# Inspect a network
docker network inspect app-net
# Connect a running container to a network
docker network connect app-net my-container
# Disconnect a container from a network
docker network disconnect app-net my-container
# Remove a network
docker network rm app-net
# Remove all unused networks
docker network pruneMulti-Container Application Example
Here is a complete example running a web app with a database and cache:
# Create a network
docker network create webapp-net
# Start PostgreSQL
docker run -d \
--name db \
--network webapp-net \
-e POSTGRES_USER=app \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=myapp \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# Start Redis
docker run -d \
--name cache \
--network webapp-net \
redis:7-alpine
# Start the application
docker run -d \
--name app \
--network webapp-net \
-e DATABASE_URL=postgres://app:secret@db:5432/myapp \
-e REDIS_URL=redis://cache:6379 \
-p 3000:3000 \
my-web-appThe app container reaches the database at db:5432 and Redis at cache:6379 using container names as hostnames.
Network Isolation
You can create separate networks for security. Containers on different networks cannot communicate:
# Frontend network
docker network create frontend
# Backend network
docker network create backend
# Nginx only on frontend
docker run -d --name nginx --network frontend -p 80:80 nginx
# API on both networks (bridges frontend and backend)
docker run -d --name api --network frontend my-api
docker network connect backend api
# Database only on backend (not accessible from frontend)
docker run -d --name db --network backend postgres:16This way, the database is only reachable from the API, not from the Nginx container.
tmpfs Mounts
For sensitive or temporary data that should never be written to disk:
docker run -d \
--name secure-app \
--tmpfs /app/tmp:size=100m \
my-appInspecting Container Networking
# Find a container's IP address
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container
# See which ports are mapped
docker port my-container
# Test connectivity between containers
docker exec web ping -c 3 api
docker exec web curl http://api:3000
Summary
Volumes provide persistent storage for containers — use named volumes for databases and bind mounts for development. Custom networks enable containers to discover each other by name and provide network isolation for security. In the next lesson, you will learn Docker Compose, which simplifies managing multi-container applications.