Bug 1 — in-container repo mounting:
- docker-compose.yml: added /data/working-copies:/data/working-copies mount
to API container so workspace dirs are visible on host filesystem
- Dockerfile: create /data/working-copies dir in image
Bug 2 — /home/user not writable:
- workspace_manager.py: chmod 777 workspace dirs + 666 files after clone
and after sync, so any container user can write
- manifest_compiler.py: explicit mkdir + chown + chmod 755 for home dir
in generated Dockerfile
Bug 3 — terminal text shifts left on typing:
- terminal.tsx: removed manual term.refresh() after fit (caused reflow)
- Track lastSentCols/lastSentRows and only send resize when dimensions
actually changed, preventing resize feedback loops
Bug 4 — ESC key captured by terminal:
- terminal.tsx: attachCustomKeyEventHandler allows ESC to propagate to
browser when not in alternate buffer (vim/tmux), so modals/navigation
work; ESC still sent to PTY when in vim/tmux alternate screen
Quality gates: ruff clean, tsc --noEmit clean, pytest workspaces (9 passed)
FastAPI auto-redirects /workspaces to /workspaces/ with 307.
Behind Traefik (HTTP internal), the 307 becomes http://,
triggering Mixed Content in the browser. Adding trailing
slashes avoids the redirect entirely.
The API container used a named Docker volume (repo_data:/data/repos) for
storing repositories. When creating tool instances with direct mount mode,
the API told Docker to bind-mount /data/repos/<repo>:/workspace into the
tool container. But the Docker daemon resolves bind-mount paths on the HOST
filesystem, not inside the API container. Since the host had no /data/repos
(the repos only existed inside the named volume), tool containers mounted
empty directories.
Changed both compose files to use a host bind mount (/data/repos:/data/repos)
instead of a named volume. This ensures:
- The API container and tool containers both see the same /data/repos path
- Bind mounts from /data/repos into tool containers work correctly
For existing installations: repos previously stored in the repo_data named
volume should be copied to /data/repos on the host before restarting the
stack.
Quality gates: compose file syntax valid
When API runs in Docker with named volume instance_data:/data/instances,
generated docker-compose.yml files use bind mounts like
/data/instances/.../repo-clone:/workspace. Docker resolves bind mounts
on the host filesystem, not in named volumes, so containers see empty
directories.
By mounting /data/instances as a host bind mount, both the API and
generated tool containers access the same host path.
- Add INSTANCE_BASE_PATH config option (defaults to /data/instances)
- Update docker.py to use configured path instead of hardcoded 'data/instances'
- Update Dockerfile to create /data/instances and chown to appuser
- Add instance_data volume to docker-compose.traefik.yml and docker-compose.yml
- Set INSTANCE_BASE_PATH env var in both compose files
This fixes the PermissionError when creating tool instances because
appuser can now write to /data/instances.
- Add init_database() with alembic programmatic API and retry logic
- Add connection retry with exponential backoff (5 attempts)
- Improve error messages for connection/auth failures
- Add table existence check before seeding data
- Update startup event to run migrations before seeding
- Add wait-for-db.sh script for Docker containers
- Update Docker and docker-compose configurations
Quality gates: ruff ✓, mypy ✓, unit tests (8 passed)
Vite reads env vars at build time, not runtime. The previous setup
only set them in docker-compose 'environment' which is only available
at container runtime. Now they are passed as build args so Vite can
embed the correct API URL during the build process.
- Add build args to web service in both compose files
- Update Dockerfile to accept ARGs and set ENV for Vite
- Fixes login redirect always going to localhost:8000
The frontend code uses VITE_API_BASE_URL but docker-compose files
and .env.example were setting VITE_API_URL, causing the login
redirect to fall back to localhost:8000.
- Update docker-compose.traefik.yml: VITE_API_URL → VITE_API_BASE_URL
- Update docker-compose.yml: VITE_API_URL → VITE_API_BASE_URL
- Update .env.example: VITE_API_URL → VITE_API_BASE_URL
- Add docker-compose.yml with postgres, redis, api, and web services
- Add multi-stage Dockerfile for API (Python 3.11)
- Add multi-stage Dockerfile for web (Node.js 20 + nginx)
- Add Makefile with common development commands
- Add .env.example with all required environment variables
- Add placeholder pyproject.toml and package.json for builds
- Configure health checks for all services
- Setup persistent volumes for postgres, redis, and repos
- Run services as non-root users
- Remove env_file references from docker-compose.yml and docker-compose.prod.yml
- Add explicit environment variable forwarding for all services
- Use TRAEFIK_NETWORK env var in docker-compose.traefik.yml instead of hardcoded name
- Add VITE_* frontend variables to .env.example and deploy/.env.example
- Add missing production variables (TRAEFIK_ENTRYPOINT, TRAEFIK_CERT_RESOLVER) to deploy/.env.example
This makes environment configuration more explicit and easier to manage
across different deployment scenarios (local dev, CI/CD, Portainer).
- Add ToolManifest Pydantic models with validators for ports, mounts, health checks, and traefik config
- Implement in-memory ToolRegistry with YAML loading and built-in manifest scanning
- Add FastAPI CRUD routes for listing, retrieving, and creating tool manifests
- Include built-in manifests for runfusion and code-server
- Harden web Dockerfile with unprivileged nginx and port 8080
- Add tool manifest specification documentation and architecture updates
Fusion-Task-Id: FN-003