Volumes & Storage
Named volumes, bind mounts, tmpfs, and volume drivers. You'll hit this when container restarts lose your database data or your local file edits don't appear inside the container.
services:
db:
image: postgres:16
volumes:
# Anonymous volume
- /var/lib/postgresql/dataservices:
db:
image: postgres:16
volumes:
# Anonymous volume
- /var/lib/postgresql/dataservices:
db:
image: postgres:16
volumes:
# Named volume
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:services:
db:
image: postgres:16
volumes:
# Named volume
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Anonymous volumes get a random hash name like a1b2c3d4.... They're hard to identify, easy to lose track of, and accumulate as orphans over time. Running docker compose down -v deletes them, making accidental data loss more likely.
Named volumes are easy to identify, back up, and manage with docker volume commands. They have a meaningful name (pgdata) so you can find them later. They persist across docker compose down by default.
services:
app:
build: .
volumes:
# Copies node_modules from host
- .:/appservices:
app:
build: .
volumes:
# Copies node_modules from host
- .:/appservices:
app:
build: .
volumes:
- .:/app
# Preserve container's node_modules
- /app/node_modulesservices:
app:
build: .
volumes:
- .:/app
# Preserve container's node_modules
- /app/node_modulesBind-mounting the entire project directory overwrites node_modules inside the container with whatever is on your host. If your host OS differs from the container (e.g., macOS vs Linux), native modules will be incompatible and your app will crash.
The anonymous volume at /app/node_modules prevents the host bind mount from overwriting the container's installed dependencies. The container keeps its own node_modules (built for its OS and architecture) while your source code is still synced from the host.
services:
nginx:
image: nginx:alpine
volumes:
# Read-write access to config
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/usr/share/nginx/htmlservices:
nginx:
image: nginx:alpine
volumes:
# Read-write access to config
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/usr/share/nginx/htmlservices:
nginx:
image: nginx:alpine
volumes:
# Read-only access to config
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:roservices:
nginx:
image: nginx:alpine
volumes:
# Read-only access to config
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:roWithout :ro, the container has full read-write access to mounted files. A compromised Nginx process could overwrite your configuration or inject malicious content into your HTML files, affecting the host filesystem.
The :ro flag makes bind mounts read-only inside the container. If the container is compromised, it cannot modify your configuration files or static assets on the host. This follows the principle of least privilege.
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Temp files written to container layer
# Visible in docker inspect, image history
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Temp files written to container layer
# Visible in docker inspect, image history
CMD ["node", "server.js"]# In docker-compose.yml
services:
app:
build: .
tmpfs:
- /app/tmp:size=100m
# Or in docker run:
# docker run --tmpfs /app/tmp:size=100m# In docker-compose.yml
services:
app:
build: .
tmpfs:
- /app/tmp:size=100m
# Or in docker run:
# docker run --tmpfs /app/tmp:size=100mWriting temporary files to the container's writable layer persists them to disk. They can be recovered from the container filesystem, show up in docker diff, and survive container restarts. Sensitive data should never be written to the container layer.
tmpfs mounts store data in memory only. It never touches disk, is not included in image layers, and is automatically cleaned up when the container stops. This is ideal for session tokens, temporary uploads, and other sensitive ephemeral data.