Skip to content

Commit 71cd656

Browse files
committed
feat(multi-instance-manager): add isolated docker profile launcher
Add a reusable multi-instance Docker manager that launches one container per saved auth profile with isolated runtime directories and documented operational guidance.\n\nCloses #300
1 parent 9237404 commit 71cd656

5 files changed

Lines changed: 672 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,5 @@ static/frontend/dist/
292292

293293
# GUI Launcher user config (contains user-specific settings)
294294
gui/user_config.json
295+
296+
.multi-instance-runtime/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ cp .env.example .env
155155
- [认证轮转与 Cookie 刷新](docs/auth-rotation-cookie-refresh.md)
156156
- [排障指南](docs/troubleshooting.md)
157157
- [开发、测试与发布](docs/development-and-release.md)
158+
- [多实例 Docker 管理器](scripts/multi-instance-manager/README.md)
158159

159160
---
160161

docs/deployment-and-operations.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ bash update.sh
128128
docker compose exec ai-studio-proxy /bin/bash
129129
```
130130

131+
## 3.5 多实例扩展
132+
133+
如需为多个已保存认证同时启动隔离容器,可使用外部管理脚本 [`scripts/multi-instance-manager/README.md`](../scripts/multi-instance-manager/README.md)
134+
135+
当前 multi-instance 管理器会为每个容器显式设置独立的 `ACTIVE_AUTH_JSON_PATH`,并把选中的 profile 复制到 [` .multi-instance-runtime/active/`](../.multi-instance-runtime/) 后再挂载为容器内的 [`auth_profiles/active/`](../auth_profiles/active/) 单文件。默认同时关闭启动时自动轮转与运行时自动轮转,这样既能维持一容器一认证隔离,也能满足 Docker headless 启动阶段对 active-dir 的运行时要求。
136+
137+
镜像名方面,管理器默认仍优先查找 `ai-studio-proxy:latest`。但如果你是通过 `cd docker && docker compose build` 构建,Compose 常见产物会是 `docker-ai-studio-proxy:latest`;在未显式覆盖镜像名时,脚本会自动回退到该 Compose 镜像并打印提示。若你通过 `--image``IMAGE_NAME` 指定了其他镜像名,则脚本会严格使用该值,不会静默改写。
138+
139+
如需让单个容器重新参与跨 profile 轮转,可在脚本中使用 `--enable-auth-rotation`,但这会放宽默认的一容器一认证隔离约束。
140+
141+
该方案面向高级用户,不修改主应用、Dockerfile 或默认 Docker 启动流程。
142+
131143
---
132144

133145
## 4. 生产配置建议(重点)
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Multi-instance manager
2+
3+
External helper for advanced users who want to run multiple isolated Docker containers from a pool of saved auth profiles.
4+
5+
## What it does
6+
7+
The manager launches one container per auth profile from [`auth_profiles/saved/`](../../auth_profiles/saved/). Each container gets:
8+
9+
- its own host API port
10+
- its own host stream port
11+
- its own dedicated startup profile via [`ACTIVE_AUTH_JSON_PATH`](../../launcher/runner.py)
12+
- the same Docker image and shared API key unless overridden
13+
14+
By default, the manager enforces strict profile isolation at startup. A container starts with its matching file from [`auth_profiles/saved/`](../../auth_profiles/saved/) and does not auto-rotate into other profiles unless you explicitly enable that behavior.
15+
16+
This component is intentionally isolated from the main application. It does not modify the core server code, the existing Docker setup, or the standard launch flow.
17+
18+
## Resource warning
19+
20+
> ⚠️ Each instance can consume around 1 GB RAM or more.
21+
> Running many containers at once may cause memory pressure or OOM kills.
22+
> Review available system memory before launching a large profile pool.
23+
24+
The script shows a visible warning and asks for confirmation before startup unless auto-confirm is enabled.
25+
26+
## Requirements
27+
28+
- Docker installed and running
29+
- Bash 3.2+ on macOS or a newer Bash on Linux
30+
- built image available locally
31+
- default expected name: `ai-studio-proxy:latest`
32+
- if you built via `cd docker && docker compose build`, Docker Compose may create `docker-ai-studio-proxy:latest`; the launcher now detects this and falls back automatically when the default image is still configured
33+
- if you use another tag, pass it explicitly with `--image ...` or set `IMAGE_NAME=...`
34+
- prepared auth files in [`auth_profiles/saved/`](../../auth_profiles/saved/)
35+
- optional [`docker/.env`](../../docker/.env) file if you want containers to inherit the usual Docker configuration
36+
37+
## Files
38+
39+
- [`run-multi-instance.sh`](run-multi-instance.sh) — launcher script
40+
- [`README.md`](README.md) — usage guide
41+
42+
## Startup contract
43+
44+
The launcher now follows a strict startup contract:
45+
46+
1. It mounts the shared [`auth_profiles/`](../../auth_profiles/) tree into the container.
47+
2. It sets `ACTIVE_AUTH_JSON_PATH=/app/auth_profiles/saved/<profile>.json` for that specific container.
48+
3. It copies the selected host profile into [` .multi-instance-runtime/active/`](../../.multi-instance-runtime/) and bind-mounts that copy as `/app/auth_profiles/active/<profile>.json` inside the container.
49+
4. It disables `AUTO_AUTH_ROTATION_ON_STARTUP` and `AUTO_ROTATE_AUTH_PROFILE` by default.
50+
51+
This dual-path startup is intentional. Although [`launcher/runner.py::_resolve_auth_file_path()`](../../launcher/runner.py:270) and [`browser_utils/initialization/core.py::initialize_page_logic()`](../../browser_utils/initialization/core.py:48) can consume [`ACTIVE_AUTH_JSON_PATH`](../../launcher/runner.py:413), real Docker headless startup may still traverse logic that expects at least one file under [`auth_profiles/active/`](../../auth_profiles/active/). The runtime copy avoids polluting the shared host [`auth_profiles/active/`](../../auth_profiles/active/) pool while still satisfying that container-local requirement.
52+
53+
If you really want cross-profile rotation inside each container, use [`--enable-auth-rotation`](run-multi-instance.sh).
54+
55+
## Quick start
56+
57+
Build the image first if needed:
58+
59+
```bash
60+
cd docker
61+
docker compose build
62+
```
63+
64+
After a regular Compose build, the local image is often named `docker-ai-studio-proxy:latest`. The manager still starts from its default `ai-studio-proxy:latest`, but now automatically falls back to `docker-ai-studio-proxy:latest` when that Compose image exists and no custom image override was requested.
65+
66+
Then run the manager from the repository root:
67+
68+
```bash
69+
bash scripts/multi-instance-manager/run-multi-instance.sh
70+
```
71+
72+
## Common examples
73+
74+
Use defaults:
75+
76+
```bash
77+
bash scripts/multi-instance-manager/run-multi-instance.sh
78+
```
79+
80+
Use another image and custom container prefix:
81+
82+
```bash
83+
bash scripts/multi-instance-manager/run-multi-instance.sh \
84+
--image docker-ai-studio-proxy:latest \
85+
--container-prefix team-a
86+
```
87+
88+
Use an explicit image override via environment variable:
89+
90+
```bash
91+
IMAGE_NAME=docker-ai-studio-proxy:latest \
92+
bash scripts/multi-instance-manager/run-multi-instance.sh
93+
```
94+
95+
Shift ports upward to avoid collisions:
96+
97+
```bash
98+
bash scripts/multi-instance-manager/run-multi-instance.sh \
99+
--base-api-port 3048 \
100+
--base-stream-port 4120
101+
```
102+
103+
Skip confirmation for automation:
104+
105+
```bash
106+
bash scripts/multi-instance-manager/run-multi-instance.sh --auto-confirm
107+
```
108+
109+
Preview actions without launching containers:
110+
111+
```bash
112+
bash scripts/multi-instance-manager/run-multi-instance.sh --dry-run
113+
```
114+
115+
Allow auth rotation inside each launched container:
116+
117+
```bash
118+
bash scripts/multi-instance-manager/run-multi-instance.sh --enable-auth-rotation
119+
```
120+
121+
## Options
122+
123+
| Option | Description | Default |
124+
| --- | --- | --- |
125+
| `--project-root PATH` | Repository root path | auto-detected |
126+
| `--profile-dir PATH` | Directory with saved auth profiles | `auth_profiles/saved` |
127+
| `--docker-env-file PATH` | Path to mounted Docker env file | `docker/.env` |
128+
| `--image NAME` | Docker image name | `ai-studio-proxy:latest` with automatic fallback to `docker-ai-studio-proxy:latest` after a standard `docker compose build` |
129+
| `--container-prefix PREFIX` | Prefix for launched containers | `ai-proxy` |
130+
| `--api-key KEY` | Shared API key for all containers | `sk-dummy` |
131+
| `--log-level LEVEL` | `SERVER_LOG_LEVEL` value | `INFO` |
132+
| `--base-api-port PORT` | Starting host API port | `2048` |
133+
| `--base-stream-port PORT` | Starting host stream port | `3120` |
134+
| `--memory-per-instance-gb N` | Estimated RAM per container | `1` |
135+
| `--auto-confirm` | Skip confirmation prompt | disabled |
136+
| `--dry-run` | Show commands without running Docker | disabled |
137+
| `--no-docker-env` | Do not mount [`docker/.env`](../../docker/.env) | disabled |
138+
| `--enable-auth-rotation` | Re-enable auth rotation inside launched containers | disabled |
139+
| `--help` | Show help | disabled |
140+
141+
## Environment variables
142+
143+
All major parameters can also be set with environment variables before launch:
144+
145+
```bash
146+
export IMAGE_NAME=docker-ai-studio-proxy:latest
147+
export BASE_API_PORT=3048
148+
export BASE_STREAM_PORT=4120
149+
export AUTO_CONFIRM=true
150+
bash scripts/multi-instance-manager/run-multi-instance.sh
151+
```
152+
153+
When `IMAGE_NAME` or `--image` is set explicitly, the launcher treats that as an intentional override and does not silently replace it with the Compose image name.
154+
155+
Supported variables:
156+
157+
- `PROJECT_ROOT`
158+
- `PROFILE_DIR`
159+
- `DOCKER_ENV_FILE`
160+
- `IMAGE_NAME`
161+
- `CONTAINER_PREFIX`
162+
- `API_KEY`
163+
- `SERVER_LOG_LEVEL`
164+
- `BASE_API_PORT`
165+
- `BASE_STREAM_PORT`
166+
- `MEMORY_PER_INSTANCE_GB`
167+
- `AUTO_CONFIRM`
168+
- `DRY_RUN`
169+
- `MOUNT_DOCKER_ENV`
170+
- `DISABLE_AUTH_ROTATION`
171+
- `EXTRA_DOCKER_ARGS`
172+
173+
## How the manager maps profiles
174+
175+
If the saved pool contains:
176+
177+
- `auth_profiles/saved/user-a.json`
178+
- `auth_profiles/saved/user-b.json`
179+
180+
The script will launch containers similar to:
181+
182+
- `ai-proxy-user-a` on API `2048` and stream `3120`
183+
- `ai-proxy-user-b` on API `2049` and stream `3121`
184+
185+
Inside each container, startup resolves through two aligned paths:
186+
187+
- `/app/auth_profiles/saved/user-a.json` and `/app/auth_profiles/active/user-a.json`
188+
- `/app/auth_profiles/saved/user-b.json` and `/app/auth_profiles/active/user-b.json`
189+
190+
The saved-path mapping is still passed through `ACTIVE_AUTH_JSON_PATH`, and the same host file is also mounted into the container-local [`auth_profiles/active/`](../../auth_profiles/active/) directory. This keeps one-container-one-profile isolation while avoiding the Docker headless startup failure that reports no active profile.
191+
192+
## Port collision behavior
193+
194+
The launcher checks host ports twice:
195+
196+
1. before removing a same-named container
197+
2. again after removal and before starting the replacement container
198+
199+
This prevents a false success path where an old container was removed even though the target port actually belonged to another listener.
200+
201+
If a host port is occupied by another process or container, the script stops before launching replacements.
202+
203+
## Safety checks
204+
205+
Before launch the script validates:
206+
207+
- Docker CLI availability
208+
- Docker daemon availability
209+
- Docker image existence
210+
- profile directory existence
211+
- presence of `*.json` profiles
212+
- host port collisions for all planned instances
213+
- optional [`docker/.env`](../../docker/.env) existence when mounting is enabled
214+
215+
## Stop and inspect containers
216+
217+
List launched containers:
218+
219+
```bash
220+
docker ps --filter "name=^ai-proxy-"
221+
```
222+
223+
Stop one container:
224+
225+
```bash
226+
docker rm -f ai-proxy-user-a
227+
```
228+
229+
Stop all containers for the default prefix:
230+
231+
```bash
232+
containers=$(docker ps -aq --filter "name=^ai-proxy-")
233+
[ -n "$containers" ] && docker rm -f $containers
234+
```
235+
236+
Check logs for one instance:
237+
238+
```bash
239+
docker logs -f ai-proxy-user-a
240+
```
241+
242+
## Notes
243+
244+
- The manager is intended for advanced operators.
245+
- It is an external wrapper, not a replacement for the standard single-instance flow.
246+
- For the normal Docker path, continue using [`docker/README.md`](../../docker/README.md) and [`docs/deployment-and-operations.md`](../../docs/deployment-and-operations.md).

0 commit comments

Comments
 (0)