Skip to content
Hoody.com

Share a directory from one container to others, including containers on a different physical server. That covers multi-service applications, team collaboration, and data exchange without duplicating files.


Storage shares API:

Create and manage shares:

Receive and mount shares:


Storage shares use a two-party system:

The source decides what is shared

The share creator sets:

  • Which directory to share (source_path)
  • Access mode (readonly or readwrite)
  • Who can access (1-to-1 or project-wide)
  • When it expires (optional)
  • Enable/disable anytime

The target decides whether to mount

The share receiver controls:

  • Whether to mount the share
  • Can reject shares they don’t need
  • Can unmount anytime
  • Independent of share status

Key principle: the source controls what is shared; the target controls whether it is mounted.


Share one directory with a single target container:

Terminal window
# Create 1-to-1 container share (readonly)
hoody storage create --container $SOURCE_ID \
--source-path "/hoody/storage/shared-assets" \
--target-container-id $TARGET_ID \
--mode readonly \
--description "Static assets for frontend container"
POST Create 1-to-1 container share
/api/v1/containers/{source_id}/storage/shares
Click "Run" to execute the request

Use when:

  • Backend sharing data with frontend
  • Database container sharing with API container
  • Specific service-to-service integration

Share a directory with every container in a project:

Terminal window
# Create project-wide share: all containers in project can access
hoody storage create --container $SOURCE_ID \
--source-path "/hoody/storage/config" \
--target-project-id $PROJECT_ID \
--mode readonly \
--description "Shared configuration for all services"
POST Create project-wide share
/api/v1/containers/{source_id}/storage/shares
Click "Run" to execute the request

Every container in the project mounts this share by default.

Use when:

  • Shared configuration across all services
  • Common assets or libraries
  • Team-wide resources

{
"mode": "readonly"
}

Target containers can:

  • Read files
  • List directories
  • Check metadata
  • Cannot create files
  • Cannot modify files
  • Cannot delete files

Suited to:

  • Static assets (images, CSS, JS)
  • Configuration files
  • Reference data
  • Logs (share read-only for monitoring)

Terminal window
# In source container
mkdir -p /hoody/storage/team-assets
# Add files
cp logo.png /hoody/storage/team-assets/
cp styles.css /hoody/storage/team-assets/

When a target container mounts a share, the files appear at:

Terminal window
/hoody/shares/{share-alias}/

Example:

Terminal window
# Source creates share with alias "config"
POST /storage/shares
{
"source_path": "/hoody/storage/app-config",
"alias": "config"
}
# Target mounts share
PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
{"mount": true}
# Files now accessible at:
/hoody/shares/config/
├── app.yaml
├── database.json
└── secrets.env

The alias is reflected in the mount point name. Hoody’s infrastructure determines the exact mount path, and the caller cannot set it directly.


A backend shares its upload directory with several frontends:

Terminal window
# Backend Container (source)
POST /containers/{backend_id}/storage/shares
{
"source_path": "/hoody/storage/user-uploads",
"target_project_id": "{project_id}",
"mode": "readwrite",
"alias": "uploads"
}
# Frontend Container 1 (accepts)
PATCH /containers/{frontend_1}/storage/incoming/{share_id}/mount
{"mount": true}
# Frontend Container 2 (accepts)
PATCH /containers/{frontend_2}/storage/incoming/{share_id}/mount
{"mount": true}
# Now all three containers see same /hoody/shares/uploads/
# Upload from any frontend → visible to backend and other frontends

A config container shares settings with all services in readonly mode:

Terminal window
# Config Container
POST /containers/{config_id}/storage/shares
{
"source_path": "/hoody/storage/production-config",
"target_project_id": "{project_id}",
"mode": "readonly",
"alias": "config"
}
# All service containers mount it
# Services read from /hoody/shares/config/
# Only config container can update (others readonly)

Two developers share a workspace between their containers:

