Skip to content

[Feature]: Environment-scoped shared volumes for task build and dependency caches #3423

Open

Description

@zjx20

Preflight

  • I searched existing issues and docs before opening this.
  • This proposal is grounded in a real Roomote use case, not a broad generated feature list.

Problem

Successive tasks in the same project repeatedly download dependencies and rebuild code in separate Docker task containers. Cache files created by one task are not automatically available to the next task, even when both use the same Roomote environment.

Examples include Go's module and build caches, npm's package download cache, and equivalent caches for other language ecosystems. Losing these caches between tasks increases network traffic, build time, and the time users wait for implementation and verification.

tool_versions and repository commands automate setup, but they do not persist these caches across task containers. Task-specific workspace volumes preserve one task's files; they do not provide shared cache storage for successive tasks in an environment.

Current workaround

  • Download dependencies and rebuild in each new task container.
  • Reuse an existing task when appropriate, retaining its local caches, rather than starting an independent task.
  • Customize Docker provisioning outside the environment editor to add mounts, requiring deployment-specific maintenance.

Proposed behavior

Allow administrators to configure shared volume mounts for each environment through the dashboard and its YAML definition.

Each environment should have its own declared volumes. When Roomote launches a Docker task using that environment, it should mount the same persistent volumes at the configured container paths. Subsequent tasks should reuse cached data without sharing their source workspaces or task state.

For example, an environment could declare separate volumes for:

  • Go module downloads, mounted at a directory selected through GOMODCACHE.
  • Go build artifacts, mounted at a directory selected through GOCACHE.
  • npm package downloads, mounted at a directory selected through npm_config_cache.

An illustrative YAML shape could be:

shared_volumes:
  - name: go-modules
    mount_path: /cache/go-modules
  - name: go-build
    mount_path: /cache/go-build
  - name: npm
    mount_path: /cache/npm

env:
  GOMODCACHE: /cache/go-modules
  GOCACHE: /cache/go-build
  npm_config_cache: /cache/npm

The field names are suggestions, not a required schema. Volume names should be scoped to the environment identity: two environments declaring go-build should receive different storage by default.

Expected behavior:

  • Create missing volumes automatically and attach them before setup commands or agent execution.
  • Preserve cache contents when a task finishes or its container is removed, and mount them again for subsequent tasks and resumes.
  • Keep source checkouts and task state isolated; share only explicitly configured directories.
  • Make mounted directories writable by the worker user without requiring the agent to fix permissions on every task.
  • Support overlapping tasks when the selected cache supports concurrent access; document access semantics and any limitations.
  • Provide a way to inspect, clear, and delete environment caches, with clear behavior when mounts are removed or an environment is deleted.

Docker named volumes are one possible implementation. Other storage mechanisms providing equivalent environment-scoped reuse would also meet the need. Initial support could be limited to the Docker provider, with unsupported providers reporting the limitation explicitly.

Affected surfaces

Self-hosting or deployment

Self-hosting implications

Shared volumes consume persistent host storage and need ownership, lifecycle, and cleanup management separate from task-container cleanup. Names should be scoped by deployment and environment identity to avoid accidental sharing.

Tasks running on different Docker hosts would need placement rules or shared storage to reuse the same data; an initial single-host implementation should document this limitation. The basic local-volume workflow should not require another external service or inbound port.

Alternatives considered

  • Prepared environment images: useful for preinstalled tools and dependencies, but do not preserve cache entries generated by later task builds. Image preparation and writable cache volumes address complementary needs.
  • Task snapshots or retained containers: preserve one task's state, but do not provide a shared cache for independent tasks.
  • Remote package proxies or build-cache services: can help, but require ecosystem-specific configuration and do not replace a general environment-level mount mechanism.
  • One global cache for all environments: simpler, but does not give projects independent configuration and lifecycle control.

Documentation impact

Document the UI and YAML fields, supported providers, mount paths and permissions, cache-directory configuration for common ecosystems, concurrent-access limitations, multi-host behavior, and cache cleanup. Explain that these volumes persist across tasks independently of source-code checkout, worker-image updates, and task snapshots.

