Multi-User Workspace
Multi-user workspace mode gives every user an isolated system account and a dedicated qwen-code-webui process, so each person's projects, sessions, and files stay separated. It requires running the Open ACE container (or process) as root, because it creates system users (useradd), fixes ownership (chown), and switches identity (sudo -u <user>).
Windows does not support multi-user mode: the configuration is automatically downgraded to single-user mode (direct execution without user switching), because Windows has no equivalent of
sudo -u.
Table of Contents
- Three Ways to Start
- Deployment Modes: Docker vs Bare Metal
- Port Range Configuration
- sudoers Configuration
- User Account Requirements
- Migrating from Single-User Mode
- Troubleshooting
Three Ways to Start
Option 1: One-click startup script (recommended)
./scripts/start-multi-user.sh
The script automatically detects the Docker Compose version (v1/v2), validates that docker-compose.yml and docker-compose.multi-user.yml exist, starts the multi-user containers, and prints the access URL and status. Extra arguments pass through to compose (e.g. ./scripts/start-multi-user.sh --build).
Option 2: Docker Compose overlay
# One-shot: generate and validate .env for production
./scripts/bootstrap-compose-env.sh
docker compose -f docker-compose.yml -f docker-compose.multi-user.yml up -d --wait
The overlay (docker-compose.multi-user.yml) automatically configures:
- Container runs as root (
user: "0") WORKSPACE_MULTI_USER_MODE=true- Explicit root authorization (
OPENACE_ALLOW_ROOT_MULTI_USER=1) - Configuration persistence (
OPENACE_CONFIG_DIR=/home/open-ace/.open-ace, matching theconfig-datavolume mount) - Production security mode by default, and hard failures (
Run ./scripts/bootstrap-compose-env.sh first) ifDB_PASSWORD/SECRET_KEY/OPENACE_ENCRYPTION_KEYare missing - A
home-datavolume at/homeshared with the scheduler service for Qwen session collection (Issue #2729) - The scheduler service enabled by default (its single-user
profiles: [scheduler]gate is cleared)
Option 3: Manual configuration
docker run --user 0 -e WORKSPACE_MULTI_USER_MODE=true \
-e OPENACE_ALLOW_ROOT_MULTI_USER=1 \
-e OPENACE_CONFIG_DIR=/home/open-ace/.open-ace ...
or with environment variables on compose:
WORKSPACE_MULTI_USER_MODE=true \
OPENACE_ALLOW_ROOT_MULTI_USER=1 \
docker compose -f docker-compose.yml -f docker-compose.multi-user.yml up -d
Configuration consistency (Issue #2242): when configuring multi-user mode manually, ALL of the following must hold, otherwise the entrypoint exits at startup with a specific fix hint instead of silently failing on useradd/chown permission errors:
- The container runs as root (
docker run --user 0, composeuser: "0", or manifestrunAsUser: 0) WORKSPACE_MULTI_USER_MODE=trueOPENACE_ALLOW_ROOT_MULTI_USER=1(explicit opt-in)OPENACE_CONFIG_DIR=/home/open-ace/.open-ace(config persistence)
Deployment Modes: Docker vs Bare Metal
Docker root mode
The Docker image already contains everything: Node.js 22, qwen-code-webui@0.2.43, and @qwen-code/qwen-code@0.23.3 (pinned in the Dockerfile). Nothing is installed on the host — the webui/CLI processes and the sudoers file all live inside the container:
docker-entrypoint.shgenerates/etc/sudoers.d/open-ace-webuiat container startup (see below).- System users are created automatically: when an admin creates a user with a
system_accountin the Open ACE admin UI, the entrypoint creates the Linux user, the workspace directory (/workspace/<username>/), and the~/.qwen/directory. No manualuseraddneeded.
Bare-metal mode (package / binary installs)
When running Open ACE directly on the host (package-method install, systemd service), you provision the qwen stack yourself:
-
Node.js >= 22 (the CLI's
engines.nodeis >= 22; npm only warns on a mismatch and still exits 0, so verify the version yourself):# NodeSource setup (Rocky Linux/CentOS)curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -yum install -y nodejs# Or Debian/Ubuntucurl -fsSL https://deb.nodesource.com/setup_22.x | bash -apt-get install -y nodejs -
Install the pinned version pair (the same validated combination shipped by the installers and the Docker image):
npm install -g qwen-code-webui@0.2.43 @qwen-code/qwen-code@0.23.3# Verify installationwhich qwen-code-webui# Should output: /usr/local/bin/qwen-code-webuiNotes: the CLI package name is
@qwen-code/qwen-code(notqwen-code); the CLI command isqwen(notqwen-code). Buildingqwen-code-webuifrom source is not recommended — upstream git tags lag the npm releases, so cloning HEAD yields an unvalidated version. -
Generate the sudoers file with the unified generator (see sudoers Configuration).
-
systemd service: ensure the unit allows sudo execution, e.g.
AmbientCapabilities=CAP_SETUID CAP_SETGIDandNoNewPrivileges=false.
Security requirements (Issue #1893)
Multi-user mode requires root and is suitable for controlled environments only. In production:
- Set
OPENACE_SECURITY_MODE=production(the overlay does this by default) - Set strong secrets:
DB_PASSWORD,SECRET_KEY,OPENACE_ENCRYPTION_KEY - Set
OPENACE_ALLOW_ROOT_MULTI_USER=1explicitly to acknowledge root execution
Port Range Configuration
Each running user instance occupies one port from the workspace pool.
Docker Compose publishes the range via WORKSPACE_PORT_RANGE_START / WORKSPACE_PORT_RANGE_END (defaults 3100 / 3200, set them in .env):
WORKSPACE_PORT_RANGE_START=3100
WORKSPACE_PORT_RANGE_END=3200
Note: Compose expands 3100-3200:3100-3200 into 101 port mappings. If startup is slow or ports clash, shrink the range (e.g. 3100-3110).
The application itself reads the pool from config.json (the install scripts write these env values into it):
{
"workspace": {
"port_range_start": 3100,
"port_range_end": 3200,
"max_instances": 30
}
}
Recommendations:
- Use ports above 3000 (avoid common service ports)
- Allocate enough ports for expected concurrent users (e.g. 100 ports for up to 100 users);
max_instancesdefaults to 30 - Verify ports are not used:
sudo netstat -tlnp | grep 3100-3200
sudoers Configuration
Docker and package deployments share one generator, scripts/generate-sudoers.sh (Issue #2334 — single source of truth). It resolves all paths before writing, validates with visudo -c, installs atomically with mode 440, keeps a .bak backup, and appends to an audit log.
-
Docker:
docker-entrypoint.shgenerates/etc/sudoers.d/open-ace-webuiautomatically at startup. No host-side sudoers is ever written. -
Bare metal (run as root or with sudo):
./scripts/generate-sudoers.sh --output /etc/sudoers.d/open-ace-webui --user open-aceOptions:
--output <file>(required),--user <user>(defaultopen-ace),--install-dir <dir>,--dry-run(print without writing).
The generated file authorizes only wrapper binaries and narrow command aliases — direct qwen-code-webui, git, gh, cat, chown, useradd, rm wildcards are deliberately NOT granted:
| Rule group | Grants |
|---|---|
GIT_SAFE / GH_SAFE | /usr/local/bin/openace-git * / /usr/local/bin/openace-gh * — cross-user git/gh grammar validated by root-owned wrappers |
MKDIR_SAFE | /usr/bin/mkdir *, /bin/mkdir * — cross-user verifier worktree directories (Issue #2674) |
FETCH_WRAPPER | /usr/local/bin/openace-fetch-wrapper — reading tool data under permission-700 user home directories (Issue #2543) |
| WebUI launcher | /usr/local/bin/openace-webui-launch * "<webui-path>" * — fail-closed; the wrapper must exist (Issue #2334) |
| Agent launch | /usr/local/bin/openace-run-as --isolated * — all AI CLIs launch through this wrapper with env -i isolation |
| Security wrappers | openace-chown, openace-useradd, openace-cat, openace-mkdir, openace-write-as, openace-rm — each validates paths/users internally |
OPENACE_UTILS | read-only utilities: test, ls, stat, id, find |
Defaults env_keep preserves only non-sensitive variables (OPENACE_PROXY_TOKEN, OPENACE_PROXY_URL, OPENACE_MODEL, OPENACE_LOG_DIR, GIT_* signing identity, SESSION_TIMEOUT_MS, KEEPALIVE_INTERVAL_MS); API keys, OPENCLAW_TOKEN, and GH_TOKEN are excluded, and secure_path is pinned. A legacy minimal fallback (older setups only, without the wrapper hardening) is:
openace ALL=(ALL) NOPASSWD: /usr/bin/qwen-code-webui *
User Account Requirements
Docker mode: no manual account creation. Create the user in the admin UI with a system_account (or via POST /api/admin/users with "system_account": "alice"); the entrypoint creates the Linux user, /workspace/<username>/, and ~/.qwen/ automatically.
Bare-metal mode: each user with a system_account must have:
-
A Linux account:
# Check if user existsid <system_account># Create if neededsudo useradd -m <system_account> -
An accessible qwen directory:
sudo mkdir -p /home/<system_account>/.qwen/projectssudo chown -R <system_account>:<system_account> /home/<system_account>/.qwen -
Accessible project directories, if applicable.
Project storage layout (Docker, inside the workspace-data volume):
/workspace/ # Docker volume
├── alice/ # user alice's workspace
│ ├── .qwen/ # qwen config
│ ├── project-1/
│ └── project-2/
└── bob/ # user bob's workspace
├── .qwen/
└── my-project/
Migrating from Single-User Mode
-
Stop the existing containers
docker compose downNote: no data is lost — Docker volumes are preserved.
-
Check the data volumes
docker volume ls | grep open-ace# expect: agent-state, config-data, postgres-data, workspace-data -
Start in multi-user mode
./scripts/start-multi-user.shor
docker compose -f docker-compose.yml -f docker-compose.multi-user.yml up -d -
Verify data integrity: users and session data intact, configuration loads, workspace functionality works.
Verification checklist:
- Database data preserved (users, sessions, configuration)
- Docker volume data preserved (config-data, workspace-data)
- Users can log in normally
- Workspaces can be created and used normally
- Existing AI sessions can be restored
Since v2.1.0 (Issue #3446) the old multi_user_mode key is rejected — the server refuses to start and names the replacement. Configure isolation with the workspace.isolation block ({"level": ..., "backend": ...}) or the WORKSPACE_ISOLATION_BACKEND env var; scripts/convert_workspace_isolation.py converts existing configs (see WORKSPACE_ISOLATION.md).
Troubleshooting
Startup errors
| Error | Cause | Solution |
|---|---|---|
multi-user workspace mode requires root | multi-user mode enabled while running as non-root | use ./scripts/start-multi-user.sh or the overlay file |
OPENACE_ALLOW_ROOT_MULTI_USER=1 is not set | running as root without explicit authorization | use ./scripts/start-multi-user.sh or set the env var |
Run ./scripts/bootstrap-compose-env.sh first | production secrets missing | run ./scripts/bootstrap-compose-env.sh |
Workspace failed to load | iframe load failure or timeout | check container logs, verify config, restart the container |
docker-compose.multi-user.yml not found | overlay file missing | make sure the repo was cloned correctly, or download the file from GitHub |
Runtime issues
| Issue | Cause | Solution |
|---|---|---|
| "sudo: no tty present" | sudo requires password | add NOPASSWD to sudoers |
| "qwen-code-webui not found" | executable not installed | install webui in PATH (bare metal) |
| "Permission denied" | user lacks permissions | check sudoers configuration |
| Port allocation failed | all ports in use | increase port range or reduce max_instances |
| Process won't start | user account missing | create the system_account user |
Debugging a workspace instance (Docker)
# Check qwen-code-webui is available
docker compose exec open-ace which qwen-code-webui
# Check the sudoers configuration
docker compose exec open-ace cat /etc/sudoers.d/open-ace-webui
# Check the system user was created
docker compose exec open-ace id alice
# Check workspace directories
docker compose exec open-ace ls -la /workspace/
# Try launching an instance manually
docker compose exec open-ace sudo -u alice qwen-code-webui --port 3100 --host 0.0.0.0
Checking multi-user status
# View running instances
curl http://localhost:19888/api/workspace/instances
# Check logs
tail -f /home/open-ace/open-ace/logs/open-ace.log | grep WebUIManager
See also DEPLOYMENT.md for the base deployment and ENV_REFERENCE.md for the workspace-related environment variables.