Ravindra BagaleCourses & study guides मराठी

Chapter 53: Docker Compose Tutorial: Services, depends_on Healthchecks, Env Files, Profiles, Overrides, Scaling and Every Command

53.5 Volumes and networks in Compose

Friends, in Chapters 50 and 51 we made volumes and networks by hand with docker volume create and docker network create. In Compose we declare them once in the file, and Compose creates, names and connects them for us. Today we keep database data safe with named volumes, edit code live with bind mounts, and split the app into a public network and a private one.

1. Start from a story

At BigBasket, the catalogue team ran Postgres in Compose with no volume. A developer ran docker compose down and up to fix a config, and two days of test data vanished.

In the same file, Postgres was on the default network, so the nginx container could reach the database port directly. A security reviewer flagged it.

Nisha added a named volume for /var/lib/postgresql/data, and split networks: frontend for nginx and the API, backend for the API and the database, marked internal so it had no internet route.

Now down and up kept the data, and only the API could talk to the database. Today we make the same two changes.

nginxfrontend network onlyapifrontend + backenddbbackend only (internal)dbdata volumesurvives down and up
Picture 1: networks and volumes

2. Volumes

Step 1: Named volume

Why: Data that outlives containers.

What: Declare under top-level volumes and mount by name.

Example 1: File

Type this:

printf 'services:\n  db:\n    image: postgres:16-alpine\n    environment: {POSTGRES_PASSWORD: example-only}\n    volumes:\n      - dbdata:/var/lib/postgresql/data\nvolumes:\n  dbdata:\n' > compose.yaml && docker compose up -d --wait && docker volume ls --format '{{.Name}}' | grep dbdata

You will see:

catalog_dbdata

What it means: Prefixed with the project.

Example 2: Write data

Type this:

docker compose exec db psql -U postgres -c 'create table item(id int); insert into item values (1);'

You will see:

CREATE TABLE
INSERT 0 1

What it means: Saved in the volume.

Example 3: down and up

Type this:

docker compose down && docker compose up -d --wait && docker compose exec db psql -U postgres -tc 'select count(*) from item'

You will see:

1

What it means: Data survived.

Step 2: down -v

Why: The one command that deletes data.

What: Only -v removes named volumes.

Example 4: Remove

Type this:

docker compose down -v

You will see:

✔ Volume catalog_dbdata  Removed

What it means: Gone.

Example 5: Check

Type this:

docker volume ls -q | grep -c catalog_dbdata

You will see:

0

What it means: No volume left.

Example 6: Fresh

Type this:

docker compose up -d --wait && docker compose exec db psql -U postgres -tc "select to_regclass('item')"

You will see:


What it means: Empty database.

Step 3: Bind mount

Why: A host folder inside the container.

What: Use a path starting with ./ or /. Good for code in development and config files.

Example 7: Static site

Type this:

mkdir -p site && printf 'hello v1\n' > site/index.html && printf 'services:\n  web:\n    image: nginx:1.27-alpine\n    ports: ["8080:80"]\n    volumes:\n      - ./site:/usr/share/nginx/html:ro\n' > compose.yaml && docker compose up -d && curl -s localhost:8080

You will see:

hello v1

What it means: Served from the host folder.

Example 8: Edit live

Type this:

printf 'hello v2\n' > site/index.html && curl -s localhost:8080

You will see:

hello v2

What it means: No rebuild needed.

Example 9: Read-only

Type this:

docker compose exec web sh -c 'echo x > /usr/share/nginx/html/a' 2>&1

You will see:

sh: can't create /usr/share/nginx/html/a: Read-only file system

What it means: :ro protects the host folder.

Step 4: Long syntax

Why: Clearer and more options.

What: type, source, target, read_only and bind or volume options.

Example 10: Long form

Type this:

printf '    volumes:\n      - type: bind\n        source: ./nginx.conf\n        target: /etc/nginx/conf.d/default.conf\n        read_only: true\n' | head -3

You will see:

volumes:
  - type: bind
    source: ./nginx.conf

What it means: Easy to review.

Example 11: tmpfs

Type this:

printf '    tmpfs:\n      - /tmp:size=64m\n' && echo memory-only

You will see:

    tmpfs:
      - /tmp:size=64m
memory-only

What it means: Fast scratch space.

Example 12: Missing source

Type this:

docker compose run --rm -v ./nope.conf:/x.conf web true 2>&1 | tail -1

You will see:

Error response from daemon: invalid mount config for type "bind": bind source path does not exist

What it means: Long-form bind needs the file to exist.

Step 5: External and shared volumes

Why: Use a volume made outside this project.

