You started Postgres in a container, added some data, then removed the container — and everything was gone. This is not a bug, it is Docker's normal behavior. Let's figure out why it works this way and how to properly store data that must survive restarts.
Why data in a container is ephemeral
A container is an isolated process with its own file system. This file system is created from an image when the container starts, and when the container is removed it disappears along with it.
Short formula: an image is an immutable template, a container is a temporary working copy.
Everything you write inside a running container (rows in a database, uploaded files, logs) lives in the so-called writable layer — a thin layer on top of the image. Docker deletes this layer together with the container when you run docker rm.
docker run --name demo postgres:16
# ... created tables, added rows ...
docker rm demo
# The data is gone
For most cases this is actually convenient: you start a container for a test, play around, remove it — clean. But databases, user files, and application state need to live longer than a single container.
Three ways to store data outside a container
Docker offers three mechanisms: named volumes, bind mounts, and tmpfs. Each has its own use case.
Named volumes — the recommended way for data
A volume (named volume) is a directory managed by Docker. It lives on the host in a special place (/var/lib/docker/volumes/) and does not depend on the container's lifecycle.
# Create a volume explicitly
docker volume create pgdata
# Attach the volume to a container
docker run -d \
--name postgres \
-v pgdata:/var/lib/postgresql/data \ # volume:path_inside_container
-e POSTGRES_PASSWORD=secret \
postgres:16
Now you can remove the container, create a new one with the same volume — and the data will not go anywhere:
docker rm postgres
docker run -d \
--name postgres \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16
# All data is still there
You manage volumes with docker volume:
docker volume ls # list volumes
docker volume inspect pgdata # where it lives, when it was created
docker volume rm pgdata # remove (only if not attached)
docker volume prune # remove all "orphaned" volumes
Named volumes are the right choice for databases, file stores, and any data you need in production.
Bind mounts — for development
A bind mount mounts a specific directory or file from the host straight into the container. No Docker "magic": you tell it yourself which path on your machine ends up where inside.
docker run -d \
--name app \
-v /Users/vva/projects/myapp:/app \ # host:container
eclipse-temurin:21-jre \
java -jar /app/app.jar
The main use case is development: you edit code in the IDE, and the changes are immediately visible inside the container without rebuilding the image. For Spring Boot this is especially handy together with devtools.
# docker-compose.yml for development
services:
app:
image: eclipse-temurin:21-jre
volumes:
- ./build/libs:/app # the built jar from the host
command: java -jar /app/app.jar
You should not use bind mounts to store Postgres data or other state in production: you become dependent on the file system structure of a specific host, and that breaks portability.
tmpfs — data in memory only
tmpfs mounts a directory inside the container into the host's RAM. The data never touches the disk and disappears when the container stops.
docker run --tmpfs /tmp myapp
It is used for temporary data that should not reach the disk: sensitive files (tokens, keys), session caches, intermediate computations. Not available on Windows hosts.
Comparison: when to use what
| Situation | Mechanism |
|---|---|
| Database in production (Postgres, Redis) | Named volume |
| Files uploaded by users | Named volume |
| Code during local development | Bind mount |
| Configuration files during development | Bind mount |
| Temporary files, sensitive data | tmpfs |
Postgres in a container: a complete example
Let's run Postgres with a named volume so the data persists across restarts. This is a typical setup for local development of a Spring Boot application.
# docker-compose.yml
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data # named volume
volumes:
pgdata: # Docker will create the volume automatically
docker compose up -d
# Created tables, added data...
docker compose down # stopped and removed the containers
docker compose up -d # brought them back up — the data is still there
Note: docker compose down does not remove volumes. To remove a volume you need an explicit flag:
docker compose down --volumes # remove containers AND volumes
This protects you from accidental data loss.
Where to look if the data is gone
If the data disappeared after a container restart — check step by step:
- Make sure a volume was used, and not just a directory inside the container.
- Check that the
--volumesflag was not used when stopping. - Make sure the mount path matches where the application actually writes its data. For Postgres it is
/var/lib/postgresql/data, for Redis it is/data, others have their own paths — check the image's documentation.
docker inspect postgres | grep -A 10 Mounts # check what is mounted
In short
- Data inside a container is ephemeral: remove the container — lose the data.
- Named volume is the right choice for databases and any state that must live longer than the container.
- Bind mount mounts a directory from the host; good for development, not for production.
- tmpfs is memory only, disappears on stop; for temporary and sensitive data.
docker volume ls / inspect / rm / prune— the main commands for managing volumes.docker compose downdoes not remove volumes — you need an explicit--volumes.
What to read next
- Running containers —
docker runflags, run modes, managing containers. - Docker Compose — how to describe multi-container applications and define volumes in
docker-compose.yml. - Networking — how containers communicate with each other and with the host.