# Shared Storage

**Page:** foundation/storage/sharing-files

[Download Raw Markdown](./foundation/storage/sharing-files.md)

---

# Shared Storage

**Share directories from one container to others—automatically works across servers.** Perfect for multi-service applications, team collaboration, and data exchange without duplicating files.


**Cross-Server Magic:** Storage shares automatically work between containers on **different physical servers**. Hoody handles all the complexity—full POSIX compliance including file locks is maintained transparently.


---

## API Endpoints Summary

**Complete Storage Shares API:**

**Creating & Managing Shares:**
- **[POST /api/v1/containers/\{id\}/storage/shares](/api/storage-shares/)** - Create new share
- **[GET /api/v1/containers/\{id\}/storage/shares](/api/storage-shares/)** - List your created shares
- **[PATCH /api/v1/containers/\{id\}/storage/shares/\{shareId\}](/api/storage-shares/)** - Update share config
- **[DELETE /api/v1/storage/shares/\{shareId\}](/api/storage-shares/)** - Delete share

**Receiving & Mounting Shares:**
- **[GET /api/v1/containers/\{id\}/storage/incoming](/api/storage-shares/)** - List incoming shares
- **[PATCH /api/v1/containers/\{id\}/storage/incoming/\{shareId\}/mount](/api/storage-shares/)** - Accept/reject share
- **[GET /api/v1/storage/incoming](/api/storage-shares/)** - All incoming shares (all containers)

---

## Core Architecture

**Storage shares use a two-party system:**



**Share Creator Controls:**
- 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



**Share Receiver Controls:**
- Whether to mount the share
- Can reject shares they don't need
- Can unmount anytime
- Independent of share status



**Key principle:** **Source controls WHAT. Target controls IF.**

---

## Share Types

### 1-to-1 Container Share

**Share specific directory with ONE container:**


  
    ```bash
    # 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"
    ```
  
  
    ```typescript
    const share = await client.api.storageShares.create(
      SOURCE_CONTAINER_ID,
      {
        source_path: '/hoody/storage/shared-assets',
        target_container_id: TARGET_CONTAINER_ID,
        mode: 'readonly',
        description: 'Static assets for frontend container'
      }
    );
    console.log(share.data.id); // Share ID for mounting
    ```
  
  
    ```bash
    curl -X POST "https://api.hoody.icu/api/v1/containers/$SOURCE_ID/storage/shares" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "source_path": "/hoody/storage/shared-assets",
        "target_container_id": "'$TARGET_ID'",
        "mode": "readonly",
        "description": "Static assets for frontend container"
      }'
    ```
  




**Use when:**
- Backend sharing data with frontend
- Database container sharing with API container
- Specific service-to-service integration

### Project-Wide Share

**Share directory with ALL containers in a project:**


  
    ```bash
    # 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"
    ```
  
  
    ```typescript
    const share = await client.api.storageShares.create(
      SOURCE_CONTAINER_ID,
      {
        source_path: '/hoody/storage/config',
        target_project_id: PROJECT_ID,
        mode: 'readonly',
        description: 'Shared configuration for all services'
      }
    );
    // Every container in the project automatically mounts this share
    ```
  
  
    ```bash
    curl -X POST "https://api.hoody.icu/api/v1/containers/$SOURCE_ID/storage/shares" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "source_path": "/hoody/storage/config",
        "target_project_id": "'$PROJECT_ID'",
        "mode": "readonly",
        "description": "Shared configuration for all services"
      }'
    ```
  




**Every container in project automatically mounts this share** (by default).


**Why automatic mounting is safe:** Containers are uniquely identified by their IDs, which are guaranteed unique across all users and servers. When a project-wide share is created, each container automatically gets a unique mount point at `/hoody/shares/{alias}/`. There's zero risk of conflicts or ambiguity.

**You don't need to accept** - just ignore the mount if you don't need it, or explicitly unmount:
```bash
PATCH /api/v1/containers/{id}/storage/incoming/{share_id}/mount
{"mount": false}
```

