Manage Volumes
Create named volumes, register bind mounts, attach storage to containers, inspect usage, and clean up unused volumes.
Prerequisites
- Docker is installed and running (green status indicator in Zenithal sidebar)
Scenario 1: Create a Named Volume for PostgreSQL
Named volumes are Docker-managed storage that persist data across container restarts and rebuilds.
- Click Volumes in the sidebar
- Click the + button (top-right) to open the Create Volume sheet
- The sheet shows two volume types as segmented buttons — select Named Volume (the default)
- Enter a volume name:
postgres-data- Names must use lowercase letters, numbers, hyphens, or underscores
- Example valid names:
postgres-data,redis_cache,app01-uploads
- Click Create
- The volume appears in the Volumes list showing:
- Volume name (
postgres-data) - Driver badge (
local) - Mount point path on the host filesystem
- Volume name (
There is no driver selection in the UI — all volumes are created with the default local driver.
Scenario 2: Register a Bind Mount
Bind mounts map a folder on your Mac directly into a container. They don't create Docker-managed volumes — they reference an existing directory.
- Click Volumes in the sidebar
- Click the + button to open the Create Volume sheet
- Select Bind Mount (the second segmented button)
- Enter a friendly name:
my-project-files - Set the Host Folder — either:
- Type a path directly:
/Users/you/Projects/my-app - Click the Browse button to open a folder picker
- Type a path directly:
- Click Create
Path Validation
Zenithal validates that the host folder is a valid macOS path. Accepted prefixes:
/Users/,/Volumes/,/tmp/,/private/,~/
If you enter a Linux-style path (e.g., /home/user/data), Zenithal shows a warning — these paths don't exist on macOS and will cause mount failures.
Scenario 3: Attach a Named Volume to a PostgreSQL Container
This is the most common volume workflow — creating persistent storage for a database.
- First, create a named volume called
postgres-data(see Scenario 1) - Click Containers in the sidebar
- Click the + button to open the Run Container sheet
- In the Image field, enter:
postgres:16-alpine - Zenithal auto-configures the container:
- Container name set automatically
- Port
5432mapped automatically POSTGRES_PASSWORDandPOSTGRES_DBadded with a generated secure password- Mount path suggested as
/var/lib/postgresql(v18+) or/var/lib/postgresql/data(v16 and older)
- Expand the Storage section
- Switch the volume type picker from Host Folder to Named Volume
- Select
postgres-datafrom the dropdown - Set Container Path to:
/var/lib/postgresql/data(for postgres 16) - Read Only: leave OFF (databases need write access)
- Click Run
Verifying Persistence
To confirm the volume is working:
- Connect to the running postgres container via the Terminal tab
- Create a test database:
psql -U postgres -c "CREATE DATABASE testdb;"- Stop and delete the container
- Create a new postgres container with the same
postgres-datavolume mounted - Connect again and verify the database still exists:
psql -U postgres -c "\l"Scenario 4: Attach a Bind Mount to an Nginx Container
Use a bind mount to serve local files directly from your Mac.
- Open the Run Container sheet (Containers → +)
- Image:
nginx:alpine - Container Name:
my-nginx - Expand the Storage section — Host Folder is selected by default
- Set the host folder:
- Click the Browse button and select
~/Projects/my-site - Or type the path directly
- Click the Browse button and select
- Set Container Path to:
/usr/share/nginx/html- Zenithal suggests this path automatically for nginx images (lightbulb icon)
- Toggle Read Only to ON (nginx only needs to read the files)
- Add a port mapping: Host
8080→ Container80(tcp) - Click Run
- Open
http://localhost:8080in your browser to see your site
Example File
Create ~/Projects/my-site/index.html before running:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello from Zenithal</title>
</head>
<body style="font-family: system-ui; text-align: center; padding: 4rem;">
<h1>Hello from Nginx</h1>
<p>This file is served from a bind mount on your Mac</p>
</body>
</html>Scenario 5: Inspect Volume Usage
- Click Volumes in the sidebar
- The list shows all named volumes (anonymous 64-character hash volumes are hidden)
- Each volume row displays:
- Icon: lock icon (orange) if in use by a container, drive icon if unused
- Name: the volume identifier
- Driver: badge showing the storage driver (typically
local) - Status: either "Used by: container-name" (orange text) or the mount point path
- Volumes currently attached to a container show the lock icon and cannot be deleted
Scenario 6: Clean Up Unused Volumes
Over time, unused volumes accumulate and consume disk space. Zenithal makes it easy to prune them.
- Click Volumes in the sidebar
- Click the Cleanup button (trash icon) in the toolbar
- A confirmation dialog appears: "This will permanently remove all volumes not used by any container. Data in these volumes cannot be recovered."
- Click Cleanup to confirm
- Zenithal shows the result:
- Success: "Removed X unused volume(s). Reclaimed: Y MB"
- Nothing to clean: "No unused volumes found."
- Volumes attached to any container (running or stopped) are never removed
Scenario 7: Delete a Specific Volume
- Click Volumes in the sidebar
- Find the volume you want to delete
- Click the trash icon on the volume row
- If the volume is in use (lock icon), the delete button is grayed out
- Confirm in the dialog: "Are you sure you want to permanently delete 'volume-name'? This data cannot be recovered."
- Click Delete
If the volume is still attached to a stopped container, you'll see: "This volume is currently in use by a container. Please stop the container first."
What You'll See
- Volumes with a lock icon are in use — they cannot be deleted
- Unused volumes show a drive icon and a delete button
- The Cleanup button removes all unused volumes at once
- Anonymous volumes (64-character hashes) are hidden from the list
Tips
- Named volumes vs bind mounts: Use named volumes for data you want Docker to manage (databases, caches). Use bind mounts for files you edit on your Mac (source code, config files)
- Database storage: Always use a named volume for database data directories (
/var/lib/postgresqlfor postgres 18+,/var/lib/postgresql/datafor older,/var/lib/mysql,/data/db). Without a volume, data is lost when the container is removed - PostgreSQL 18+ mount path: Starting with PostgreSQL 18, mount at
/var/lib/postgresql(the parent directory) instead of/var/lib/postgresql/data. This allowspg_upgrade --linkto work without mount boundary issues - Read-only mounts: Toggle Read Only ON for bind mounts where the container only reads files (e.g., nginx serving static HTML). This prevents accidental writes
- Mount path suggestions: When creating a container, Zenithal shows suggested mount paths for known images (e.g.,
/usr/share/nginx/htmlfor nginx) - Pruning is safe: Cleanup only removes volumes with zero container references — running and stopped containers' volumes are protected
- Bind mounts don't appear here: Bind mounts are not Docker-managed volumes and don't show in the Volumes list. Check the container's Inspect view instead
- Volume lifecycle: Deleting a container does NOT delete its named volumes — you must prune or delete them separately