Terminal window
# Developer A's container
POST /containers/{dev_a}/storage/shares
{
"source_path": "/home/user/project",
"target_container_id": "{dev_b_container}",
"mode": "readwrite",
"alias": "shared-project"
}
# Developer B mounts in their container
# Both containers read and write the same files
# A write from one is visible to the other

Services share their logs with a monitoring container in readonly mode:

Terminal window
# Service 1
POST /storage/shares
{
"source_path": "/hoody/storage/service1/logs",
"target_container_id": "{monitor_container}",
"mode": "readonly"
}
# Service 2
POST /storage/shares
{
"source_path": "/hoody/storage/service2/logs",
"target_container_id": "{monitor_container}",
"mode": "readonly"
}
# Monitor container sees all logs
/hoody/shares/service1-logs/
/hoody/shares/service2-logs/

A share reports one of these statuses:

StatusDescriptionNext Steps
activeThe share specification is retained. This is not proof that it is enabled, unexpired, mounted, or writableCheck enabled, expires_at, and status_message
failedMount failed (see status_message)Check errors, fix, retry

active means “this share specification is retained”, not “everything landed”. A share stays active while cross-server read-write publication is paused or provisioning is deferred to the background reconciler; those conditions are explained in status_message. It can also stay active while a consumer is unmounted, but status_message does not report per-consumer mount state.

Check status:

Terminal window
GET /api/v1/containers/{id}/storage/shares/{shareId}
# Response includes: "status": "active" AND "status_message"
# status_message is null when nothing is degraded. Read it, not just status.

The source container can disable a share without deleting it:

Terminal window
# Disable share temporarily
PATCH /api/v1/containers/{source_id}/storage/shares/{share_id}
{
"enabled": false,
"description": "Temporarily disabled for maintenance"
}
# Files unmount from target containers
# Share configuration preserved
# Re-enable later
PATCH /api/v1/containers/{source_id}/storage/shares/{share_id}
{
"enabled": true
}

Use for: maintenance windows, testing, and gradual rollouts.

Set an expiration timestamp at creation:

Terminal window
POST /api/v1/containers/{id}/storage/shares
{
"source_path": "/hoody/storage/temp-files",
"target_container_id": "{target}",
"mode": "readonly",
"expires_at": 1735689600 # Unix timestamp: 2025-01-01
}
# Share auto-unmounts and notifies before expiry

Suited to: temporary access, demo environments, and time-limited shares.


Terminal window
# Shares you created from specific container
hoody storage list --container $SOURCE_ID
# All shares you created (across all containers)
hoody storage list-all
Terminal window
# Incoming shares for specific container
hoody storage incoming list --container $TARGET_ID
# All incoming shares (all your containers)
hoody storage incoming list-all
Terminal window
# Upgrade share from readonly to readwrite
hoody storage update --container $SOURCE_ID --share-id $SHARE_ID \
--mode readwrite --description "Now allows writes"

Target containers must remount to pick up the new mode.

Terminal window
# Delete share: unmounts from all target containers
hoody storage delete $SHARE_ID --yes

Storage shares work across different physical servers with full POSIX compliance:

┌──────────────────────────────┐
│ Server US-West-1 │
│ ┌──────────────────────┐ │
│ │ Source Container │ │
│ │ /shared/data/ │ │
│ └──────────────────────┘ │
│ ↓ Hoody handles │
└──────────────────────────────┘
↓ cross-server
┌──────────────────────────────┐
│ Server EU-Central-1 │
│ ┌──────────────────────┐ │
│ │ Target Container │ │
│ │ /hoody/shares/data/ │ │
│ └──────────────────────┘ │
└──────────────────────────────┘

What you get:

  • Cross-server mounting: works whether the containers sit on the same server or different ones
  • Full POSIX compliance: all filesystem operations behave normally
  • File locks respected: concurrent access is coordinated
  • No per-share setup for supported modes: once CROSS_HOST_NFS_ENABLED=true, cross-server read-only shares use the normal share workflow; cross-server readwrite also requires CROSS_HOST_NFS_ALLOW_RW=true
  • Same API, conditional capability: the share API is the same for same-server and cross-server targets, but cross-server read-write creation is rejected unless read-write is enabled

