Skip to content

Commit 77234d8

Browse files
Isolate using per-session Docker containers for shell tool
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d3f225d commit 77234d8

16 files changed

Lines changed: 755 additions & 860 deletions

README.md

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
A sample app demonstrating one possible way to build a multi-user, server-hosted agent chat experience. This is built using [GitHub Copilot SDK](https://github.com/github/copilot-sdk), so it can complete challenging real-world tasks using the same proven harness that powers Copilot CLI.
44

5-
In this sample, each user gets an isolated session with its own virtual filesystem and virtual bash runtime, all managed server-side.
5+
In this sample, each user gets an isolated session with its own workspace directory and a dedicated Linux container for running shell commands, all managed server-side.
66

77
![Screenshot](docs/screenshot.png)
88

9-
The agent can manage its own files, write and execute Python code, and use `curl` to access only pre-approved web resources.
9+
The agent can manage its own files and write and execute Python code and `curl` commands inside its session's container.
1010

11-
⚠️ This is a sample to demonstrate a possible app architecture. It's not an app you could deploy as-is, since it lacks important security features such as auth, and the filesystem isolation is limited. See [limitations](#limitations) for more details.
11+
⚠️ This is a sample to demonstrate a possible app architecture. It's not an app you could deploy as-is, since it lacks important security features such as auth, and the filesystem/network isolation is limited. See [limitations](#limitations) for more details.
1212

1313
## Running the sample
1414

15-
You need a GitHub token (a fine-grained PAT with no special permissions, or the output of `gh auth token`). This token is used by Copilot SDK to perform AI inferencing using a model approved for your account.
15+
You need a GitHub token (a fine-grained PAT with no special permissions, or the output of `gh auth token`), and [Docker](https://www.docker.com/) (the app server itself uses Docker to start one small container per session). The GitHub token is used by Copilot SDK to perform AI inferencing using a model approved for your account.
1616

1717
```bash
1818
# On Bash (macOS/Linux)
@@ -27,13 +27,13 @@ docker compose up
2727

2828
When it's running, open [http://localhost:3001](http://localhost:3001).
2929

30-
### In-memory mode
30+
### Running shell commands in per-session containers
3131

32-
By default, each session's virtual filesystem (VFS) is backed by disk. You can see and edit all the virtual filesystems inside `app/sessions`.
33-
34-
To use a purely in-memory virtual filesystem instead, set an environment variable `USE_IN_MEMORY_VFS` to `true` and re-run `docker compose up`.
35-
36-
In realistic server deployments it would be better to use disk rather than in-memory storage, because disk space is more cheaply available. But if you're building a client-side application that creates large numbers of short-lived agent sessions, it might work well to use an in-memory VFS.
32+
Each session gets its own small, disposable Docker container (built from `app/session-image/Dockerfile`,
33+
a stock Alpine image with just `bash`, `python3`, and `curl` added) that boots in about a second. The
34+
session's on-disk workspace directory (`app/sessions/<sessionId>`) is bind-mounted read-write into that
35+
container at `/workspace`, so the `bash` tool and the agent's file tools both see the exact same files.
36+
The container has no other access to the host.
3737

3838
## Architecture
3939

@@ -52,9 +52,9 @@ When a user connects, the app server creates an isolated session with:
5252

5353
- A **Copilot SDK session** (`CopilotSession`) that maintains conversation state, tool handlers, and event streaming.
5454
- We limit it to using tools that are intended to be safe in multi-user environments because they don't read/write files.
55-
- The session's only tool that can operate on disk is `bash`, but this is swapped out for a virtual version (see below).
56-
- A **virtual filesystem** (in-memory or disk-backed) scoped to that session. The Copilot agent reads and writes files within this filesystem only. It cannot see the server's disk or the state of other sessions.
57-
- A **virtual bash runtime** ([just-bash](https://github.com/aspect-build/just-bash)) attached to the virtual filesystem, exposed to the agent as a tool. Network access is restricted to a small allowlist.
55+
- The session's only tool that can operate on disk is `bash`, but this is swapped out for one that runs inside the session's own container (see below).
56+
- A **workspace directory** on the app server's disk, scoped to that session. The Copilot agent's file tools read and write within this directory only (see `containerFs.ts`). It cannot see the server's other files or the state of other sessions.
57+
- A **per-session Docker container** (see `docker/sessionContainer.ts`) with that same workspace directory bind-mounted read-write at `/workspace`, exposed to the agent as the `bash` tool. Unlike the previous just-bash-based version of this sample, this container's network access is **not** restricted to an allowlist - see [limitations](#limitations).
5858

5959
Multiple browser tabs can observe the same session simultaneously — the server maintains a single `CopilotSession` per session ID and fans out events to all connected WebSockets. To see this, copy and paste your session URL into a second browser window or tab.
6060

@@ -68,15 +68,17 @@ The architecture supports running multiple app server instances behind a load ba
6868

6969
The `rsync-filestore` container is a minimal Alpine image running an rsync daemon. It serves as the persistent, shared store that survives app server restarts and enables session mobility across servers.
7070

71-
**This is just one possible example of how to balance performance and resilience to server recycling.** Other strategies are also possible, because the storage virtualization APIs in Copilot SDK allow you to store things anywhere you like. For example you could directly stream session events to an event store rather than letting them be written to disk in the first place. But for this example, simply synchronizing and restoring the session's entire VFS is a simple and comprehensive solution.
71+
**This is just one possible example of how to balance performance and resilience to server recycling.** Other strategies are also possible, because the storage virtualization APIs in Copilot SDK allow you to store things anywhere you like. For example you could directly stream session events to an event store rather than letting them be written to disk in the first place. But for this example, simply synchronizing and restoring the session's entire workspace directory is a simple and comprehensive solution.
7272

7373
## Code structure
7474

7575
* `app`: the application server. You could run many instances and load balance over them.
7676
* `api`: server-side code that defines and manages agent sessions
77-
* `chatSocket.ts`: starts a WebSocket listener. As clients connect/disconnect, starts and stops `CopilotSession` instances and synchronizes storage to `rsync-filestore`
78-
* `storage/`: simple VFS implementations
79-
* `bash.ts`: swaps out Copilot SDK's built-in shell tool with one backed by [just-bash](https://github.com/vercel-labs/just-bash). This is simply an example - you could instead map it into a per-session container, any other isolated shell. Various other open source projects provide isolated shells.
77+
* `chatSocket.ts`: starts a WebSocket listener. As clients connect/disconnect, starts and stops `CopilotSession` instances (and their containers) and synchronizes storage to `rsync-filestore`
78+
* `storage/`: manages each session's on-disk workspace directory
79+
* `containerFs.ts`: a `SessionFsProvider` that gives the agent's file tools access to a session's workspace directory, confined to it
80+
* `docker/`: builds the session container image once per app process and starts/stops/execs into one container per session
81+
* `bash.ts`: swaps out Copilot SDK's built-in shell tool with one that runs commands inside the session's container
8082
* `web-ui`: an Express+React application providing the user interface
8183
* `hooks/useChat.ts`: opens the websocket connection to `api/chat` and uses a reducer pattern to convert the event stream into a UI
8284
* everything else: generic chat UI (a lot of code but nothing interesting)
@@ -89,6 +91,8 @@ The `rsync-filestore` container is a minimal Alpine image running an rsync daemo
8991
This example illustrates many useful ideas, but isn't something you can deploy as-is, because:
9092

9193
- **No authentication.** The sample has no user authentication or session authorization. Anyone with access to the server can create or resume sessions.
94+
- **Unrestricted container network access.** Session containers can reach any network host via `curl`, with no egress allowlist or firewall. A real deployment would want to restrict this (e.g. with a network policy, proxy, or `--network` configuration on the container).
95+
- **Session containers run with the app server's Docker access.** The app server needs access to a Docker daemon (via the Docker socket) to create session containers. Anyone who could get code execution in the app server could likely also control that Docker daemon.
9296
- **Not production-hardened.** Error handling, rate limiting, and resource quotas are minimal. This is a reference implementation to illustrate the architecture, not a production-ready service.
9397

9498
## License

app/Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
FROM node:22-slim
22

3-
RUN apt-get update && apt-get install -y --no-install-recommends rsync && rm -rf /var/lib/apt/lists/*
3+
RUN apt-get update && apt-get install -y --no-install-recommends rsync ca-certificates && rm -rf /var/lib/apt/lists/*
44

55
WORKDIR /app
66

7-
# Install dependencies (postinstall needs scripts/)
7+
# Install dependencies
88
COPY package.json package-lock.json* ./
9-
COPY scripts ./scripts
109
RUN npm install
1110

1211
# Source is bind-mounted in dev, but copy for production builds

0 commit comments

Comments
 (0)