What: external: true tells Compose not to create or delete it.

Example 13: Create

Type this:

docker volume create shared-uploads

You will see:

shared-uploads

What it means: Made by hand.

Example 14: Use

Type this:

printf 'services:\n  t:\n    image: alpine:3.20\n    volumes: [up:/data]\n    command: ls /data\nvolumes:\n  up:\n    external: true\n    name: shared-uploads\n' > compose.yaml && docker compose run --rm t && echo listed

You will see:

listed

What it means: Mounted the existing volume.

Example 15: down -v keeps it

Type this:

docker compose down -v && docker volume ls -q | grep shared

You will see:

shared-uploads

What it means: External volumes are never removed.

Step 6: Backup a Compose volume

Why: Same trick as Chapter 50.

What: Run a temporary container with the volume and a host folder.

Example 16: Backup

Type this:

docker compose stop db && docker run --rm -v catalog_dbdata:/v:ro -v $PWD:/b alpine:3.20 tar czf /b/db.tgz -C /v . && ls -lh db.tgz | awk '{print $5}'

You will see:

6.1M

What it means: Archive on the host.

Example 17: Restore

Type this:

docker volume create catalog_dbdata2 && docker run --rm -v catalog_dbdata2:/v -v $PWD:/b alpine:3.20 tar xzf /b/db.tgz -C /v && echo restored

You will see:

catalog_dbdata2
restored

What it means: Into a new volume.

Example 18: Logical dump

Type this:

docker compose exec db pg_dump -U postgres postgres | gzip > dump.sql.gz && ls dump.sql.gz

You will see:

dump.sql.gz

What it means: Preferred for databases.

Step 7: Volume driver options

Why: Store a volume on NFS or EFS.

What: driver_opts passes options to the local driver, so a named volume can point at a network share.

Example 19: NFS volume

Type this:

printf 'volumes:\n  uploads:\n    driver: local\n    driver_opts:\n      type: nfs\n      o: addr=10.0.1.20,rw,nfsvers=4.1\n      device: ":/exports/uploads"\n' > vol.yaml && tail -1 vol.yaml

You will see:

device: ":/exports/uploads"

What it means: The share on the NFS server.

Example 20: Mounted

Type this:

docker compose -f compose.yaml -f vol.yaml run --rm t df -h /data | tail -1

You will see:

10.0.1.20:/exports/uploads   8.0E  1.2G  8.0E  1% /data

What it means: Many servers can share the same files.

Example 21: EFS

Type this:

printf 'o: addr=fs-0abc1234.efs.ap-south-1.amazonaws.com,nfsvers=4.1,rw\n'

You will see:

o: addr=fs-0abc1234.efs.ap-south-1.amazonaws.com,nfsvers=4.1,rw

What it means: Same idea on AWS with an example file system ID.

3. Networks

default networkeveryone sees everyonefrontendweb and apibackend (internal)api and db, no internetResultweb cannot reach db
Picture 2: default vs custom networks

Step 8: Default network

Why: Compose makes one for the project.

What: All services join project_default and can reach each other by name.

Example 22: List

Type this:

docker compose up -d && docker network ls --format '{{.Name}}' | grep catalog

You will see:

catalog_default

What it means: One network.

Example 23: Members

Type this:

docker network inspect catalog_default -f '{{range .Containers}}{{.Name}} {{end}}'

You will see:

catalog-web-1 catalog-db-1

What it means: Both on it.

Example 24: Reach

Type this:

docker compose exec web nc -zv db 5432 2>&1

You will see:

db (172.21.0.3:5432) open

What it means: web can reach the database. Not ideal.

Step 9: Custom networks

Why: Split by role.

What: Declare networks and list them per service.

Example 25: File

Type this:

printf 'services:\n  web:\n    image: nginx:1.27-alpine\n    networks: [frontend]\n  api:\n    image: alpine:3.20\n    command: sleep 3600\n    networks: [frontend, backend]\n  db:\n    image: postgres:16-alpine\n    environment: {POSTGRES_PASSWORD: example-only}\n    networks: [backend]\nnetworks:\n  frontend:\n  backend:\n    internal: true\n' > compose.yaml && docker compose up -d && docker network ls --format '{{.Name}}' | grep catalog_

You will see:

catalog_backend
catalog_frontend

What it means: Two networks.

Example 26: Blocked

Type this:

docker compose exec web nc -zv -w 2 db 5432 2>&1

You will see:

nc: bad address 'db'

What it means: web cannot even resolve db.

Example 27: Allowed

Type this:

docker compose exec api nc -zv db 5432 2>&1

You will see:

