Problem Summary
In a Docker-in-Docker (DinD) setup where the worker container runs inside Docker and creates containers:- Volume Path Mismatch: Volume mount paths are relative to the Docker daemon’s filesystem, not the worker container
- Security Risk: Shared volumes in multi-tenant SaaS allow cross-tenant data access
- Limited stdin Approach: Using stdin for data transfer doesn’t support file-based tools
Solution: Isolated Named Volumes
Use unique Docker named volumes created pertenantId + runId + timestamp:
Architecture
Security Benefits
| Aspect | Old Approach | Isolated Volumes |
|---|---|---|
| Tenant Isolation | ❌ Shared volume or stdin | ✅ Unique volume per tenant+run |
| Path Traversal | ⚠️ Possible with file mounts | ✅ Validated filenames |
| Data Leakage | ❌ Files persist in shared space | ✅ Immediate cleanup |
| Audit Trail | ❌ None | ✅ Volume labels track tenant/run |
| DinD Compatible | ❌ File mounts don’t work | ✅ Named volumes work perfectly |
Implementation
Before: File Mounting (Broken in DinD)
After: Isolated Volumes (DinD Compatible)
Comparison: All Approaches
| Feature | File Mounts | stdin Approach | Isolated Volumes |
|---|---|---|---|
| DinD Compatible | ❌ No | ✅ Yes | ✅ Yes |
| File-based tools | ✅ Yes | ❌ No | ✅ Yes |
| Config files | ✅ Yes | ❌ No | ✅ Yes |
| Output files | ❌ Hard to read | ❌ No | ✅ Yes |
| Binary files | ✅ Yes | ❌ No | ✅ Yes |
| Large files | ✅ Yes | ⚠️ Memory limits | ✅ Yes |
| Tenant isolation | ❌ No | ⚠️ Process-level | ✅ Volume-level |
Usage Examples
Simple Input Files
Input + Output Files
Multiple Volumes
Volume Lifecycle
- Create:
docker volume create tenant-A-run-123-... - Populate: Use temporary Alpine container to write files
- Mount: Container uses the volume via
-v volumeName:/path - Read: Use temporary Alpine container to read files
- Cleanup:
docker volume rm tenant-A-run-123-...
Automatic Cleanup
Volumes are always cleaned up viafinally blocks:
Orphan Cleanup
For volumes that weren’t cleaned up (e.g., worker crash):Security Requirements
Tenant Isolation
Every execution gets a unique volume:tenant-acme-run-wf-abc123-1732150000
Read-Only Mounts
Nonroot Container Support
Volumes automatically support containers running as nonroot users (e.g., distroless images with uid 65532). After files are written to the volume, permissions are set to777 to allow any container user to read/write:
- Files are written to volumes using Alpine containers (running as root)
- Distroless nonroot images run as uid 65532
- Without permission changes, nonroot containers can’t write output files
- Each volume is isolated per tenant + run
- Volumes are cleaned up after execution
- No cross-tenant access is possible
Path Validation
Filenames are automatically validated:Security Guarantees
| Security Feature | How It Works |
|---|---|
| Tenant Isolation | Volume name includes tenant ID |
| No Collisions | Timestamp prevents conflicts |
| Path Safety | Filenames validated (no .. or /) |
| Automatic Cleanup | Finally blocks guarantee removal |
| Audit Trail | Volumes labeled studio.managed=true |
| DinD Compatible | Named volumes work in nested Docker |
Performance
Volume Creation Overhead
- Creation: ~50-100ms per volume
- File writes: ~10-50ms per file (depends on size)
- Cleanup: ~50-100ms per volume
Optimization Tips
- Batch file writes: Write all files in one
initialize()call - Reuse volumes: For sequential operations in same run, reuse the volume
- Lazy cleanup: Clean up volumes in background job if latency-sensitive
When to Use Each Approach
Use Isolated Volumes When:
- ✅ Running in DinD environment
- ✅ Need multi-tenant isolation
- ✅ Tool requires file-based config
- ✅ Tool writes output files
- ✅ Handling binary/large files
Use stdin/stdout When:
- ✅ Tool supports stdin input
- ✅ Single-tenant or dev environment
- ✅ Small text-only inputs
- ✅ Don’t need output files
Use File Mounts When:
- ✅ NOT running in DinD (direct Docker)
- ✅ Development/testing only
- ✅ Quick prototyping
Migration Checklist
To migrate a component to use isolated volumes:- Import
IsolatedContainerVolume - Get
tenantIdfrom context (use fallback for now) - Create volume instance:
new IsolatedContainerVolume(tenantId, runId) - Replace file writes with
volume.initialize({ files }) - Replace volume mount with
volume.getVolumeConfig() - Add
finallyblock withvolume.cleanup() - If tool writes outputs, use
volume.readFiles()to retrieve them - Test in DinD environment