What happens when the source container is stopped?

Section titled “What happens when the source container is stopped?”

The share remains mounted but inaccessible.

  • Target containers see mount point: /hoody/shares/{alias}/
  • Attempting to read files: Stale file handle error
  • When source restarts: Access restored automatically

Best practice: do not rely on shares from containers that stop frequently.

Can I share the same directory to multiple containers?

Section titled “Can I share the same directory to multiple containers?”

Yes. Create multiple shares from the same source_path:

Terminal window
# Share /hoody/storage/assets with 3 containers
POST /storage/shares {"source_path": "/hoody/storage/assets", "target_container_id": "A"}
POST /storage/shares {"source_path": "/hoody/storage/assets", "target_container_id": "B"}
POST /storage/shares {"source_path": "/hoody/storage/assets", "target_container_id": "C"}
# Or share once to entire project (all containers see it)
POST /storage/shares {"source_path": "/hoody/storage/assets", "target_project_id": "{project}"}

Can I share /hoody/databases/ directories?

Section titled “Can I share /hoody/databases/ directories?”

Not recommended for SQLite databases. You can share /hoody/databases/ directories, but doing so bypasses the concurrent-write safety:

Terminal window
# NOT Recommended: Sharing /hoody/databases via storage shares
POST /storage/shares
{
"source_path": "/hoody/databases",
"mode": "readwrite"
}
# Problem: Network-shared SQLite loses local-filesystem optimizations
# Better: Each container uses /hoody/databases/ locally (same-server concurrent writes)
# Better: Use hoody-sqlite HTTP API for cross-container database access

Why not recommended:

  • /hoody/databases/ concurrent-write safety is optimized for local same-server access
  • Network sharing adds latency and lock timeout risks
  • SQLite is not designed for network filesystems

Better solutions:

  • Same server: each container accesses /hoody/databases/ directly (concurrent-write-safe)
  • Cross-server: use the hoody-sqlite HTTP API to access databases remotely
  • Data sharing: share application data directories, not database files

Share remains available but unmounted. Target can accept later:

Terminal window
# Initially reject
PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
{"mount": false}
# Accept later when needed
PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
{"mount": true}
# Files appear in /hoody/shares/{alias}/

Do shares persist through container restarts?

Section titled “Do shares persist through container restarts?”

Yes. Share configuration and mount state survive:

  • Source container restart: the share remains
  • Target container restart: the mount point is restored
  • Both restart: everything reconnects

Can I change the alias after creating a share?

Section titled “Can I change the alias after creating a share?”

Yes:

Terminal window
PATCH /api/v1/containers/{source_id}/storage/shares/{share_id}
{
"alias": "new-alias"
}

Target containers must unmount and remount to use the new alias. The old mount point /hoody/shares/old-alias/ becomes invalid.

Source container: the files count toward the source’s storage.

Target containers: mounted shares do not count toward the target’s storage quota, because they are references rather than copies.


Problem: Share status is "failed" with error message

Common causes:

  1. Source path doesn’t exist:

    Terminal window
    # In source container, verify path exists
    ls -la /hoody/storage/shared-path
    # Create if missing
    mkdir -p /hoody/storage/shared-path
    # Update share to retry
    PATCH /api/v1/containers/{source_id}/storage/shares/{share_id}
    {"enabled": true}
  2. Permission issues:

    Terminal window
    # Fix permissions in source container
    chown -R root:root /hoody/storage/shared-path
    chmod -R 755 /hoody/storage/shared-path
  3. Source container stopped:

    • Start source container
    • Share will automatically reactivate
  4. Not failed, but degraded: a share whose read-write is paused stays active, not failed. If writes are rejected while status reads active, check status_message first. When populated, it names the required action: set a disk size limit when the owner container has no enforced quota; remove a nested subvolume that escapes the quota; wait for the owner host to regain enforced disk quotas when the host itself cannot enforce them. Read-write publication resumes automatically after any of those conditions is fixed. For an already-published share, however, an owner-host quota-enforce capability miss can render the export read-only while deliberately leaving status_message null or unchanged; restore quota enforcement on the owner host in that case.