db (172.22.0.3:5432) open

What it means: api is on both.

Step 10: internal: true

Why: No route to the internet.

What: Containers only on an internal network cannot reach outside.

Example 28: db offline

Type this:

docker compose exec db wget -q -T 3 -O- https://example.com 2>&1 | tail -1

You will see:

wget: bad address 'example.com'

What it means: No internet.

Example 29: api online

Type this:

docker compose exec api wget -q -T 3 -O- https://example.com | head -c 15

You will see:

<!doctype html>

What it means: Through frontend.

Example 30: Why

Type this:

printf 'A database never needs to call the internet.\n'

You will see:

A database never needs to call the internet.

What it means: Less risk if it is ever compromised.

Step 11: Aliases and fixed IPs

Why: Extra names or addresses.

What: aliases add DNS names; ipv4_address pins an IP with an ipam subnet.

Example 31: Alias

Type this:

printf '    networks:\n      backend:\n        aliases: [postgres, catalog-db]\n' && echo 'db is also postgres'

You will see:

    networks:
      backend:
        aliases: [postgres, catalog-db]
db is also postgres

What it means: Old apps keep working.

Example 32: Lookup

Type this:

docker compose exec api nslookup postgres | tail -1

You will see:

Address: 172.22.0.3

What it means: Same container.

Example 33: Subnet

Type this:

printf 'networks:\n  backend:\n    ipam:\n      config:\n        - subnet: 172.30.0.0/24\n' | tail -1

You will see:

- subnet: 172.30.0.0/24

What it means: Avoid clashes with office VPN ranges.

Step 12: External networks

Why: Join a network shared by many projects.

What: Common for a reverse proxy like Traefik used by several apps.

Example 34: Create

Type this:

docker network create proxy

You will see:

8c1d...

What it means: Shared network.

Example 35: Join

Type this:

printf 'services:\n  web:\n    image: nginx:1.27-alpine\n    networks: [proxy]\nnetworks:\n  proxy:\n    external: true\n' > compose.yaml && docker compose up -d && docker network inspect proxy -f '{{range .Containers}}{{.Name}} {{end}}'

You will see:

catalog-web-1

What it means: Joined.

Example 36: down keeps it

Type this:

docker compose down && docker network ls -q --filter name=proxy | wc -l

You will see:

1

What it means: External networks stay.

Step 13: Inspect what Compose made

Why: Labels tell you the project.

What: Compose labels every resource with com.docker.compose.project.

Example 37: Volumes

Type this:

docker volume ls --filter label=com.docker.compose.project=catalog --format '{{.Name}}'

You will see:

catalog_dbdata

What it means: Only this project.

Example 38: Networks

Type this:

docker network ls --filter label=com.docker.compose.project=catalog --format '{{.Name}}'

You will see:

catalog_backend
catalog_frontend

What it means: Easy cleanup lists.

Example 39: Volumes command

Type this:

docker compose volumes

You will see:

DRIVER    VOLUME NAME
local     catalog_dbdata

What it means: Compose's own listing.

4. Common mistakes and how Nisha avoids them

Mistake one: no volume for a database. Nisha always mounts a named volume on the data folder.

Mistake two: down -v by habit. Nisha keeps it for fresh starts only.

Mistake three: everything on the default network. Nisha splits frontend and backend.

Mistake four: bind-mounting node_modules over the image's folder. Nisha uses a named volume for node_modules, covered in Compose Watch.

Mistake five: subnets that clash with the office VPN. Nisha pins a safe range with ipam.

Check yourself

Q1. Which command deletes named volumes?

docker compose down -v.

Q2. What does internal: true do?

Blocks internet access for that network.

Q3. How do you stop web reaching db?

Put them on different networks.

Q4. What does external: true mean?

Compose uses it but never creates or deletes it.

Friends, data in named volumes, roles in separate networks. Did you understand? Let us try it once more. Split your own Compose app into frontend and backend networks and prove the web container cannot reach the database.

Ravindra Bagale's Tip

Before every docker compose down, ask yourself: do I want the data back? If yes, never add -v.

Lab: try at home

  1. Add a named volume to Postgres and survive down/up.
  2. Delete it with down -v.
  3. Serve a site from a read-only bind mount.
  4. Use an external volume.
  5. Split frontend and backend networks.
  6. Prove internal: true blocks the internet.

Recap

  • Named volumes keep data.
  • down -v deletes them.
  • Bind mounts share host folders.
  • Custom networks separate roles.
  • internal: true blocks the internet.
  • External resources are never deleted by Compose.

Ravi Sir's Cards for this lesson

मराठी cards