Set up a dev container
boboddy work spins up a single Docker container from your project’s .devcontainer/devcontainer.json for every step execution, and runs the OpenCode agent inside that same container (from a Boboddy-managed mounted runtime payload). So your devcontainer is the one and only runtime — it must contain the language toolchain your steps need, but it does not need OpenCode installed; Boboddy mounts and launches its own pinned OpenCode runtime.
This page provides a prompt you can paste into any AI coding assistant (Claude, Copilot, etc.) to generate a minimal, project-appropriate devcontainer config.
AI prompt
Section titled “AI prompt”Prefer a design session. If you want to generate one outside Boboddy instead, copy the prompt below and run it in your project’s root directory. The AI will inspect your codebase and output a .devcontainer/devcontainer.json (and a Dockerfile if needed).
You are helping me create a minimal `.devcontainer/devcontainer.json` for this project so it can be used as a Boboddy worker execution environment.
**Your goal:** produce the smallest devcontainer config that gives the execution environment everything it needs — nothing more.
**Step 1 — Analyse the codebase**
Examine the following files (read whichever exist):
- `package.json` / `bun.lock` / `yarn.lock` / `pnpm-lock.yaml` — runtime, package manager, scripts- `Dockerfile` / `docker-compose.yml` — existing base image choices- `.nvmrc` / `.node-version` / `.tool-versions` — pinned runtimes- `pyproject.toml` / `requirements.txt` / `Pipfile` — Python deps- `go.mod` — Go version- `Cargo.toml` — Rust toolchain- `*.sln` / `*.csproj` — .NET version- Any CI config (`.github/workflows/`, `.circleci/`, etc.) — look for `uses: actions/setup-*` or `apt-get install` lines
**Step 2 — Determine the environment**
From your analysis, identify:
1. **Primary runtime** and its minimum required version (e.g. Node 20, Python 3.11, Go 1.22)2. **Package manager** (npm / yarn / pnpm / bun / pip / cargo / go)3. **System packages** genuinely required at runtime (not just for local dev convenience)4. **Any secrets or environment variables** the code reads — note them as comments; do not hardcode values
**Step 3 — Choose a base**
- Prefer an official [Dev Container base image](https://mcr.microsoft.com/en-us/artifact/mar/devcontainers/) that matches the primary runtime.- If the project already has a `Dockerfile`, use it as the base (`"build": { "dockerfile": "../Dockerfile" }`).- Only write a custom `Dockerfile` if no suitable base image exists.
**Step 4 — Write the config**
Output:
1. `.devcontainer/devcontainer.json` — include only the fields you actually need: - `name` — short human-readable name - `image` or `build` — base image or Dockerfile path - `features` — Dev Container Features for any additional tooling (e.g. git, docker-in-docker). Use the `ghcr.io/devcontainers/features/` namespace. Omit features already present in the base image. - `containerEnv` — environment variables that must be set (use placeholder values like `"${localEnv:MY_SECRET}"` for secrets) - `onCreateCommand` — dependency install command if needed (e.g. `npm ci`, `pip install -r requirements.txt`) - `remoteUser` — set to a non-root user if the base image provides one
2. If you wrote a custom `Dockerfile`, output it as `.devcontainer/Dockerfile`.
**Rules:**- Do NOT include VS Code-specific fields (`customizations`, `extensions`, `settings`) — this container is used for headless agent execution, not interactive development.- Do NOT add tools "just in case" — every layer adds startup latency.- Do NOT hard-code secret values.- If the project needs to run Docker commands (e.g. it builds or runs containers itself), prefer the `docker-outside-of-docker` feature (`ghcr.io/devcontainers/features/docker-outside-of-docker`) over `docker-in-docker` — it mounts the host Docker socket rather than running a nested daemon, which is faster and uses less memory.- Prefer `onCreateCommand` for install steps over baking dependencies into a custom image, unless the install is very slow.- Add a short comment above each non-obvious field explaining why it is there.
Output the file(s) using code fences with the file path as the language identifier, for example:
````.devcontainer/devcontainer.json{ ... }````
After the file output, add a **"Why"** section (plain prose, ≤ 150 words) explaining your base image choice and any non-obvious decisions.What to do with the output
Section titled “What to do with the output”- Create a
.devcontainer/directory at your project root if it doesn’t exist. - Write the generated
devcontainer.json(and optionalDockerfile) into that directory. - Commit both files.
- Test locally by running
boboddy work --once— the worker will pull the image and report any missing tools in its logs.
Multiple configs
Section titled “Multiple configs”A repository can keep more than one devcontainer config, for example a frontend image, a performance-testing image, or a CI image:
.devcontainer/ devcontainer.json # default frontend/devcontainer.json perf/devcontainer.jsonSteps use the default config unless they select another. The default is .devcontainer/devcontainer.json, then devcontainer.json at the repository root. To run a step in a different config, pass its full repo-relative path to Runtime.devcontainer:
import { defineStep, Runtime } from "@boboddy/sdk";
export const buildFrontend = defineStep({ key: "build-frontend", name: "Build Frontend", environment: { runtime: Runtime.devcontainer({ config: ".devcontainer/frontend/devcontainer.json", }), }, agentPrompt: "Build the frontend.",});Things to know when you write an alternate config:
- The file must be named
devcontainer.jsonor.devcontainer.json, in any directory of the repository. Theconfigvalue is the full path, not a name. - There is no fallback. If the file is missing on the branch the step starts from, the step fails and names the path and branch. A later step in a pipeline starts from the work branch of the nearest earlier step that produced one, so that branch must contain the config (see Base branch). See Selecting a devcontainer config.
- The workspace is still mounted from the repository root, whichever directory the config lives in. A relative
build.dockerfileorbuild.contextin a nested config resolves against the config file’s directory, not the repository root. - A
workspaceFolderthat differs from/workspaces/<repository-folder-name>does not move the mount unless the config also setsworkspaceMount. KeepworkspaceFolderunder the mount, or setworkspaceMount, or the agent’s working directory will not exist in the container. - A
codeStepruns in a worker-supplied managed runtime by default, which needs no devcontainer config. A code step that opts intoRuntime.devcontainer({ config })runs its runner script inside the selected container, so that alternate config must provide a JavaScript runtime (bunornode) and the pipeline-builder dependencies.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
boboddy work fails with “image not found” |
Base image name is wrong or private — check docker pull <image> manually |
| Agent can’t find the package manager | Add an onCreateCommand to install deps, or use a features entry |
| Container starts but step fails with missing binary | A system package or runtime tool is absent — add it via features or a custom Dockerfile |
Step fails with Devcontainer config "..." not found in the cloned repository |
The path in Runtime.devcontainer({ config }) does not exist on the branch the step starts from. Check the path and that the file is committed and pushed to that branch |
| Execution is slow | The onCreateCommand runs on every job; move stable deps into the image |