Volumes
Holden manages persistent storage for your apps using Docker bind mounts. This page explains the directory structure and permissions handling.
Volume Formats
Section titled “Volume Formats”There are two ways to specify volumes in holden.yml:
| Format | Example | Behavior |
|---|---|---|
| Relative | ./uploads:/app/uploads | Stored under app’s data directory |
| Absolute | /mnt/nas:/app/data | Passed through as-is |
Relative Volumes
Section titled “Relative Volumes”Relative volumes are stored at the app level:
{data_dir}/{path}Where data_dir is either:
HOLDEN_BASE_DATA_DIR/{app-id}(default)data_dirif overridden inholden.yml
Example for app myapp with HOLDEN_BASE_DATA_DIR=/appdata:
services: web: volumes: - ./uploads:/app/uploads - ./cache:/tmp/cacheCreates:
/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 pathBoth services access the same /appdata/myapp/uploads/ directory on the host.
Absolute Volumes
Section titled “Absolute Volumes”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:roDirectory Structure
Section titled “Directory Structure”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.
User Permissions
Section titled “User Permissions”When a Docker image specifies a non-root USER, Holden automatically ensures relative volume directories have correct ownership. Before starting the container, Holden:
- Inspects the image to detect the configured
USER - Creates the directory if it doesn’t exist
- Runs
chownwith the image’s UID:GID on the directory
This prevents permission errors when containers run as non-root users — no manual configuration needed.
Edge Cases
Section titled “Edge Cases”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 Volumes
Section titled “Needs Volumes”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.