RizTech Academy logo
RizTech Academy
ContainersLesson 6 of 630 min

Volumes, networks and persistence

Two questions decide whether a containerised system actually works in production: where does the data go when a container is destroyed? and how do containers talk to each other and the outside world? The answers are volumes (persistence) and networks (connectivity). Get these wrong and you lose a database on the next deploy, or your services cannot reach each other. This lesson is volumes and networks — the last piece of the containers foundation, and one with real consequences.

Containers are ephemeral — that is the point

A container's own filesystem is ephemeral: when the container is removed, everything written inside it is gone. Stop and remove a database container that stored its data inside itself, and the data vanishes. This is not a bug — it is the whole design. Containers are meant to be disposable: you throw them away and start fresh ones constantly (deploys, restarts, scaling), and that only works if a container carries no precious state inside it.

The consequence you must internalise: anything that must survive — a database's data, uploaded files — must live outside the container. That is what volumes are for. Storing a database's data inside the container is one of the classic, painful container mistakes: it works in a demo and loses everything the first time the container is replaced.

Volumes: persistence that outlives the container

A volume is storage managed by Docker that lives outside any single container and can be attached to one. Data written to a volume persists when the container is removed and can be reattached to a new container. In the greetings stack, Redis uses one:

redis:
  image: redis:7-alpine
  volumes:
    - redis-data:/data      # mount the named volume 'redis-data' at /data inside the container

redis-data:/data means "mount the named volume redis-data at the path /data inside the container" — where Redis writes its data. Because the data is on the volume, not in the container, it survives: the verified greetings stack shows the visit counter continuing to climb (…1, 2, 3…) even after the app container is restarted, because Redis's data lives on the volume, not in the container. Remove the volume (docker compose down -v) and the counter resets — proving the data was on the volume all along.

Two kinds of mount you will use:

  • Named volumes (redis-data:/data) — Docker manages the storage; the right choice for production data like databases. Persistent, portable, backed up as a unit.
  • Bind mounts (./src:/app/src) — map a folder on your host into the container. Great for local development: mount your source code in so edits on your laptop appear live inside the container without a rebuild. Less suitable for production data (ties you to a host path).

The rule: named volumes for data that must persist, bind mounts for developer convenience. And the deeper rule: never keep important state inside the container's own filesystem.

Networks: how containers connect

Containers are isolated by default, including their networking — a container cannot reach another unless they share a network. Docker networking gives you the connections you need:

  • Containers on the same network reach each other by name. As the Compose lesson showed, Compose creates a network for your stack and the app reaches Redis at the hostname redis. Under the hood this is a Docker network with name-based service discovery. You can also create networks explicitly (docker network create) and attach containers to them.
  • Publishing a port (-p 8080:80, or ports: in Compose) connects a container to the outside world — mapping a host port to a container port so traffic from outside reaches the container. Without publishing, a container's ports are reachable only from within its Docker network, not from your host or the internet.
  • Isolation as a feature. You can put your database on a network shared only with the app, and not publish its port at all — so the database is reachable by the app but not exposed to the outside. This is a real security pattern: publish only what must be public (the web app), and keep internal services (databases, caches) on an internal network, unpublished.

So networking is two things: internal connectivity (services find each other by name on a shared network) and controlled exposure (publish only the ports that genuinely need to face the world). The greetings stack uses both — the app talks to Redis internally by name, and only the app's port 3000 is published; Redis's 6379 is not exposed to the host at all.

Putting the foundation together

You now have the full containers foundation, and it all connects:

  • An image (from a good, small, multi-stage Dockerfile) packages your app and its environment.
  • You run it as a container, configured by environment variables, with ports published as needed.
  • Compose describes a multi-container stack — your app plus its dependencies — started with one command.
  • Volumes keep the data that must survive, outside the ephemeral containers.
  • Networks let the services find each other by name and control what is exposed.

This is a complete, real local development setup — and every one of these concepts reappears, scaled up, in Kubernetes (the next module): images become the thing pods run, environment config becomes ConfigMaps and Secrets, volumes become persistent volumes, and service-name networking becomes Kubernetes Services. Learn it here at Compose scale, and the cluster will make sense.

Check your work

Containers are ephemeral: a container's own filesystem is lost when it is removed — by design (containers are disposable, replaced constantly). So anything that must survive lives outside the container. Storing a DB's data inside the container is a classic data-loss mistake.

Volumes = Docker-managed storage outside any container, that persists and reattaches. Named volumes (redis-data:/data) for production data (the greetings counter survives an app restart on its volume; down -v resets it); bind mounts (./src:/app/src) map a host folder in, for live-edit local dev. Named volumes for persistence, bind mounts for convenience; never keep important state in the container FS.

Networks: containers connect only on a shared network — same-network containers reach each other by name (Compose's service discovery); publishing a port (-p/ports:) exposes a container to the outside, and unpublished ports stay internal. Security pattern: publish only what must be public (the web app), keep databases/caches internal and unpublished (greetings publishes app:3000, not Redis:6379).

The foundation together: small multi-stage image → run configured by env with published ports → Compose for the multi-container stack → volumes for surviving data → networks for name-based connectivity and controlled exposure. Every concept scales up into Kubernetes next.

Practice

  1. Run the greetings + Redis stack, increment the counter, restart the app container, and show the counter persists; then down -v and show it resets — explain why.
  2. Explain the difference between a named volume and a bind mount, with a use case for each.
  3. Explain why storing a database's data inside the container's own filesystem is a serious mistake.
  4. Explain how the app reaches Redis by the hostname redis and why no IP is hard-coded.
  5. Modify the stack so Redis's port is not published, and explain the security benefit.
  6. Map each container concept (image, env config, volume, service-name networking) to what you expect it becomes in Kubernetes.

Official documentation

Next: the Container Orchestration with Kubernetes module.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship