Named volumes are great for keeping data alive across container restarts. The default behavior mounts the entire volume root at the container target. Sometimes you want to mount only a sub-directory — for instance, a single service among many sharing the same named volume, each scoped to its own directory. The subpath option does exactly that.

The basic shape

services:
  app:
    image: myapp
    volumes:
      - type: volume
        source: shared-data
        target: /app/data
        volume:
          subpath: app

volumes:
  shared-data:

The named volume shared-data exists once on the host. The app container sees only the app/ directory of it mounted at /app/data. Other directories in shared-data are invisible to app.

A multi-service example

Several services share a single named volume and each one lives in its own corner:

services:
  api:
    image: myapi
    volumes:
      - type: volume
        source: app-data
        target: /var/data
        volume:
          subpath: api

  worker:
    image: myworker
    volumes:
      - type: volume
        source: app-data
        target: /var/data
        volume:
          subpath: worker

  backup:
    image: alpine
    command: tar -czf /backup/snapshot.tgz /source
    volumes:
      - type: volume
        source: app-data
        target: /source
        # No subpath, sees the whole volume

volumes:
  app-data:

api and worker see their own scoped directory. backup mounts the volume root and can archive everything in one shot. One named volume, one backup target, clear isolation between services.

Why not just use multiple volumes?

Three reasons subpath is preferable to N separate named volumes when the data is conceptually one dataset:

  • Single backup unit: one volume, one snapshot, one restore.
  • Shared driver options: NFS settings (Tip #66), encryption, performance tuning — declared once, inherited by every consumer.
  • Atomic migration: moving the underlying storage moves all sub-paths at once.

When the data is genuinely unrelated, prefer separate volumes. subpath is for the case where the directories naturally belong together.

The first-mount creates the directory

If the sub-directory does not exist when a container starts, Compose creates it. The first container to mount subpath: foo will see an empty directory at the target. Subsequent containers see whatever is in there.

This matters for permissions: the directory is created with the running container’s UID. If different services run as different users, set the ownership explicitly in an init container or with the user: directive (Tip #14).

Read-only sub-paths

subpath combines with read_only for a config-style mount where the producer writes once and consumers read:

services:
  config-writer:
    image: my-config-builder
    volumes:
      - type: volume
        source: shared
        target: /out
        volume:
          subpath: config

  reader:
    image: myapp
    volumes:
      - type: volume
        source: shared
        target: /etc/myapp
        read_only: true
        volume:
          subpath: config

volumes:
  shared:

The config-writer populates /out (the config subpath); reader mounts the same directory read-only at /etc/myapp.

Image-backed volumes (Compose 2.35+)

A close cousin of volume.subpath is image.subpath, available since Compose v2.35.0. It mounts a directory from an OCI image as a volume, scoped to one sub-path:

services:
  app:
    image: myapp
    volumes:
      - type: image
        source: my-assets:v1
        target: /assets
        image:
          subpath: web/static

Use it for read-only datasets packaged as container images — model weights, static sites, sample data — when you only need part of the image’s filesystem.

What subpath does not do

  • It does not let you mount a path outside the volume root (no ../escape).
  • It does not work with bind mounts — for bind mounts, just adjust the host path itself.
  • It does not change permissions, ownership, or read/write mode — those still come from read_only, user, or the volume driver.

Pro tip: name the sub-paths the same as the services

A small naming convention pays off in larger stacks:

volume:
  subpath: ${SERVICE_NAME}    # or hardcode to match the service key

When you later run docker volume inspect <project>_shared and walk the directory tree, the structure mirrors your compose.yaml and you can map files back to their owners in one glance.

Further reading