**Future feature:** Blacklist specific shares by default (opt-in instead of opt-out). For now, shares auto-mount and you can ignore/unmount as needed.


**Use when:**
- Shared configuration across all services
- Common assets or libraries
- Team-wide resources

---

## Access Modes


  
    ```json
    {
      "mode": "readonly"
    }
    ```
    
    **Target containers can:**
    - ✅ Read files
    - ✅ List directories
    - ✅ Check metadata
    - ❌ **Cannot** create files
    - ❌ **Cannot** modify files
    - ❌ **Cannot** delete files
    
    **Perfect for:**
    - Static assets (images, CSS, JS)
    - Configuration files
    - Reference data
    - Logs (share read-only for monitoring)
  
  
  
    ```json
    {
      "mode": "readwrite"
    }
    ```
    
    **Target containers can:**
    - ✅ Read files
    - ✅ Create new files
    - ✅ Modify existing files
    - ✅ Delete files
    - ✅ Create/delete directories
    
    **Changes visible to:**
    - Source container (immediately)
    - All other containers with share mounted
    
    **Perfect for:**
    - Collaborative workspaces
    - Shared databases (use with `/hoody/databases/`)
    - Upload directories
    - Multi-writer data pipelines
  


---

## Complete Share Workflow

### Creating a Share


  
    ```bash
    # 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/
    ```
  
  
  
    

    Response includes `share_id`.
  
  
  
    

    

    Share now accessible in `/hoody/shares/{share_alias}/`.
  
  
  
    ```bash
    # In target container
    ls /hoody/shares/shared-assets/
    # logo.png  styles.css
    
    # Files from source container, accessible in target
    cat /hoody/shares/shared-assets/styles.css
    ```
  


---

## Mount Points

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

```bash
/hoody/shares/{share-alias}/
```

**Example:**

```bash
# Source creates share with alias "config"
POST /storage/shares
{
  "source_path": "/hoody/storage/app-config",
  "alias": "config"
}

# Target mounts share
PATCH /storage/incoming/{share_id}/mount
{"mount": true}

# Files now accessible at:
/hoody/shares/config/
├── app.yaml
├── database.json
└── secrets.env
```

**The alias becomes the mount point name.**

---

## Common Use Cases

### Multi-Service Application

**Backend shares upload directory with multiple frontends:**

```bash
# 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
```

### Shared Configuration

**Config container shares settings with all services (readonly):**

```bash
# 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)
```

### Collaborative Development

**Developers share workspace between containers:**

```bash
# 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 edit files in real-time
# Changes sync instantly
```

### Log Aggregation

**Services share logs with monitoring container (readonly):**

```bash
# 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/
```

---

## Share Lifecycle

### Share Statuses

**Shares progress through these statuses:**

| Status | Description | Next Steps |
|--------|-------------|-----------|
| `active` | Successfully mounted in target | In use |
| `failed` | Mount failed (see status_message) | Check errors, fix, retry |

**Check status:**

```bash
GET /api/v1/containers/{id}/storage/shares/{share_id}

# Response includes: "status": "active"
```

### Enabling/Disabling Shares

**Source container can disable without deleting:**

