Skip to content

Volumes

Holden manages persistent storage for your apps using Docker bind mounts. This page explains the directory structure and permissions handling.

There are two ways to specify volumes in holden.yml:

FormatExampleBehavior
Relative./uploads:/app/uploadsStored under app’s data directory
Absolute/mnt/nas:/app/dataPassed through as-is

Relative volumes are stored at the app level:

{data_dir}/{path}

Where data_dir is either:

Example for app myapp with HOLDEN_BASE_DATA_DIR=/appdata:

holden.yml
services:
web:
volumes:
- ./uploads:/app/uploads
- ./cache:/tmp/cache

Creates:

/appdata/myapp/uploads/
/appdata/myapp/cache/

Multiple services can mount the same volume by using the same relative path:

services:
web:
image: myapp:latest
volumes:
- ./uploads:/app/uploads
worker:
image: myapp:latest
volumes:
- ./uploads:/data/uploads # Same host path, different container path

Both services access the same /appdata/myapp/uploads/ directory on the host.

Absolute paths are passed directly to Docker. Use these for:

  • Shared NAS mounts
  • Pre-existing data directories
  • Paths outside the data directory
volumes:
- /mnt/nas/shared:/app/assets:ro

A typical data directory looks like:

  • Directory/appdata/
    • Directorymyapp/
      • Directoryuploads/
      • Directorycache/
      • Directorypostgres/
      • Directoryvalkey/

App volumes and needs data live side by side under the app’s data directory.

When a Docker image specifies a non-root USER, Holden automatically ensures relative volume directories have correct ownership. Before starting the container, Holden:

  1. Inspects the image to detect the configured USER
  2. Creates the directory if it doesn’t exist
  3. Runs chown with the image’s UID:GID on the directory

This prevents permission errors when containers run as non-root users — no manual configuration needed.

Existing directory with wrong owner — Holden chowns it to match the image’s user.

Parent directory permissions — If Holden can’t create or chown the directory (e.g., parent is root-owned and Holden lacks permissions), it fails with a clear error. Fix the parent permissions or run Holden as root.

Absolute volumes — Holden does not chown absolute paths. These are “bring your own” — you manage permissions externally. This includes NAS mounts, shared directories, and any path starting with /.

Root or unset user — If the image runs as root (or doesn’t specify a user), Holden skips chown entirely.

Named users — Some images specify a username (e.g., bun) rather than a numeric UID. Holden resolves named users to their numeric UID automatically by inspecting the image.

Needs containers (postgres, valkey, garage) store data in their own subdirectory:

/appdata/myapp/postgres/
/appdata/myapp/valkey/
/appdata/myapp/garage/

These are managed automatically. You don’t need to configure volumes for needs.