- Add design doc / ADR for configurable /home/user home directory - Add implementation plan with phased rollout - Add test plan / QA checklist - Update OpenSpec task for home-path-expansion
5.8 KiB
Design / ADR: Tool Container Home Directory
Status
Proposed
Context
Tool containers currently mix home-directory conventions:
- Legacy
dockerfileandcomposetool types mount the repository at/workspaceand run asroot. - Manifest-based tool types may run as a non-root user (
user) and already support~/$HOMEexpansion to/home/{user.name}via thehome-path-expansionwork. tool-images/base.dockerfilealready creates auserwithWORKDIR /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
- Provide a configurable workspace/home directory that defaults to
/home/userfor all tool containers. - Mount the repository/workspace under that directory using the actual directory name (
{repo_name}or{workspace_name}). - Make the setting part of the ToolType / manifest definition so tool authors control it.
- Keep
manifest.user.nameas the runtime user; the new field controls the directory path. - Preserve
/workspaceas a compatibility symlink to avoid breaking existing scripts, bookmarks, and settings. - 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 toToolType.home_directory).
The runtime home directory for a container is resolved in this precedence order:
- Manifest
home_directoryif present. - ToolType
home_directory(database column, default/home/user). - Legacy fallback:
/rootfor compose/dockerfile tools without the new field.
What home_directory controls
- 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})
- Repositories:
- Dockerfile
HOMEenvironment variable for manifest tools:ENV HOME={home_directory}. - Dockerfile
WORKDIRfor manifest tools (when no explicitruntime.working_diroverrides it). - Base for
~/$HOMEexpansion 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:
- Add
home_directorycolumn totool_types. - Set existing rows to
/home/user. - Rewrite stored
compose_templateanddockerfile_templatestrings to replace/workspacewith{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 viasudo) before switching to the runtime user. - Chown mounted paths under
{home_directory}to the container user. - Avoid
chown -Ron 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
rootbecause 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/sudoinside the container. - Creating
/workspacesymlink at build time vs. runtime needs care because the target may not exist until the repo is mounted. - Legacy
tool-images/base.dockerfileruns asUSER user; it will need an entrypoint that can elevate permissions or the base image must be adjusted.
Related Documents
openspec/designs/home-path-expansion.mdopenspec/specs/home-path-expansion.mdopenspec/tasks/home-path-expansion.mddocs/superpowers/plans/tool-container-home-directory.mddocs/superpowers/specs/tool-container-home-directory-test-plan.md