Back to blog

Docker Networks and Volumes Explained

dockernetworkingdevopscontainerstutorial
Docker Networks and Volumes Explained

In the previous post, you saw that a Node.js app could connect to PostgreSQL using @db:5432 instead of @localhost:5432. That works because of Docker networking. You also saw that database data survives container restarts because of volumes.

This post explains both — how containers communicate, and how data persists.


Part 1: Docker Networks

What is a Docker Network?

Docker creates a virtual network so containers can talk to each other. By default, containers are isolated — they can't reach each other unless they're on the same network.

When you use Docker Compose, all services are automatically placed on a shared default network. That's why services can reference each other by name — no IP addresses needed.

# List all networks
docker network ls
 
# Inspect a specific network
docker network inspect bridge

Network Drivers

Docker supports several network drivers for different use cases:

DriverDescription
bridgeDefault — isolated virtual network on a single host
hostContainer shares the host's network directly, no isolation
noneContainer has no network access
overlayConnects containers across multiple Docker hosts (Docker Swarm)

For most local development, bridge is what you'll use. Docker Compose uses it automatically.

How Containers Talk to Each Other

When containers are on the same network, they connect using the container or service name as the hostname. Docker resolves the name to an internal IP automatically — you never need to know the actual IP.

# docker-compose.yml
services:
  app:
    build: .
    environment:
      - DATABASE_URL=postgres://admin:secret@db:5432/mydb  # "db" is the service name
    depends_on:
      - db
 
  db:
    image: postgres:16-alpine

The app service connects to db using hostname db. Docker handles the DNS resolution internally.

Creating Networks Manually

With plain docker run (without Compose), you manage networks yourself:

# Create a custom network
docker network create my-network
 
# Run containers on the same network
docker run --network my-network --name app my-app
docker run --network my-network --name db postgres:16
 
# Connect a running container to a network
docker network connect my-network my-container

Custom Networks in Docker Compose

For more control, you can define explicit networks in Compose and assign services to specific ones. This lets you isolate services from each other:

services:
  app:
    build: .
    networks:
      - frontend
      - backend
 
  db:
    image: postgres:16-alpine
    networks:
      - backend   # db is only on the backend network
 
networks:
  frontend:
  backend:

Here, db is on backend only. If you add a public-facing proxy service on frontend only, it can't reach db directly — a useful security boundary.


Part 2: Docker Volumes

Why Volumes Exist

By default, data inside a container is lost when the container is removed. The container filesystem is ephemeral — it exists only as long as the container does.

Volumes solve this. A volume stores data outside the container's lifecycle, managed by Docker. Even if you delete and recreate a container, the volume (and its data) remains.

Volumes are used for:

  • Database data (PostgreSQL, MySQL, MongoDB)
  • Uploaded files
  • Application logs
  • Any data you can't afford to lose
# List all volumes
docker volume ls
 
# Inspect a volume
docker volume inspect my-volume
 
# Remove a volume
docker volume rm my-volume
 
# Remove all unused volumes
docker volume prune

Named Volumes

Docker manages the storage location — you just give it a name. Ideal for persistent data like databases where you don't care about the exact path on the host.

services:
  db:
    image: postgres:16-alpine
    volumes:
      - db_data:/var/lib/postgresql/data   # named volume
 
volumes:
  db_data:   # declare here so Docker creates and manages it

Data in db_data survives docker compose down. Only docker compose down -v deletes it.

Bind Mounts

A bind mount links a specific directory or file from your host machine into the container. Changes on the host are immediately visible inside the container — no rebuild needed.

This is the standard pattern for development hot-reload:

services:
  app:
    build: .
    volumes:
      - .:/app              # Sync current directory into /app
      - /app/node_modules   # Keep container's node_modules intact

The second line — /app/node_modules — is a common trick. Without it, the bind mount of . would overwrite the container's node_modules with your host's (which might not exist or differ). This anonymous volume ensures the container keeps its own node_modules.

With docker run:

docker run -v $(pwd):/app my-app

Named Volume vs Bind Mount

Named VolumeBind Mount
Managed byDockerYou
Host pathDocker decidesYou specify
Use caseDatabase, persistent dataDevelopment, code sync
PerformanceGoodGood (macOS can be slower)

Putting It Together

Here's a complete Compose file using both custom networks and volumes:

services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app               # Bind mount for hot-reload
      - /app/node_modules    # Preserve container's node_modules
    networks:
      - app-network
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: mydb
    volumes:
      - db_data:/var/lib/postgresql/data   # Persist database data
    networks:
      - app-network
 
volumes:
  db_data:
 
networks:
  app-network:
docker compose up -d
 
# Stop containers — data in db_data volume is preserved
docker compose down
 
# Stop containers AND delete volumes — data is gone
docker compose down -v

Summary

✅ Docker networks let containers communicate — isolated by default, connected when on the same network
✅ Docker Compose automatically puts all services on one network — connect via service name, not IP
✅ Use custom networks in Compose to isolate services that shouldn't talk to each other
✅ Container data is ephemeral — volumes keep data alive beyond container lifecycle
✅ Named volumes are Docker-managed, ideal for databases and persistent data
✅ Bind mounts sync host directories into containers — perfect for development hot-reload
docker compose down removes containers; docker compose down -v also removes volumes


Series: Docker & Kubernetes Learning Roadmap
Previous: Docker Compose Basics
Next: Docker Networking & Volumes Deep Dive


Ready to go deeper? The next post covers Docker networking internals, overlay networks, production storage patterns, and more.

📬 Subscribe to Newsletter

Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.

We respect your privacy. Unsubscribe at any time.

💬 Comments

Sign in to leave a comment

We'll never post without your permission.