Operate

Container Attachments

How container attachments work, why isolated_paths are repo-relative, and when attached cache paths preserve value across ephemeral runs.

learnmaintainersintermediatestable2026-04-29

Why this feature exists

Attachments let a container context keep the source tree bind-mounted from the host while moving selected dependency or artifact paths onto Ota-managed storage.

This is the right boundary for platform-sensitive or high-churn paths such as node_modules, .next, .venv, .m2, or .npm when those paths should survive container recreation without polluting the host tree.

  • source still comes from the host bind mount at /workspace
  • only the declared attachment paths are overlaid
  • for attachments.isolated_paths, ota backs each path with an engine-managed named volume
  • ota injects OTA_WORKSPACE automatically for task execution, so advanced overrides do not need to hardcode /workspace
  • that means the attached path can survive ephemeral container recreation when the same context shape is reused

The most important rule

attachments.isolated_paths entries are repo-relative paths, not absolute container paths.

Ota maps each entry to /workspace/<path> inside the container.

Correct: declare the repo-relative path. Ota maps .m2 to /workspace/.m2 inside the container.

Correctyaml
execution:  contexts:    app:      backend: container      attachments:        isolated_paths:          - .m2

Invalid: absolute container paths are rejected. isolated_paths must stay relative to the repo root.

Invalidyaml
execution:  contexts:    app:      backend: container      attachments:        isolated_paths:          - /workspace/.m2

How the mapping works

node_modules and .next are common examples, not special cases baked into ota.

The real rule is simpler: declare any repo-relative path that should be lifted out of disposable container-local state and kept on durable Ota-managed storage instead.

  • node_modules becomes /workspace/node_modules
  • .next becomes /workspace/.next
  • .m2 becomes /workspace/.m2
  • .npm becomes /workspace/.npm

These repo-relative paths already line up with where frontend tools usually write, so no extra tool reconfiguration is needed.

Typical frontend contextyaml
execution:  contexts:    app:      backend: container      attachments:        isolated_paths:          - node_modules          - .next

Why these frontend paths are common examples

Frontend repos often use node_modules and .next because those paths already live under the repo root and the tools already write there naturally.

That makes them good examples of attachment-backed paths that need no extra env wiring.

  • without an attachment, those paths stay in the container filesystem
  • persistent containers may reuse that state because the same container survives
  • ephemeral containers usually lose that state because each run starts fresh
  • with an attachment, ota lifts that repo path onto durable storage so later runs can reuse it
  • the same pattern works for any repo-relative path, not just Node or Next.js paths

When tool reconfiguration is required

An attachment only makes a path durable. It does not automatically force a tool to use that path.

If the tool already writes into a repo-relative path under /workspace, the attachment is enough. If the tool writes somewhere else by default, ota can now derive the common cache redirects for well-known pairs such as Maven plus .m2, npm or npx plus .npm, pnpm plus .pnpm-store, Gradle plus .gradle, pip plus .pip-cache, and Poetry plus .pypoetry-cache.

Use explicit env only when you want to override ota's fallback wiring or point the tool somewhere else on purpose.

Maven usually writes under the container home directory, not under /workspace, so ota derives the redirect automatically when the context isolates .m2.

Maven cacheyaml
execution:  contexts:    application:      backend: container      attachments:        isolated_paths:          - .m2 tasks:  build:    context: application    run: mvn package

When the repo does not have a node_modules tree but still fetches packages through npm or npx, ota can derive the cache path automatically from .npm too.

npm or npx cacheyaml
execution:  contexts:    tooling:      backend: container      attachments:        isolated_paths:          - .npm tasks:  api:automation:    context: tooling    script: ./scripts/api/run-api-tests.sh

The same fallback model also covers pnpm, Gradle, pip, and Poetry when the context isolates their conventional cache paths.

Other known cache pairsyaml
execution:  contexts:    tooling:      backend: container      attachments:        isolated_paths:          - .pnpm-store          - .gradle          - .pip-cache          - .pypoetry-cache

If you want to override ota's fallback, use OTA_WORKSPACE explicitly instead of hardcoding /workspace.

Explicit overrideyaml
tasks:  build:    env:      MAVEN_OPTS: -Dmaven.repo.local=${OTA_WORKSPACE}/custom-m2/repository    run: mvn package

Persistent versus ephemeral

Persistent containers naturally preserve container-local state because the container itself is reused.

Ephemeral containers do not preserve container-local state by themselves, but attached isolated paths can still preserve value because the state lives in the Ota-managed volume, not in the disposable container filesystem.

  • persistent container + no attachment: container-local cache can survive because the container survives
  • ephemeral container + no attachment: container-local cache usually does not survive
  • ephemeral container + attachment-backed cache path: the cache can survive because the path is volume-backed
  • tasks still run as separate tasks; attachments preserve the path, not one shared shell session

When to use it

  • use it for repo-relative dependency trees like node_modules, .next, or .venv that should stay container-owned
  • use it for tool caches like .m2 or .npm when you also point the tool at /workspace/<path>
  • use it when ephemeral runs should still get durable cache value without pretending the whole container is persistent

What not to do

  • do not put absolute paths like /workspace/.m2 in isolated_paths
  • do not assume the attachment alone retargets a tool to that path
  • do not use node_modules as a generic Node answer when the repo does not actually have a repo-local dependency tree
  • do not treat attachments as one shared session; they only preserve the declared path