Activity

  1. roomote-community commented on Oct 7, 2026

    @roomote-community
    Contributor

    I've analyzed this issue and here's my proposed implementation plan:

    1. Add an optional shared_volumes field to packages/types/src/environment-config.ts with entries shaped as { name, mount_path }. Validate a constrained logical name, an absolute cache path, unique names and mount paths, a small per-environment limit, and reject overlapping/reserved paths such as the task workspace. Keep this to Docker-managed named volumes rather than arbitrary host bind mounts. Add the field to apps/web/src/components/settings/environments/yaml-utils.ts so YAML round-trips preserve it, and expose the same list in the visual environment editor using the existing Docker-project list patterns.

    2. Add a shared Docker environment-volume helper under packages/compute-providers/src/ that derives an opaque deterministic volume name from the durable deployment identity, environment UUID, and logical volume name. Label every volume with Roomote-managed resource type, deployment, environment, and logical-name labels. This keeps two environments or deployments from accidentally sharing storage and makes volumes discoverable even after a declaration is renamed or removed.

    3. In apps/controller/src/compute-providers/spawn-docker-worker.ts, create missing declared volumes before the worker container starts, attach them with Docker --mount type=volume, and initialize each mount root for the resolved worker UID/GID before install-worker.sh and environment setup run. Leave these volumes out of destroyDockerInstance() in packages/compute-providers/src/adapters/docker.ts; task workspace volumes remain task-scoped, while retained-container resumes naturally retain the original shared mounts.

    4. Add a distinct provider capability in packages/types/src/compute-providers/capabilities.ts for environment shared volumes, enabled only for the Docker provider initially. Add a common spawn-time guard so a task using an environment with shared_volumes fails early with an actionable unsupported-provider message instead of silently running without its caches.

    5. Add an admin-only environment-volume management queue, following the existing Docker validation queue pattern in packages/sdk/src/server/lib/docker-environment-validation.ts and apps/bullmq/src/docker-validation-queue.ts. The BullMQ process already has restricted Docker volume API access in the supported Compose deployment. Use it to:

      • list configured and orphaned environment volumes by label and report basic status/usage;
      • clear a volume by removing it so the next task recreates it, rejecting the operation while a task container still has it attached;
      • enqueue retryable cleanup of all labeled volumes when an environment is deleted;
      • preserve a removed/renamed declaration until an administrator explicitly deletes that orphaned cache, avoiding destructive cleanup during a normal config edit.
    6. Add tRPC commands and dashboard controls alongside the existing environment/snapshot controls in apps/web/src/trpc/routers/_app.ts, apps/web/src/trpc/commands/environments/, and apps/web/src/components/settings/environments/Environments.tsx. Show each cache's logical name, mount path, existence/usage, and clear/delete actions, and update environment-deletion copy to state that cache cleanup is queued.

    7. Cover the contract with schema and YAML round-trip tests, Docker spawn tests for deterministic isolation, attachment, ownership, fresh-task reuse, and standby resume, teardown tests proving per-task cleanup does not remove environment volumes, queue tests for inspect/clear/delete and active-container conflicts, provider-capability tests, and environment UI/command tests. The main existing suites are packages/types/src/environment-config.test.ts, apps/web/src/components/settings/environments/YamlEnvironmentEditor*.test.*, apps/controller/src/compute-providers/__tests__/spawn-docker-worker.test.ts, apps/controller/src/compute-providers/__tests__/docker-sandbox-security.test.ts, and environment command/component tests.

    8. Update apps/docs/environments.mdx, apps/docs/environments/definition.mdx, and SELF_HOSTING.md with the YAML example, Docker-only initial support, writable-path rules, explicit cache cleanup, concurrent read/write semantics, cache-poisoning/trust considerations, and the single-host limitation. Concurrent tasks would mount the same volume read/write; Roomote would not serialize access, so the configured package/build cache must support concurrent writers.

    This approach keeps source checkouts and task state isolated, reuses only explicitly declared cache directories, preserves caches across ordinary task teardown, and gives administrators a bounded lifecycle instead of turning cache volumes into untracked Docker resources. It also avoids a database migration: the declaration stays in the existing environment config JSON, while Docker labels provide runtime discovery.

    Please tag @roomote if you'd like me to implement this, or reply with feedback on the plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions