Dev Containers skill for Codex

Clone, open, tests pass, same as CI. Codex builds dev containers that match CI tool versions, install from lockfiles in onCreate, run real services through Compose, and keep dependency folders in volumes for speed. One idempotent setup script feeds the dev container, CI and the Codex cloud environment, so tests that pass locally also pass in agent tasks where network may be off after setup.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- devcontainers

Installs to ~/.agents/skills/devcontainers/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $devcontainers, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Creates and maintains .devcontainer/devcontainer.json setups - image or Dockerfile builds, Dev Container Features, Docker Compose multi-service environments, lifecycle hooks (onCreate, postCreate, postStart), forwarded ports, non-root users, mounts and caches, prebuilt images, the devcontainer CLI for CI, and keeping one setup script shared with Codex cloud environments so local, CI and agent runs match. Use when standardizing a dev environment, onboarding takes too long, "works on my machine" bugs appear, or Codex cloud tasks fail because tools differ from local.

Use it when

  • A repository needs a reproducible, one-step development environment
  • Onboarding takes hours or tool versions drift between machines
  • Codex cloud or CI runs behave differently from local development
  • A project needs databases or other services available while developing

Use something else when

How to check the result

devcontainer up succeeds from a clean clone, the AGENTS.md test command passes via devcontainer exec, and a Codex cloud task runs the same command successfully.

The mistake it prevents

Installing dependencies during tests instead of in the setup step, which breaks as soon as the agent or CI runs without network access.

~/.agents/skills/devcontainers/SKILL.md

--- name: devcontainers description: Creates and maintains .devcontainer/devcontainer.json setups - image or Dockerfile builds, Dev Container Features, Docker Compose multi-service… license: MIT ---

A good dev container opens to a working environment in one step: toolchain versions match CI, dependencies install from lockfiles, services the app needs are running, and tests pass with the documented command. The same setup script feeds the dev container, CI and Codex cloud, so a task that passes in one passes in the others.

Inspect first

  1. Read AGENTS.md, README, .tool-versions, .nvmrc, .python-version, rust-toolchain.toml, go.mod and CI workflows: these define the versions to match.
  2. Look for an existing .devcontainer/ (or .devcontainer.json at the root) and docker-compose*.yml.
  3. List services the app needs locally (Postgres, Redis, a queue) and their versions in production.
  4. Check the host: Docker Desktop, Podman or Colima; Apple silicon means images must have arm64 variants or builds slow down under emulation.

Layout

.devcontainer/
├── devcontainer.json
├── Dockerfile            # only when Features are not enough
└── compose.yaml          # only when services are needed
scripts/
└── setup.sh              # shared by devcontainer, CI and Codex cloud

Single-container baseline

