A programmatic Go runtime for Dev Containers.
Embed the full devcontainer lifecycle — resolve, build, up, exec,
lifecycle phases, down — into your Go application without shelling out
to the Node @devcontainers/cli.
The reference @devcontainers/cli
is a Node binary. Embedding it in a Go service means a Node runtime
dependency, opaque failure modes (success exit codes with
outcome:error JSON on stdout), and CLI-flag-shaped APIs for every
interaction. This library is a clean Go implementation of the spec's
embedding-relevant subset, designed to be a drop-in replacement for
shelling out.
Alpha. API is stable enough for early integration but may change
between minor versions until v1.0.0. The events channel surface is
explicitly experimental.
The container backend is pluggable: anything implementing
runtime.Runtime can be wired in at engine construction time. The
documented backend is:
runtime/docker— Docker Engine overmoby/moby/client. Requires a reachable Docker daemon socket.
The engine, feature pipeline, lifecycle, and compose paths are written
against the runtime.Runtime interface rather than against Docker
directly.
Status of each Dev Containers spec
field/behavior the library covers. Legend: ✅ acted on ·
Sources
| Field | Status | Notes |
|---|---|---|
image |
✅ | Pull, run, exec |
build (dockerfile, context, args, target, cacheFrom) |
✅ | User Dockerfile + features layered atop |
dockerComposeFile, service, runServices |
✅ | compose-go parse + either shell-out to docker compose (default) or an in-process orchestrator (EngineOptions.ComposeBackend = ComposeBackendNative). The native orchestrator drives any Runtime implementing the compose primitives. |
Container config
| Field | Status | Notes |
|---|---|---|
workspaceFolder, workspaceMount |
✅ | |
mounts (bind / volume / tmpfs) |
✅ | |
containerEnv, remoteEnv |
✅ | |
containerUser, remoteUser |
✅ | |
updateRemoteUserUID |
✅ | Portable shell (Debian, Alpine/BusyBox) |
userEnvProbe (all four modes) |
✅ | |
runArgs, init, privileged, capAdd, securityOpt, overrideCommand |
✅ | |
shutdownAction |
✅ | Honored via Engine.Shutdown (none / stopContainer / stopCompose) |
Features
| Field | Status | Notes |
|---|---|---|
features (OCI / HTTPS / local) |
✅ | DAG ordering, options validation, content-addressed cache |
dependsOn / installsAfter / overrideFeatureInstallOrder |
✅ | |
| Pre-baked-image short-circuit | ✅ | devcontainer.metadata label read on Up |
devcontainer-lock.json |
❌ | #26 |
Lifecycle
| Phase | Status | Notes |
|---|---|---|
initializeCommand (host) |
✅ | Opt-in via UpOptions.RunInitializeCommand; requires EngineOptions.HostExecutor |
onCreateCommand, updateContentCommand, postCreateCommand |
✅ | Run-once idempotency markers |
postStartCommand, postAttachCommand |
✅ | Run-every-start / run-every-Up |
waitFor |
✅ | |
| Parallel command form (object → named commands run concurrently) | ✅ | |
secretsCommand |
✅ | Opt-in via UpOptions.RunSecretsCommand; requires EngineOptions.HostExecutor |
Substitution
| Variable | Status | Notes |
|---|---|---|
${localWorkspaceFolder[Basename]}, ${containerWorkspaceFolder[Basename]} |
✅ | |
${localEnv:VAR[:default]}, ${devcontainerId} |
✅ | |
${containerEnv:VAR[:default]} |
✅ | Two-pass: host context pre-create, re-applied per Exec via the workspace substituter |
Ports
| Field | Status | Notes |
|---|---|---|
forwardPorts |
Not actuated; #7 | |
portsAttributes, otherPortsAttributes |
Surfaced on ResolvedConfig; not enforced |
|
appPort (deprecated) |
✅ translated | Folded into forwardPorts (skipping container ports already declared); deprecation warning still emitted |
Other
| Behavior | Status | Notes |
|---|---|---|
customizations.<tool> pass-through |
✅ | map[string]json.RawMessage for callers |
hostRequirements |
Surfaced; not enforced | |
devcontainer.metadata label round-trip |
✅ | Written on build, read + merged on next Up |
| Unknown-field warnings (top-level + nested) | ✅ | Includes build, hostRequirements, gpu sub-objects |
| GPG / SSH agent forwarding | ❌ | #28 |
Out of scope (➖): Templates spec, dotfiles repos, IDE injection hooks, Kubernetes drivers.
go get github.com/crunchloop/devcontainerRequires:
- Go 1.25+
- Docker: daemon socket reachable; Docker Compose v2 plugin only when
running
dockerComposeFileprojects under the default shellout backend (skip the plugin if you opt intoComposeBackendNative).
package main
import (
"context"
"fmt"
"log"
devcontainer "github.com/crunchloop/devcontainer"
"github.com/crunchloop/devcontainer/runtime/docker"
)
func main() {
ctx := context.Background()
rt, err := docker.New(ctx, docker.Options{})
if err != nil {
log.Fatalf("docker: %v", err)
}
defer rt.Close()
eng, err := devcontainer.New(devcontainer.EngineOptions{Runtime: rt})
if err != nil {
log.Fatalf("engine: %v", err)
}
ws, err := eng.Up(ctx, devcontainer.UpOptions{
LocalWorkspaceFolder: "/path/to/your/project",
})
if err != nil {
log.Fatalf("up: %v", err)
}
defer eng.Down(ctx, ws, devcontainer.DownOptions{Remove: true})
res, err := eng.Exec(ctx, ws, devcontainer.ExecOptions{
Cmd: []string{"sh", "-c", "echo $USER"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println("user:", res.Stdout)
}Runnable end-to-end examples in examples/:
image-source/— minimal image-only devcontainerwith-features/— image + a local feature withcontainerEnvcompose/— multi-servicedockerComposeFile
The main entry points live in the root package:
type Engine struct { /* ... */ }
func New(opts EngineOptions) (*Engine, error)
func Resolve(ctx context.Context, opts ResolveOptions) (*ResolvedConfig, error)
func (*Engine) Up(ctx, UpOptions) (*Workspace, error)
func (*Engine) Attach(ctx, WorkspaceID) (*Workspace, error)
func (*Engine) Exec(ctx, *Workspace, ExecOptions) (ExecResult, error)
func (*Engine) ExecByID(ctx, WorkspaceID, ExecOptions) (ExecResult, error)
func (*Engine) RunLifecycle(ctx, *Workspace, LifecyclePhase) error
func (*Engine) Down(ctx, *Workspace, DownOptions) errorSub-packages:
config— devcontainer.json parsing, merging, host-context substitutionruntime— container backend abstraction (Runtime,ComposeRuntime, capabilities, network/volume/list primitives)runtime/docker— Docker Engine API implementation (usesmoby/moby/client)feature— feature resolution (OCI / HTTPS / local), DAG ordering, dockerfile generationcompose—dockerComposeFileparsing viacompose-spec/compose-go, plus a runtime-agnostic in-process orchestrator (Orchestrator,Plan, topological + health gating) used whenComposeBackendNativeis selected
make test # unit tests
make test-integration # integration tests against real Docker
make lint # golangci-lintThe integration suite (build tag integration) exercises real Docker:
pulls public images from GHCR, builds Dockerfiles, runs feature install
scripts, drives docker compose up/down. Skipped automatically if a
Docker daemon isn't reachable.
Architectural notes — the choices behind the public API and why
they were made — live under design/. Useful
when contributing or when embedding the library in a non-trivial
way; not required reading for normal use.
See CONTRIBUTING.md. Bug reports welcome via GitHub issues.