Operate
Container Attachments
How container attachments work, why isolated_paths are repo-relative, and when attached cache paths preserve value across ephemeral runs.
Recommended next
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_WORKSPACEautomatically 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.
execution: contexts: app: backend: container attachments: isolated_paths: - .m2Invalid: absolute container paths are rejected. isolated_paths must stay relative to the repo root.
execution: contexts: app: backend: container attachments: isolated_paths: - /workspace/.m2How 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_modulesbecomes/workspace/node_modules.nextbecomes/workspace/.next.m2becomes/workspace/.m2.npmbecomes/workspace/.npm
These repo-relative paths already line up with where frontend tools usually write, so no extra tool reconfiguration is needed.
execution: contexts: app: backend: container attachments: isolated_paths: - node_modules - .nextWhy 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.
execution: contexts: application: backend: container attachments: isolated_paths: - .m2 tasks: build: context: application run: mvn packageWhen 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.
execution: contexts: tooling: backend: container attachments: isolated_paths: - .npm tasks: api:automation: context: tooling script: ./scripts/api/run-api-tests.shThe same fallback model also covers pnpm, Gradle, pip, and Poetry when the context isolates their conventional cache paths.
execution: contexts: tooling: backend: container attachments: isolated_paths: - .pnpm-store - .gradle - .pip-cache - .pypoetry-cacheIf you want to override ota's fallback, use OTA_WORKSPACE explicitly instead of hardcoding /workspace.
tasks: build: env: MAVEN_OPTS: -Dmaven.repo.local=${OTA_WORKSPACE}/custom-m2/repository run: mvn packagePersistent 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.venvthat should stay container-owned - use it for tool caches like
.m2or.npmwhen 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/.m2inisolated_paths - do not assume the attachment alone retargets a tool to that path
- do not use
node_modulesas 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