We run five Claude Code sessions at once Draper's engineering team runs five Claude Code sessions in parallel by combining git worktrees, Docker, Linear, tmux, and a custom CLI called swt, giving each agent an isolated environment with its own services, database, and ports. The workflow routes tickets from Slack into Linear, where agents automatically create branch-per-ticket workspaces with draft PRs, and the entire tooling was built by AI agents using prompts provided in the post. How we run five Claude Code sessions at once at Draper: git worktrees, Docker, Linear, tmux, and a small CLI we call swt. One Claude Code session is a great engineer. Five Claude Code sessions in the same checkout are five great engineers fighting over one desk, one database, and one set of ports. This post is the full recipe we use at Draper to run many agents in parallel without them ever touching each other: how tickets flow from Slack through Linear, how Docker plus git worktrees gives each agent a private universe, and how one command swt new eng-451 turns a ticket into a running, isolated, agent-ready workspace with a draft PR already open. Two notes before we start: This article covers our development tooling only, not how the deploy works. The deploy side per-PR preview environments, the release pipeline itself is its own story, and we're happy to write it up if people want it. Let us know. Everything in this post was built by asking an agent to build it. At every step I've included the prompt you can paste into your own agent to get the equivalent for your repo. You don't need to write any of this by hand. 1. The motivation: Claude sessions aren't isolated by default Git worktrees solve half the problem, and it's the half everyone talks about. Each worktree is a separate checkout on its own branch, so two agents never edit the same file. Code isolation: done. Then session two starts its dev server. Our monorepo runs a Nuxt chat app on 3002, a Fastify API on 3000, a node-graph editor on 3003, a Prefect server on 4200, a CMS dev server on 4001 and an OTLP collector on 4318. Claude Code sessions are port-hungry: every parallel session wants its own copy of all of that. The second session either dies with EADDRINUSE, or, much worse, it silently connects to the first session's server and cheerfully "verifies" its change against code it never wrote. That second failure mode is the expensive one. The agent isn't wrong, its world is. And ports are only the visible symptom. Vanilla parallel sessions also share: One database. Agent A runs a half-finished migration while agent B is mid-task. Agent B's world just changed shape underneath it. One .env. An agent tweaks a variable to test something, and every other session inherits it. One set of background state : queues, caches, uploaded files, seeded users. So the actual requirement is stronger than "separate branches." Every session needs its own environment : own services, own database, own hostnames, own blast radius. That's the recipe in one line: Claude Code + Docker + Linear Claude Code. Docker gives each session its environment. Linear gives each session its purpose. The rest of this post wires those together. 2. The Linear ticket lifecycle Work enters our system in exactly one place: Slack . The product team writes tickets in plain English in a product channel, and bug reports land in a reports channel; an agent files both into Linear as Triage issues. Nobody opens Linear to create a ticket. Two details of how those tickets are filed do a lot of quiet work later: Bug reports become children. A Linear parent issue represents one feature or fix that ships as one PR. User-reported symptoms of it get filed as sub-issues of that parent. When the PR eventually merges with a Fixes ENG-451, ENG-452, ENG-453 line, the whole family closes together. The reporter's @mention is preserved. The Slack agent stores the reporting user's Slack mention on the Linear issue when it files it. Months of goodwill come from this one field, as you'll see in section 5. From there the lifecycle is: Triage → Todo → In Progress → In Staging → Done. Here's the important part: humans only touch the first two states. Product triages and prioritises into Todo. After that: In Progress happens automatically the moment an engineer or their agent starts the work, because starting the work pushes a branch named after the ticket, and Linear's GitHub integration reacts to it. In Staging happens automatically when the PR merges into our staging branch. Done happens automatically for the whole staged batch when staging is released to main, and Slack gets notified both times. Nobody has ever dragged a card across our board. The board is a read model of git. 3. Docker + worktrees: code isolation AND environment isolation This is the foundation everything else stands on, and it's really important to get right: worktrees isolate the code, Docker isolates the world. Once both are true, Claude sessions cannot interfere with each other, and the isolated databases mean an agent can iterate violently run migrations, wipe tables, reseed without mangling anyone's settings or data. The construction: Everything runs in Docker Compose , including in development. One docker-compose.yml at the repo root, and every service is gated behind a Compose profile infra, cache, chat-dev, editor-dev, ... , so a stack can run any subset of the system. Each worktree gets its own Compose project. COMPOSE PROJECT NAME=sunrise for the main checkout, COMPOSE PROJECT NAME=sunrise-eng-451-fix-checkout-race for a feature worktree. Same YAML, different project name, completely disjoint containers, networks and volumes. OrbStack DNS instead of host ports. This is the trick that kills the port problem outright. We develop on macOS with OrbStack I strongly recommend it over Docker Desktop for this , and OrbStack gives every container a hostname: