Skip to content

Volumes ​

Portler manages project-scoped Docker named volumes for you, with first-class support for forking data — made for the git-worktree workflow of testing a migration against a copy of your database.

Managed @ volumes ​

Declare a managed volume by prefixing the source with @:

yaml
services:
  postgres:
    image: postgres:16-alpine
    port: 5432
    volumes:
      - '@postgres-data:/var/lib/postgresql/data'

@postgres-data becomes the Docker volume portler-<project>-<hash>-postgres-data, where <project> and <hash> come from the project root (under --prod with a top-level name: it is portler-<name>--postgres-data instead, so a release that unpacks into a new folder keeps it; see Stable identity on a server). Portler creates it with portler.* labels before the first run so it can find it again later. Before mounting it, Portler verifies that project label again (including after a concurrent create race); an unlabelled or foreign volume that merely has the expected name is refused rather than exposed to the service.

Stable across git worktrees

Linked worktrees of the same repository resolve @ volumes to the main repository's volumes, so a worktree sees the same data as the main checkout. Containers and networks stay per-directory — only volumes are shared.

Listing volumes ​

bash
portler volumes

Lists this project's volumes with their volume set, declaring services, size, and the containers using them.

Forking a volume ​

bash
portler volumes fork <volume> <new-set>

Clones a volume's data into a <volume>--<new-set> variant:

bash
portler volumes fork postgres-data migration

The copy runs cp -a inside a throwaway alpine:3 container, so the original is untouched. <volume> can be the short name from portler.yml, a name--set variant, or the full Docker volume name. Both the source and the newly-created target must carry this project's ownership label; a name match alone never authorizes reading, overwriting, or rollback-removing volume data. The target also gets a random portler.fork-attempt label: when two forks into the same new set race, only the one that created the target copies into it, the other stops with "another fork created target volume … at the same time", and a failed copy only removes a target its own attempt created.

Volume sets ​

bash
portler up --volume-set <name>

Runs the stack against the --<name> variants of all @ volumes. PORTLER_VOLUME_SET=<name> works as an environment fallback, which is handy as a per-worktree default (e.g. via direnv). Volume set names may contain a-z0-9_.- but not --.

portler restart comes back on the set a service was started with (it records it), unless you give it another one with --volume-set.

Starting with a set that was never forked creates a fresh empty variant. When the base volume or other sets already exist for that volume — usually a sign of a typo — up prints a warning first (with a did-you-mean suggestion) but still proceeds; fork an existing volume instead if you wanted its data.

The worktree flow ​

  1. Create a worktree for your branch:
    bash
    git worktree add ../myapp-migration migration-branch
  2. Fork the database volume:
    bash
    portler volumes fork postgres-data my-branch
  3. Run the worktree's stack against the fork:
    bash
    cd ../myapp-migration
    portler up --volume-set my-branch

Now you can test a destructive migration without touching the original data. Avoid running two stacks against the same volume at once.

Removing volumes ​

bash
portler volumes remove <volume> [--force]

Removes a project volume. Portler requires its project ownership label and refuses while a running container mounts it. --force bypasses only the in-use check—it never authorizes deletion of an unlabelled or foreign volume.

Older Portler versions could leave a managed-name volume unlabelled. Inspect such a legacy volume yourself with docker volume inspect <name>; after confirming its data is disposable, remove it manually with docker volume rm <name>. Portler deliberately provides no ownership bypass.

Requirements ​

All volume commands need the Docker daemon; when it is not running they fail with a clear error.

Released under the MIT License.