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
  • active execution ownership follows that same storage truth, so separate container contexts can coexist on the same logical path name when Ota can prove they resolve to different managed isolation namespaces

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    command:      exe: mvn      args:        - 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