Why Containers Lose Data by Default
Containers are ephemeral. When a container stops or is removed, everything written to its filesystem disappears. For stateless applications this is fine — but databases, file uploads, logs, and caches need data to survive container restarts.
Docker solves this with three storage mechanisms: named volumes, bind mounts, and tmpfs mounts.
Named Volumes (Recommended for Production)
Named volumes are managed by Docker and stored in Docker's storage area on the host (typically /var/lib/docker/volumes/). They're the preferred method for persisting data in production.
# Create a named volume
docker volume create pg_data
# Use it when running a container
docker run -d --name postgres -e POSTGRES_PASSWORD=secret -v pg_data:/var/lib/postgresql/data postgres:16-alpineIn a docker-compose.yml:
services:
db:
image: postgres:16-alpine
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data:The pg_data volume persists even after docker compose down. Only docker compose down -v removes it.
Volume Commands
docker volume ls # list all volumes
docker volume inspect pg_data # show volume details and mountpoint
docker volume rm pg_data # remove a volume (container must be stopped)
docker volume prune # remove all unused volumesBind Mounts (Best for Local Development)
Bind mounts map a directory on your host machine directly into the container. Changes on the host are immediately visible inside the container — perfect for hot-reload development.
docker run -d -p 3000:3000 -v $(pwd):/app -v /app/node_modules my-app:devThe second -v /app/node_modules is an anonymous volume that prevents the host's node_modules from overwriting the container's — a common gotcha on macOS.
In docker-compose.yml:
services:
api:
build: .
volumes:
- .:/app # bind mount: host source → container
- /app/node_modules # anonymous volume: protect container's node_modulesAvoid bind mounts in production — they couple your container to a specific host path.
tmpfs Mounts (In-Memory Storage)
tmpfs mounts store data in the host's memory — data is fast but lost when the container stops. Useful for sensitive temporary data you explicitly don't want persisted to disk.
docker run -d --tmpfs /tmp:rw,noexec,nosuid,size=100m my-app:latestOr in docker-compose.yml:
services:
api:
image: my-app:latest
tmpfs:
- /tmp:size=100mBacking Up and Restoring Volumes
Backup a named volume to a tar archive:
docker run --rm -v pg_data:/data -v $(pwd):/backup alpine tar czf /backup/pg_data.tar.gz -C /data .Restore from backup:
docker run --rm -v pg_data:/data -v $(pwd):/backup alpine tar xzf /backup/pg_data.tar.gz -C /dataVolume Permissions
Databases and applications often run as non-root users. Set ownership when initializing a volume:
FROM node:20-alpine
WORKDIR /app
# Create uploads directory owned by the node user
RUN mkdir -p /app/uploads && chown node:node /app/uploads
VOLUME /app/uploads
USER nodeChoosing the Right Storage Type
| Use Case | Recommended Storage |
|---|---|
| Database files (PostgreSQL, MySQL) | Named volume |
| File uploads | Named volume |
| Source code (local dev) | Bind mount |
| Sensitive temp files | tmpfs |
| Cache (Redis) | Named volume |
| Config files | Bind mount or secrets |
Databases in Production: Use Managed Services
For production workloads, self-managed databases in containers require operational expertise — backups, failover, replication, and disk management. PandaStack offers managed PostgreSQL, MySQL, Redis, and MongoDB databases with automated backups, point-in-time recovery, and connection pooling built in.
You get the data persistence without the operational burden. Pair a managed database with your containerized application deployed via:
npm install -g @pandastack/cli
panda deploy --type container --repo your-org/your-repoConnect your container to the managed database using environment variables set from [dashboard.pandastack.io](https://dashboard.pandastack.io). See [docs.pandastack.io](https://docs.pandastack.io) for the full guide.