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