Problem: Share mounted but /hoody/shares/{alias}/ is empty

Solutions:

  1. Verify the share is active, and read status_message:

    Terminal window
    GET /api/v1/containers/{target_id}/storage/incoming
    # Check: "status": "active", "enabled": true
    # `status_message` can explain degraded or deferred provisioning.
    # This response does not report whether this consumer is mounted.
  2. Check source container is running:

    Terminal window
    GET /api/v1/containers/{source_id}
    # Verify: "status": "running"
  3. Verify files exist in source:

    Terminal window
    # In source container
    ls /hoody/storage/shared-path
    # Should show files
  4. Unmount and remount:

    Terminal window
    PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
    {"mount": false}
    PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
    {"mount": true}

Problem: Cannot access files in /hoody/shares/{alias}/

Cause: Source container restarted while target was accessing files

Solution:

Terminal window
# Unmount and remount
PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
{"mount": false}
PATCH /api/v1/containers/{target_id}/storage/incoming/{share_id}/mount
{"mount": true}
# Or restart target container (auto-remounts)
POST /api/v1/containers/{target_id}/restart
# (Consolidated lifecycle route: POST /api/v1/containers/{id}/{operation}
# where {operation} is one of: start | stop | force-stop | restart | pause | resume)

Share appears in incoming but will not mount

Section titled “Share appears in incoming but will not mount”

Problem: PATCH /mount succeeds but files don’t appear

Check:

  1. Share is enabled:

    Terminal window
    GET /api/v1/containers/{target_id}/storage/incoming
    # Find the share in the list and verify: "enabled": true
  2. Share not expired:

    Terminal window
    # Check expires_at (Unix timestamp)
    # If expired, ask source to extend or remove expiration
  3. Target container has permission:

    • Verify target is in specified project (for project-wide shares)
    • Verify target_container_id matches (for 1-to-1 shares)

Share subdirectories, not /hoody/storage itself

Section titled “Share subdirectories, not /hoody/storage itself”
Terminal window
# Good: Specific subdirectory
{"source_path": "/hoody/storage/assets"}
# Risky: Entire Hoody Kit storage
{"source_path": "/hoody/storage"}
# Sharing entire /hoody/storage exposes all service data

Use project-wide shares for common resources

Section titled “Use project-wide shares for common resources”

One share for all containers:

Terminal window
# Instead of creating 10 identical 1-to-1 shares
POST /storage/shares {"target_project_id": "{project}"}
# All containers in project can mount
# Simpler management, one configuration

Multiple writers can conflict:

Terminal window
# Container A writes /hoody/shares/data/file.txt
# Container B writes /hoody/shares/data/file.txt (same file)
# Last write wins (potential data loss)

Solution: use application-level locking, or coordinate writes (for example, a different directory per container).

For SQLite databases, use /hoody/databases/, which is concurrent-write safe.

Deleting a share unmounts it from every target:

Terminal window
# Snapshot source container first
POST /api/v1/containers/{source_id}/snapshots
{"alias": "before-share-deletion"}
# Then delete share
DELETE /api/v1/storage/shares/{share_id}
Terminal window
# Clear documentation
{
"description": "Read-only access to team logo, CSS, and JS assets for frontend containers. Source: /hoody/storage/static-web-assets"
}
# Vague
{
"description": "shared files"
}

This matters once you are managing dozens of shares.


Storage ecosystem:

Related features:

Summary:

  • The source container controls what is shared
  • The target container controls whether it mounts the share
  • Two modes: readonly, readwrite
  • Two types: 1-to-1, project-wide
  • Works cross-server
  • Combine with /hoody/databases/ for shared SQLite databases