Learn

/

Volumes & Storage

Volumes & Storage

4 patterns

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.

Avoid
services:
  db:
    image: postgres:16
    volumes:
      # Anonymous volume
      - /var/lib/postgresql/data
services:
  db:
    image: postgres:16
    volumes:
      # Anonymous volume
      - /var/lib/postgresql/data

Prefer
services:
  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:
Why avoid

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.

Why prefer

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.

Docker docs: Volumes
Avoid
services:
  app:
    build: .
    volumes:
      # Copies node_modules from host
      - .:/app
services:
  app:
    build: .
    volumes:
      # Copies node_modules from host
      - .:/app

Prefer
services:
  app:
    build: .
    volumes:
      - .:/app
      # Preserve container's node_modules
      - /app/node_modules
services:
  app:
    build: .
    volumes:
      - .:/app
      # Preserve container's node_modules
      - /app/node_modules
Why avoid

Bind-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.

Why prefer

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.

Docker docs: Bind mounts
Avoid
services:
  nginx:
    image: nginx:alpine
    volumes:
      # Read-write access to config
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./html:/usr/share/nginx/html
services:
  nginx:
    image: nginx:alpine
    volumes:
      # Read-write access to config
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./html:/usr/share/nginx/html

Prefer
services:
  nginx:
    image: nginx:alpine
    volumes:
      # Read-only access to config
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./html:/usr/share/nginx/html:ro
services:
  nginx:
    image: nginx:alpine
    volumes:
      # Read-only access to config
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./html:/usr/share/nginx/html:ro
Why avoid

Without :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.

Why prefer

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.

Docker docs: Read-only bind mounts
Avoid
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"]

Prefer
# 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=100m
Why avoid

Writing 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.

Why prefer

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.

Docker docs: tmpfs mounts