// .devcontainer/devcontainer.json
{
  "name": "api",
  "image": "mcr.microsoft.com/devcontainers/base:bookworm",
  "features": {
    "ghcr.io/devcontainers/features/node:1": { "version": "22" },
    "ghcr.io/devcontainers/features/python:1": { "version": "3.13" },
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "remoteUser": "vscode",
  "containerEnv": { "PIP_DISABLE_PIP_VERSION_CHECK": "1" },
  "mounts": [
    "source=api-node-modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
  ],
  "onCreateCommand": "bash scripts/setup.sh",
  "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
  "forwardPorts": [3000],
  "portsAttributes": { "3000": { "label": "web", "onAutoForward": "notify" } },
  "customizations": {
    "vscode": {
      "extensions": ["dbaeumer.vscode-eslint", "ms-python.python"],
      "settings": { "editor.formatOnSave": true }
    }
  }
}
  • Pin Feature major versions (:1) and tool versions; latest drifts away from CI.
  • Put node_modules (or .venv, target/) in a named volume on macOS and Windows; bind mounts of large dependency trees are slow.
  • The vscode user in the official base images matches host UID on Linux through updateRemoteUserUID (default true), avoiding root-owned files in the repo.

Lifecycle hooks

HookRunsUse for
initializeCommandOn the host, before buildCreating .env from example; avoid heavy work
onCreateCommandOnce, when container is created (also during prebuilds)Install dependencies
updateContentCommandAfter create and when content updates in prebuildsRefresh deps after lockfile change
postCreateCommandOnce, after create, with user secrets availableSteps needing user credentials
postStartCommandEvery startStart background services, fix permissions
postAttachCommandEvery editor attachRarely needed

Keep hooks idempotent; they rerun on rebuild.

Multi-service with Compose

{
  "name": "api",
  "dockerComposeFile": "compose.yaml",
  "service": "app",
  "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
  "shutdownAction": "stopCompose",
  "onCreateCommand": "bash scripts/setup.sh"
}
# .devcontainer/compose.yaml
services:
  app:
    build: { context: .., dockerfile: .devcontainer/Dockerfile }
    volumes: ["..:/workspaces/api:cached"]
    command: sleep infinity
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/api_dev
  db:
    image: postgres:17
    environment: { POSTGRES_USER: dev, POSTGRES_PASSWORD: dev, POSTGRES_DB: api_dev }
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s

Match the database major version to production. The app reaches services by service name (db), not localhost.

The shared setup script

#!/usr/bin/env bash
# scripts/setup.sh - one source of truth for dev container, CI and Codex cloud
set -euo pipefail
corepack enable
npm ci
python -m pip install --upgrade uv
uv sync --frozen
  • Make it idempotent, non-interactive and offline-tolerant after the first run.
  • Call it from onCreateCommand, from CI before tests, and paste bash scripts/setup.sh into the Codex cloud environment’s setup script field.

Codex cloud parity

  • Codex cloud runs tasks in its own container (based on the codex-universal image) and does not read devcontainer.json. Parity comes from sharing the setup script and pinning the same language versions in the environment settings.
  • Codex cloud runs the setup script with network access; the agent phase may have network off. Everything tests need (dependencies, generated code, test fixtures) must be installed in setup, not fetched during tests.
  • Services such as Postgres are not started for you there; either start them in setup (if installable in the image), use lightweight fallbacks such as SQLite for unit tests, or mark integration tests to skip when the service is absent.
  • Record the test command in AGENTS.md so local Codex, the dev container and cloud tasks all run the same thing.

Prebuilt images and CI

npm install -g @devcontainers/cli            # needs network
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . npm test
devcontainer build --workspace-folder . --image-name ghcr.io/acme/api-devcontainer:latest
  • Prebuild the image in CI when setup takes more than a few minutes, then reference it with "image" (or cacheFrom) so contributors pull instead of build.
  • Run the test suite inside the dev container in CI at least weekly to catch drift.
  • Pushing images to a registry is outward-facing; confirm the registry and tag with the user.

Gotchas

  • Secrets in containerEnv or committed .env files end up in the repo; use remoteEnv with ${localEnv:VAR} or the editor’s secret store.
  • Features install in declaration order only when dependencies are declared; use overrideFeatureInstallOrder if one Feature needs another.
  • Docker-in-Docker Feature needs privileged mode; prefer docker-outside-of-docker for local use, and know that both widen the container’s access to the host.
  • Changing devcontainer.json requires “Rebuild Container”; changes to the setup script alone do not rerun onCreateCommand.

Verification

  • devcontainer up --workspace-folder . succeeds from a clean clone
  • devcontainer exec --workspace-folder . <test command from AGENTS.md> passes
  • Tool versions inside (node -v, python --version) match CI
  • A Codex cloud task in the same repo runs the test command successfully
  • Time from clone to green tests recorded (target under 10 minutes cold, under 1 warm)

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Dev Containers"
  short_description: "Clone, open, tests pass, same as CI"
  default_prompt: "$devcontainers Add a dev container for this repo with Node 22, Python 3.13 and Postgres"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem