When you need true isolation between users in a multi-tenant AI system, shared processes with user IDs are not enough. Here is a pattern that gives each user complete isolation using Docker containers.
The Problem
I was building an AI companion on Telegram (Adola) where each user has ongoing conversations with their own AI agent. A shared instance had three fatal problems:
- Context contamination between users
- One slow request blocks everyone
- No persistent filesystem per user
The Pattern
User Message -> Gateway -> Docker Container (per user) -> Response
Container Lifecycle
async function ensureContainer(userId: string) {
const name = `app-user-${userId.slice(0, 8)}`;
const container = docker.getContainer(name);
try {
const info = await container.inspect();
if (info.State.Running) return container;
await container.start();
} catch {
// Container does not exist, create it
await docker.createContainer({
name,
Image: CONTAINER_IMAGE,
HostConfig: {
Binds: [`${DATA_DIR}/${userId}/workspace:/workspace`],
NetworkMode: NETWORK,
},
});
}
return container;
}
Idle Cleanup
Stopped containers use zero CPU/memory. A cleanup loop stops containers after 30 minutes of inactivity:
setInterval(async () => {
for (const [userId, lastActivity] of activeUsers) {
if (Date.now() - lastActivity > IDLE_TIMEOUT) {
await docker.getContainer(`app-user-${userId.slice(0,8)}`).stop();
}
}
}, 300_000); // every 5 min
Cold Start Performance
Restarting a stopped container takes ~3 seconds. For a chat application, this is acceptable -- the user sees a typing indicator while the container boots.
Workspace Persistence
Each user gets a bind-mounted workspace directory:
/data/users/{userId}/workspace/
MEMORY.md # Agent-maintained notes
SCHEDULES.json # Reminders
session.jsonl # Conversation history
Bind mounts (not volumes) let the gateway read user files directly without going through docker exec.
Resource Usage
| Users | Running Containers | Stopped | RAM Used |
|---|---|---|---|
| 7 | 1-3 (active) | 4-6 | ~1.2 GB |
| 50 | 5-10 (active) | 40-45 | ~3 GB |
| 100 | 10-20 (active) | 80-90 | ~5 GB |
Stopped containers cost only disk space for their writable layer (usually <100MB each).
When to Use This
- Users need persistent filesystem access
- Strong isolation between users matters
- Per-user resource limits are important
- Agent/AI workloads with stateful processes
When Not to Use This
- Thousands of concurrent users (use Kubernetes with pod-per-user instead)
- Stateless request-response APIs
- Latency-sensitive applications where 3s cold start is unacceptable
Try It
If you want to see this pattern in action: t.me/adola2048_bot is a Telegram AI companion built this way. Each user gets their own container with persistent memory. Free, no signup.
Top comments (0)