# Design / ADR: Tool Container Home Directory ## Status Proposed ## Context Tool containers currently mix home-directory conventions: - Legacy `dockerfile` and `compose` tool types mount the repository at `/workspace` and run as `root`. - Manifest-based tool types may run as a non-root user (`user`) and already support `~` / `$HOME` expansion to `/home/{user.name}` via the `home-path-expansion` work. - `tool-images/base.dockerfile` already creates a `user` with `WORKDIR /home/user`, but the platform does not guarantee this for all tool types. Users expect a predictable, writable home directory inside every tool container and want the repository/workspace to live under it, preserving the repository's root directory name so paths are meaningful (e.g., `/home/user/my-app`, not `/home/user/workspace` or a generic `/workspace`). ## Goals 1. Provide a **configurable workspace/home directory** that **defaults to `/home/user`** for all tool containers. 2. Mount the repository/workspace under that directory using the **actual directory name** (`{repo_name}` or `{workspace_name}`). 3. Make the setting part of the **ToolType / manifest definition** so tool authors control it. 4. Keep **`manifest.user.name`** as the runtime user; the new field controls the directory path. 5. Preserve **`/workspace` as a compatibility symlink** to avoid breaking existing scripts, bookmarks, and settings. 6. Ensure **config-profile and git mounts** staged by the API remain writable by the non-root container user. ## Non-Goals - Changing the container runtime user model (still driven by `manifest.user`). - Moving non-tool services (API, web, Postgres, Redis) under `/home/user`. - Forcing every existing manifest to migrate; explicit repo mount targets remain respected. - Adding a frontend UI for the new field in this iteration (backend field + manifest schema change only). ## Decision ### Configuration surface Add a `home_directory` field to the ToolType model and the manifest schema. It defaults to `/home/user` and can be overridden per tool type. - **ToolType level**: `ToolType.home_directory: str = "/home/user"` (non-nullable, default). - **Manifest level**: `manifest.home_directory: str` (optional; if absent, fall back to `ToolType.home_directory`). The runtime home directory for a container is resolved in this precedence order: 1. Manifest `home_directory` if present. 2. ToolType `home_directory` (database column, default `/home/user`). 3. Legacy fallback: `/root` for compose/dockerfile tools without the new field. ### What `home_directory` controls 1. **Repository/workspace mount target** when no explicit repo mount is defined: - Repositories: `{home_directory}/{repo_name}` - Workspaces: `{home_directory}/{workspace_name}` (fall back to `{repo_name}`) 2. **Dockerfile `HOME` environment variable** for manifest tools: `ENV HOME={home_directory}`. 3. **Dockerfile `WORKDIR`** for manifest tools (when no explicit `runtime.working_dir` overrides it). 4. **Base for `~` / `$HOME` expansion** in config-profile mounts and git-mount mappings. ### Precedence: explicit manifest mount wins If a manifest already contains a mount with `source_type: repo` and an explicit `target`, that target is used unchanged. `home_directory` is only used to synthesize the default repo/workspace mount when none is explicitly specified. ### Migration for legacy tools Use an **Alembic data migration**: 1. Add `home_directory` column to `tool_types`. 2. Set existing rows to `/home/user`. 3. Rewrite stored `compose_template` and `dockerfile_template` strings to replace `/workspace` with `{home_directory}/{{WORKSPACE_NAME}}` (or equivalent template variable) where the mount target is the workspace. The migration is reversible via downgrade: restore the original `/workspace` strings and drop the column. ### `/workspace` compatibility Keep `/workspace` as a symlink inside the container pointing to the resolved repo/workspace target (e.g., `/home/user/my-app`). This protects: - Existing user startup commands and scripts. - Bookmarks and IDE settings that reference `/workspace`. - Legacy templates that were migrated. The symlink is created by the generated Dockerfile (or startup entrypoint) because the actual repo is mounted at runtime. ### Permission model Use an **entrypoint permission fixer** at container startup: - Run as `root` (or via `sudo`) before switching to the runtime user. - Chown mounted paths under `{home_directory}` to the container user. - Avoid `chown -R` on large repo subtrees; chown the top-level directories and rely on the runtime user owning newly created files. - This handles config-profile and git mounts that arrive at runtime as bind mounts from the host, which may otherwise be owned by `root` because the API stages them. ## Consequences ### Positive - Predictable, user-writable home directory across all tool containers. - Repository mount paths are meaningful (`/home/user/my-app`). - Backward-compatible symlink keeps existing user data and scripts working. - Tool authors can opt into other home directories without changing the runtime user. ### Negative / Risks - Requires an Alembic data migration that rewrites stored templates; downgrade must be carefully tested. - Entrypoint permission fixer adds startup complexity and requires `root`/`sudo` inside the container. - Creating `/workspace` symlink at build time vs. runtime needs care because the target may not exist until the repo is mounted. - Legacy `tool-images/base.dockerfile` runs as `USER user`; it will need an entrypoint that can elevate permissions or the base image must be adjusted. ## Related Documents - `openspec/designs/home-path-expansion.md` - `openspec/specs/home-path-expansion.md` - `openspec/tasks/home-path-expansion.md` - `docs/superpowers/plans/tool-container-home-directory.md` - `docs/superpowers/specs/tool-container-home-directory-test-plan.md`