```bash
# 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, gradual rollouts.

### Expiring Shares

**Set automatic expiration:**

```bash
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
```

**Perfect for:** Temporary access, demo environments, time-limited shares.

---

## Managing Shares

### List All Your Created Shares


  
    ```bash
    # Shares you created from specific container
    hoody storage list --container $SOURCE_ID

    # All shares you created (across all containers)
    hoody storage list-all
    ```
  
  
    ```typescript
    // Shares from specific container
    const shares = await client.api.storageShares.list(SOURCE_CONTAINER_ID);
    console.log(shares.data); // Array of shares you created

    // All shares across all containers
    const allShares = await client.api.storageShares.listGlobal();
    ```
  
  
    ```bash
    # Shares you created from specific container
    curl "https://api.hoody.icu/api/v1/containers/$SOURCE_ID/storage/shares" \
      -H "Authorization: Bearer $TOKEN"

    # All shares you created (across all containers)
    curl "https://api.hoody.icu/api/v1/storage/shares" \
      -H "Authorization: Bearer $TOKEN"
    ```
  


### List Incoming Shares


  
    ```bash
    # Incoming shares for specific container
    hoody storage get-incoming-shares $TARGET_ID

    # All incoming shares (all your containers)
    hoody storage list-all-incoming
    ```
  
  
    ```typescript
    // Incoming shares for specific container
    const incoming = await client.api.storageShares.listIncoming(TARGET_CONTAINER_ID);
    console.log(incoming.data); // Shares offered to this container

    // All incoming shares across all containers
    const allIncoming = await client.api.storageShares.listIncomingGlobal();
    ```
  
  
    ```bash
    # Incoming shares for specific container
    curl "https://api.hoody.icu/api/v1/containers/$TARGET_ID/storage/incoming" \
      -H "Authorization: Bearer $TOKEN"

    # All incoming shares (all your containers)
    curl "https://api.hoody.icu/api/v1/storage/incoming" \
      -H "Authorization: Bearer $TOKEN"
    ```
  


### Update Share Configuration


  
    ```bash
    # Upgrade share from readonly to readwrite
    hoody storage update $SOURCE_ID $SHARE_ID \
      --mode readwrite --description "Now allows writes"
    ```
  
  
    ```typescript
    await client.api.storageShares.update(
      SOURCE_CONTAINER_ID,
      SHARE_ID,
      { mode: 'readwrite', description: 'Now allows writes' }
    );
    ```
  
  
    ```bash
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$SOURCE_ID/storage/shares/$SHARE_ID" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"mode": "readwrite", "description": "Now allows writes"}'
    ```
  


**Target containers must remount** to get updated mode.

### Delete Share


  
    ```bash
    # Delete share — unmounts from all target containers
    hoody storage delete $SHARE_ID
    ```
  
  
    ```typescript
    await client.api.storageShares.delete(SHARE_ID);
    // Unmounts from all target containers, configuration deleted permanently
    ```
  
  
    ```bash
    curl -X DELETE "https://api.hoody.icu/api/v1/storage/shares/$SHARE_ID" \
      -H "Authorization: Bearer $TOKEN"
    ```
  


---

## Cross-Server Sharing (Automatic)

**Storage shares automatically 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 automatically:**
- ✅ **Cross-server mounting** - Works whether containers on same or different servers
- ✅ **Full POSIX compliance** - All filesystem operations work normally
- ✅ **File locks respected** - Concurrent access properly coordinated
- ✅ **Zero configuration** - No setup needed for cross-server shares
- ✅ **Transparent operation** - Same API whether same-server or cross-server


**SQLite Databases:** While file locks ARE respected, we **don't recommend** storing SQLite databases in shared storage for cross-container access. Use [`/hoody/databases/`](/foundation/storage/sqlite-drive/) instead.

**Why:** SQLite's file locking is designed for local filesystems. Network-shared SQLite can experience:
- Lock timeouts under high load
- Potential corruption on network interruptions
- Performance degradation compared to local access

**Better solution:** Store SQLite in `/hoody/databases/` (concurrent-write-safe FUSE mount) or use [hoody-sqlite](/kit/sqlite/) HTTP API for cross-container database access.


---

## Useful Questions

### What happens when source container is stopped?

**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:** Don't rely on shares from containers that stop frequently.

### Can I share the same directory to multiple containers?

Yes! Create multiple shares from same source_path:

```bash
# 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?

**Not recommended for SQLite databases.** While you CAN share `/hoody/databases/` directories, it bypasses the concurrent-write safety:

```bash
# ❌ 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 not designed for network filesystems

**Better solutions:**
- **Same server:** Each container accesses `/hoody/databases/` directly (concurrent-write-safe)
- **Cross-server:** Use [hoody-sqlite HTTP API](/kit/sqlite/) to access databases remotely
- **Data sharing:** Share application data directories, not database files

### What if target rejects the share?

Share remains available but unmounted. Target can accept later:

```bash
# Initially reject
PATCH /storage/incoming/{share_id}/mount
{"mount": false}

