Skip to content
Docker
Lab 3 of 11·40mIntermediate

Wire two containers together on a network you define

Put a service and a Postgres instance on a user-defined network, resolve one from the other by name, and watch what a volume survives.

You need

  • Docker Engine 24+ or Docker Desktop
  • A terminal

Do first

Container-to-container DNS is the part people reach for Compose to avoid understanding. It is worth understanding: a user-defined bridge network gives every container on it a DNS name equal to its own name, and that single fact explains most of what a Compose file is doing for you.

1. Create a network and look at it

docker network create lab-net
docker network inspect lab-net \
  --format '{{.Driver}} {{(index .IPAM.Config 0).Subnet}}'

The default bridge network does not give you DNS between containers. A user-defined bridge does. This is the whole reason to create one.

Verify

docker network inspect lab-net --format '{{.Driver}}' # bridge

2. Start Postgres with a named volume

docker run -d --name lab-db --network lab-net \
  -e POSTGRES_PASSWORD=labpass \
  -e POSTGRES_DB=labdb \
  -v lab-db-data:/var/lib/postgresql/data \
  postgres:16-alpine

No -p. Nothing is published to your host, because only the other container needs to reach it — an exposed database port is one of the most common ways a lab environment becomes an incident.

Wait for it to finish initialising:

docker exec lab-db pg_isready -U postgres

Verify

docker exec lab-db pg_isready -U postgres # /var/run/postgresql:5432 - accepting connections

3. Resolve the database by name from another container

docker run --rm -it --network lab-net postgres:16-alpine \
  psql -h lab-db -U postgres -d labdb -c 'select version();'

It will prompt for the password: labpass.

The host lab-db resolved to the other container. Nothing configured that — Docker's embedded DNS server answers for container names on a user-defined network. Prove that is what happened:

docker run --rm --network lab-net alpine:3.20 nslookup lab-db

Verify

docker run --rm --network lab-net alpine:3.20 nslookup lab-db # resolves to a 172.x address served by 127.0.0.11

Now show it fails on the default network:

docker run --rm alpine:3.20 nslookup lab-db

Verify

docker run --rm alpine:3.20 nslookup lab-db # NXDOMAIN — no DNS between containers on the default bridge

4. Write data, then destroy the container

docker exec -e PGPASSWORD=labpass lab-db \
  psql -U postgres -d labdb \
  -c "create table notes (body text)" \
  -c "insert into notes values ('survived')"

Delete the container entirely — not stop, delete:

docker rm -f lab-db

Start a new one, attached to the same volume:

docker run -d --name lab-db --network lab-net \
  -e POSTGRES_PASSWORD=labpass \
  -v lab-db-data:/var/lib/postgresql/data \
  postgres:16-alpine
sleep 5
docker exec -e PGPASSWORD=labpass lab-db \
  psql -U postgres -d labdb -c 'select * from notes'

The row is there. The container was disposable; the volume was not. That is the line between the two, and it is the same line you will draw later between a stateless service and its managed database.

Verify

docker exec -e PGPASSWORD=labpass lab-db \ psql -U postgres -d labdb -c 'select * from notes' # one row: survived

5. Confirm the isolation is real

The database is on lab-net and publishes nothing. From your host:

psql -h localhost -U postgres -d labdb

Refused — there is no path from your host to that port. Only containers on lab-net can reach it.

Verify

nc -z localhost 5432; echo "exit=$?" # exit=1 — nothing listening on the host

Clean up

Order matters: containers, then the network, then the volume.

docker rm -f lab-db
docker network rm lab-net
docker volume rm lab-db-data

Verify

docker volume ls --filter name=lab-db-data # only the header row

Where this goes next

You have a network you defined, name-based service discovery, and a volume that outlived its container. A Compose file writes those three things down; a Kubernetes Service and PersistentVolumeClaim are the same two ideas with more ceremony. The CI/CD track picks this up by building the service image in a pipeline and running these same containers as test dependencies.