Persistent Data
Containers are ephemeral
When you stop and remove a container, everything inside it is gone. The SQLite database, the uploaded files, the session store — all lost. The next docker run starts from a clean image.
This is by design. Containers are disposable. You should be able to destroy and recreate them without losing data. But the data has to live somewhere.
Docker volumes
A volume is storage that lives outside the container. It persists when the container is removed and can be attached to new containers.
# Create a named volume
docker volume create app-data
# Run a container with the volume
docker run -d \
-v app-data:/data \
-e DATABASE_URL=/data/app.db \
myapp -v app-data:/data mounts the app-data volume at /data inside the container. The SQLite database at /data/app.db persists across container restarts, stops, and removals.
Volumes in Docker Compose
services:
app:
build: .
volumes:
- app-data:/data
- uploads:/app/uploads
environment:
- DATABASE_URL=/data/app.db
volumes:
app-data: # Persists the SQLite database
uploads: # Persists uploaded files Named volumes are declared at the bottom of the compose file. Docker manages their location on the host filesystem.
What to put in volumes
Databases. SQLite files, PostgreSQL data directories. Without a volume, the database resets on every container restart.
Uploaded files. Files uploaded by users (from the File Uploads course). Without a volume, uploads disappear when the container restarts.
Application state. Session stores, caches, logs — anything that needs to survive a restart.
What NOT to put in volumes
Application code. The code is in the image. Mounting code via volumes is for development only (live reload). In production, the image contains the code.
node_modules. Installed inside the image during docker build. Mounting from the host causes OS-mismatch issues (same problem as .dockerignore).
Secrets. Use environment variables, not files in volumes, for secrets.
Bind mounts vs named volumes
Named volumes (-v app-data:/data): Docker manages the storage location. Portable. Easy to back up with docker volume commands.
Bind mounts (-v /host/path:/container/path): Maps a specific host directory to a container path. You control the location. Good for development (mount source code) and for accessing host files (SSL certificates, nginx config).
# Named volume (Docker manages location)
volumes:
- app-data:/data
# Bind mount (specific host path)
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro The :ro suffix makes the bind mount read-only inside the container. Nginx reads the config but cannot modify it.
Backing up volumes
# Backup a volume to a tar file
docker run --rm \
-v app-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/app-data-backup.tar.gz -C /data .
# Restore from a tar file
docker run --rm \
-v app-data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/app-data-backup.tar.gz -C /data This creates a temporary Alpine container, mounts the volume and a host directory, and copies the data. Production apps should automate this as a cron job.
Exercises
Exercise 1: Run your app with a named volume for the database. Create some data. Stop and remove the container. Start a new container with the same volume. Verify the data is still there.
Exercise 2: Run your app without a volume. Create data. Remove the container. Start a new one. The data is gone.
Exercise 3: Back up your database volume to a tar file. Restore it to a new volume. Verify the data is intact.
What happens to data inside a container when the container is removed?