# Accept later when needed
PATCH /storage/incoming/{share_id}/mount
{"mount": true}

# Files appear in /hoody/shares/{alias}/
```

### Do shares persist through container restarts?

Yes. Share configuration and mount state survive:
- ✅ Source container restart - share remains
- ✅ Target container restart - mount point restored
- ✅ Both restart - everything reconnects

### Can I change the alias after creating a share?

Yes:

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

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

### Do storage shares count toward quota?

**Source container:** Files count toward source's storage.

**Target containers:** Mounted shares do NOT count toward target's storage quota (they're references, not copies).

---

## Troubleshooting

### Share Status Shows "failed"

**Problem:** Share status is `"failed"` with error message

**Common causes:**

1. **Source path doesn't exist:**
   ```bash
   # 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 /storage/shares/{id}
   {"enabled": true}
   ```

2. **Permission issues:**
   ```bash
   # 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

### Cannot See Mounted Share Files

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

**Solutions:**

1. **Verify share is active:**
   ```bash
   GET /api/v1/containers/{target_id}/storage/incoming
   # Check: "status": "active", "mount": true
   ```

2. **Check source container is running:**
   ```bash
   GET /api/v1/containers/{source_id}
   # Verify: "status": "running"
   ```

3. **Verify files exist in source:**
   ```bash
   # In source container
   ls /hoody/storage/shared-path
   # Should show files
   ```

4. **Unmount and remount:**
   ```bash
   PATCH /storage/incoming/{share_id}/mount
   {"mount": false}
   
   PATCH /storage/incoming/{share_id}/mount
   {"mount": true}
   ```

### "Stale file handle" Errors

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

**Cause:** Source container restarted while target was accessing files

**Solution:**

```bash
# Unmount and remount
PATCH /storage/incoming/{share_id}/mount
{"mount": false}

PATCH /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 Won't Mount

**Problem:** `PATCH /mount` succeeds but files don't appear

**Check:**

1. **Share is enabled:**
   ```bash
   GET /storage/incoming/{share_id}
   # Verify: "enabled": true
   ```

2. **Share not expired:**
   ```bash
   # 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)

---

## Best Practices

### 1. Share /hoody/storage Subdirectories, Not Root

```bash
# ✅ 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
```

### 2. Use Project-Wide for Common Resources

**One share for all containers:**

```bash
# 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
```

### 3. Monitor Readwrite Shares

**Multiple writers can conflict:**

```bash
# 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 (e.g., different directories per container).

**Or use `/hoody/databases/` for SQLite databases** (automatic concurrent-write safety).

### 4. Snapshot Before Deleting Shares

**Share deletion unmounts from all targets:**

```bash
# 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}
```

### 5. Use Descriptive Descriptions

```bash
# ✅ 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"
}
```

**Future you will thank present you** when managing dozens of shares.

---

## What's Next

**Storage ecosystem:**
- **[Container Storage →](./)** - Understanding container filesystem
- **[Mount Locally →](./mount-locally/)** - SFTP/WebDAV for local file managers
- **[SQLite Driver →](./sqlite-drive/)** - Concurrent-write databases
- **[Cloud Storage →](./cloud/)** - Connect 63 cloud providers
- **[/ramdisk →](./ramdisk/)** - Ultra-fast RAM storage

**Related features:**
- **[Container Copy →](/foundation/containers/copy-sync/)** - Duplicate entire containers
- **[Snapshots →](/foundation/containers/snapshots/)** - Backup container state
- **[Hoody Files →](/api/files/)** - HTTP filesystem access

**Understanding gained:**
- ✅ Source container controls WHAT is shared
- ✅ Target container controls IF they mount it
- ✅ Two modes: readonly, readwrite
- ✅ Two types: 1-to-1, project-wide
- ✅ Works cross-server automatically (SSHFS-backed storage relay)
- ✅ Combine with `/hoody/databases/` for shared SQLite databases

---

> **Share directories between containers.**  
> **Readonly for safety. Readwrite for collaboration.**

**One share, multiple consumers. Data exchange without duplication.**