Skip to main content

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​

./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 the config-data volume mount)
  • Production security mode by default, and hard failures (Run ./scripts/bootstrap-compose-env.sh first) if DB_PASSWORD / SECRET_KEY / OPENACE_ENCRYPTION_KEY are missing
  • A home-data volume at /home shared 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:

  1. The container runs as root (docker run --user 0, compose user: "0", or manifest runAsUser: 0)
  2. WORKSPACE_MULTI_USER_MODE=true
  3. OPENACE_ALLOW_ROOT_MULTI_USER=1 (explicit opt-in)
  4. 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.sh generates /etc/sudoers.d/open-ace-webui at container startup (see below).
  • System users are created automatically: when an admin creates a user with a system_account in the Open ACE admin UI, the entrypoint creates the Linux user, the workspace directory (/workspace/<username>/), and the ~/.qwen/ directory. No manual useradd needed.

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:

  1. Node.js >= 22 (the CLI's engines.node is >= 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/Ubuntu
    curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
    apt-get install -y nodejs
  2. 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 installation
    which qwen-code-webui
    # Should output: /usr/local/bin/qwen-code-webui

    Notes: the CLI package name is @qwen-code/qwen-code (not qwen-code); the CLI command is qwen (not qwen-code). Building qwen-code-webui from source is not recommended — upstream git tags lag the npm releases, so cloning HEAD yields an unvalidated version.

  3. Generate the sudoers file with the unified generator (see sudoers Configuration).

  4. systemd service: ensure the unit allows sudo execution, e.g. AmbientCapabilities=CAP_SETUID CAP_SETGID and NoNewPrivileges=false.

Security requirements (Issue #1893)​

Multi-user mode requires root and is suitable for controlled environments only. In production:

  1. Set OPENACE_SECURITY_MODE=production (the overlay does this by default)
  2. Set strong secrets: DB_PASSWORD, SECRET_KEY, OPENACE_ENCRYPTION_KEY
  3. Set OPENACE_ALLOW_ROOT_MULTI_USER=1 explicitly 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_instances defaults 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.sh generates /etc/sudoers.d/open-ace-webui automatically 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-ace

    Options: --output <file> (required), --user <user> (default open-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 groupGrants
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 wrappersopenace-chown, openace-useradd, openace-cat, openace-mkdir, openace-write-as, openace-rm — each validates paths/users internally
OPENACE_UTILSread-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:

  1. A Linux account:

    # Check if user exists
    id <system_account>

    # Create if needed
    sudo useradd -m <system_account>
  2. An accessible qwen directory:

    sudo mkdir -p /home/<system_account>/.qwen/projects
    sudo chown -R <system_account>:<system_account> /home/<system_account>/.qwen
  3. 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​

  1. Stop the existing containers

    docker compose down

    Note: no data is lost — Docker volumes are preserved.

  2. Check the data volumes

    docker volume ls | grep open-ace
    # expect: agent-state, config-data, postgres-data, workspace-data
  3. Start in multi-user mode

    ./scripts/start-multi-user.sh

    or

    docker compose -f docker-compose.yml -f docker-compose.multi-user.yml up -d
  4. 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​

ErrorCauseSolution
multi-user workspace mode requires rootmulti-user mode enabled while running as non-rootuse ./scripts/start-multi-user.sh or the overlay file
OPENACE_ALLOW_ROOT_MULTI_USER=1 is not setrunning as root without explicit authorizationuse ./scripts/start-multi-user.sh or set the env var
Run ./scripts/bootstrap-compose-env.sh firstproduction secrets missingrun ./scripts/bootstrap-compose-env.sh
Workspace failed to loadiframe load failure or timeoutcheck container logs, verify config, restart the container
docker-compose.multi-user.yml not foundoverlay file missingmake sure the repo was cloned correctly, or download the file from GitHub

Runtime issues​

IssueCauseSolution
"sudo: no tty present"sudo requires passwordadd NOPASSWD to sudoers
"qwen-code-webui not found"executable not installedinstall webui in PATH (bare metal)
"Permission denied"user lacks permissionscheck sudoers configuration
Port allocation failedall ports in useincrease port range or reduce max_instances
Process won't startuser account missingcreate 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.