Help us shape the future of Go! Take the 2026 Go Developer Survey

docker

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package docker brings a devcontainer up against a Docker Engine by talking to the engine API directly — no `docker` CLI shell-out, so the package is safely embeddable in services and operators. Podman is not supported.

Compatibility with the upstream devcontainer CLI is intentional. Container labels (devcontainer.local_folder, devcontainer.config_file), marker file paths, and lifecycle command semantics mirror microsoft/devcontainers-cli so a container created here can be picked up by upstream tooling (and vice versa).

Embedding

Construct a Host with a *client.Client you already manage:

cli, _ := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
h := docker.New(cli, docker.Options{Logger: log.Printf})
res, err := h.Up(ctx, docker.UpRequest{...})

The package never reads $DOCKER_HOST, never writes to stdout/stderr unless explicitly given writers in Options or per-call request, never consults global state. That makes it safe to drop into an HTTP handler, a controller-runtime reconcile loop, or a test.

relationships:

references: [../../../internal/dockerimage]

relationships:

references: [up, spawn, metadata]

Index

Constants

View Source
const (
	LabelLocalFolder = "devcontainer.local_folder"
	LabelConfigFile  = "devcontainer.config_file"
)

Labels written by Up so containers can be rediscovered later. Names match upstream's src/spec-node/singleContainer.ts.

Variables

View Source
var ErrContainerNotFound = errors.New("docker host: container not found")

ErrContainerNotFound is returned by Inspect/Stop/Remove when no container matches the supplied selector.

Functions

This section is empty.

Types

type ContainerInfo

type ContainerInfo struct {
	ID         string
	Image      string
	Running    bool
	StartedAt  string
	CreatedAt  string
	Labels     map[string]string
	WorkingDir string
}

ContainerInfo is a small projection of docker's InspectResponse. Returned by Inspect; sufficient for the most common embedder needs without exposing the full SDK type at the API boundary.

type ExecRequest

type ExecRequest struct {
	// TerminalSize sets the initial PTY size [rows, columns] when TTY is true.
	TerminalSize *[2]uint
	ContainerID  string
	// Cmd is the argv passed to the container. Caller is responsible
	// for shell wrapping (string lifecycle commands wrap with /bin/sh -c
	// before reaching here).
	Cmd []string
	// User is "<uid>[:<gid>]" or a username — same as docker exec -u.
	User string
	// WorkDir is the cwd inside the container.
	WorkDir string
	// Env is appended to the container's environment for this exec.
	Env map[string]string
	// TTY allocates a pseudo-terminal. Required for interactive shells.
	TTY bool
	// Stdin / Stdout / Stderr are forwarded to/from the exec session.
	// Any of them may be nil; nil stdin sends EOF immediately, nil
	// stdout/stderr discards that stream.
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

ExecRequest captures the inputs to a single docker exec call.

type ExecResult

type ExecResult struct {
	ExitCode int
	// Stdout / Stderr capture output IF the request did not provide
	// writers — captured-mode is the default for "did the marker check
	// succeed?" probes. When the request supplies writers, these are
	// empty.
	Stdout []byte
	Stderr []byte
}

ExecResult reports the outcome of a docker exec.

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host is the per-daemon connection. Safe for concurrent use.

func New

func New(c *dockerclient.Client, opts Options) *Host

New wraps an existing docker client. The client lifetime is the caller's responsibility; New does not Close it.

func (*Host) Client

func (h *Host) Client() *dockerclient.Client

Client returns the underlying docker client. Provided so embedders can issue ad-hoc calls (e.g. an operator that wants to attach a custom network) without re-dialing the daemon.

func (*Host) Exec

func (h *Host) Exec(ctx context.Context, req ExecRequest) (*ExecResult, error)

Exec runs a command inside an existing container and waits for it to finish. Returns the exit code and any captured output.

When Stdin/Stdout/Stderr are nil, output is captured into the returned ExecResult. When supplied, output streams to the caller's writers and the returned ExecResult buffers are empty.

func (*Host) Execer

func (h *Host) Execer(containerID string) runner.Execer

Execer returns a runner.Execer that runs commands inside the given container ID. Embedders that drive lifecycle commands themselves (rather than calling Up) can hand the result to runner.Run.

func (*Host) FindByLabels

func (h *Host) FindByLabels(ctx context.Context, labels map[string]string) (string, error)

FindByLabels returns the ID of the newest container that carries every label, as upstream selects a container by its --id-label set. It returns ErrContainerNotFound when none does.

func (*Host) Inspect

func (h *Host) Inspect(ctx context.Context, localWorkspace string) (*ContainerInfo, error)

Inspect returns the current state of the devcontainer for the given local workspace path. Returns ErrContainerNotFound if no container matches the workspace label.

func (*Host) InspectByID

func (h *Host) InspectByID(ctx context.Context, id string) (*ContainerInfo, error)

InspectByID is Inspect when you already have a container ID (from e.g. Up's UpResult).

func (*Host) Remove

func (h *Host) Remove(ctx context.Context, localWorkspace string) error

Remove stops (if running) and removes the devcontainer for a given workspace, including its anonymous volumes.

func (*Host) RemoveByID

func (h *Host) RemoveByID(ctx context.Context, id string) error

RemoveByID is Remove when you already have a container ID.

func (*Host) Stop

func (h *Host) Stop(ctx context.Context, localWorkspace string) error

Stop sends SIGTERM and waits up to Options.StopTimeout for the container to exit. Returns nil even if the container was already stopped, so it's safe to call as a teardown step regardless of state.

func (*Host) StopByID

func (h *Host) StopByID(ctx context.Context, id string) error

StopByID stops a container by ID.

func (*Host) Up

func (h *Host) Up(ctx context.Context, req UpRequest) (*UpResult, error)

Up brings the devcontainer up: find or create the container, start it if stopped, then run the appropriate lifecycle phases gated by marker files. Idempotent: calling Up twice on the same workspace reuses the existing container and only re-runs the phases whose markers indicate they need to.

type Options

type Options struct {
	// TerminalSize is the optional lifecycle PTY size [rows, columns].
	TerminalSize *[2]uint
	// Logger receives informational messages. If nil, messages are
	// discarded — appropriate for a service or operator that handles
	// its own structured logging via context.
	Logger func(format string, args ...any)
	// Stdout / Stderr default to nil (silent). Lifecycle command output
	// is written to these unless the per-call request overrides.
	Stdout io.Writer
	Stderr io.Writer
	// StopTimeout bounds how long Stop waits for graceful shutdown
	// before SIGKILL. Defaults to 10s.
	StopTimeout time.Duration
	// RegistryAuth supplies the registry credentials sent with image
	// pulls. When nil, pulls are anonymous. A *registryauth.Store from
	// registryauth.FromEnvironment reads the docker CLI's configuration.
	RegistryAuth RegistryAuth
}

Options tunes a Host. All fields are optional.

type RegistryAuth

type RegistryAuth interface {
	EngineAuth(ref string) (string, error)
}

RegistryAuth returns the Engine API X-Registry-Auth value for an image reference, or "" for an anonymous request.

type RemoteUserUIDUpdate

type RemoteUserUIDUpdate struct {
	// HostUID / HostGID are the IDs the in-image remoteUser should be
	// remapped to. Typically the embedder calls os.Getuid / os.Getgid
	// on linux/macOS; the package never does so itself.
	HostUID int
	HostGID int
	// DefaultOff requires an explicit updateRemoteUserUID: true in the spec.
	DefaultOff bool
}

RemoteUserUIDUpdate, when supplied on UpRequest, asks the host to rewrite the configured remoteUser's UID/GID inside the running container so files created in bind-mounted host paths land with the invoking user's ownership instead of the image-baked user's. This is the embeddable equivalent of upstream's `updateRemoteUserUID` behavior — except we patch /etc/passwd + /etc/group at runtime via `docker exec` instead of rebuilding the image, which keeps the package shell-out-free.

The caller controls host-platform eligibility by omitting this request. The CLI enables it by default on Linux; macOS requires an explicit --update-remote-user-uid-default on flag.

The update is skipped — without error — when:

  • the spec sets `updateRemoteUserUID: false`,
  • DefaultOff is set and the spec does not explicitly enable remapping,
  • the resolved remoteUser is "root" or a numeric UID,
  • the in-image UID/GID already match HostUID/HostGID,
  • or the in-image /etc/passwd does not contain remoteUser.

The rewrite is idempotent. It runs on every Up call so a host that later moves to a different UID still converges.

type UpRequest

type UpRequest struct {
	// ContainerDataFolder selects lifecycle and dotfiles marker storage.
	ContainerDataFolder string
	// ContainerSystemDataFolder selects the upstream system patch marker folder.
	ContainerSystemDataFolder string
	// GPUAvailability controls implicit hostRequirements.gpu requests. Empty means detect.
	GPUAvailability string
	// OmitConfigRemoteEnvFromMetadata omits the current config remoteEnv from new labels.
	OmitConfigRemoteEnvFromMetadata bool
	// Spec is the parsed devcontainer.json. Required.
	Spec *spec.DevContainer
	// SpecPath is the host-side path the spec was loaded from. Stored
	// in the devcontainer.config_file label so upstream tooling can
	// reverse-look-up the source file.
	SpecPath string
	// LocalWorkspace is the host-side workspace folder (absolute).
	// Required: it's the discovery key (devcontainer.local_folder
	// label) and the default bind-mount source.
	LocalWorkspace string
	// ContainerWorkspace is the path inside the container where the
	// workspace is mounted. When empty, falls back to spec
	// workspaceFolder, then "/workspaces/<basename>". Without a spec
	// workspaceFolder, a reused container's bind mounts take precedence.
	ContainerWorkspace string
	// Image is the image reference to run (use pkg/builder to produce
	// one). Up pulls it when the daemon does not have it.
	Image string
	// ImageBuilt means Image was built from this request's spec and features.
	// Its label already contains those contributions; append only runtime updates.
	ImageBuilt bool
	// SkipLifecycle suppresses all lifecycle commands for this invocation.
	SkipLifecycle bool
	// SkipPostAttach suppresses postAttachCommand for this invocation.
	SkipPostAttach bool
	// Dotfiles installs the user's dotfiles after postCreateCommand unless
	// SkipLifecycle is set.
	Dotfiles runner.Dotfiles
	// Lifecycle selects lifecycle stop points and prebuild behavior.
	Lifecycle runner.LifecycleControls
	// RemoteEnv is the caller's remote environment for lifecycle commands
	// and the dotfiles install (upstream --remote-env). The configuration's
	// remoteEnv wins for the same name, as upstream. It is not stored.
	RemoteEnv map[string]string
	// DefaultUserEnvProbe selects the shell probe when userEnvProbe is not configured.
	DefaultUserEnvProbe string
	// Secrets are secret environment variables for lifecycle commands and
	// the dotfiles install (upstream --secrets-file). They win over every
	// other layer and are never stored in container configuration, labels
	// or files.
	Secrets map[string]string

	// Features as resolved by pkg/features. Their containerEnv and
	// lifecycle commands are merged into the container.
	Features []features.Resolved
	// LocalEnv is the host environment used for ${localEnv:VAR}
	// substitution. Pass an empty map (not nil) for no host env.
	LocalEnv map[string]string
	// IDLabels, when non-empty, replace the workspace labels
	// (devcontainer.local_folder, devcontainer.config_file) as the
	// container's identity, as upstream --id-label does: Up reuses only a
	// container that carries all of them, sets exactly them on a new
	// container, and derives ${devcontainerId} from them.
	IDLabels map[string]string
	// WorkspaceMount, when non-nil, replaces the default workspace bind
	// mount (LocalWorkspace at the container workspace folder), for
	// example to mount the workspace's Git root. A spec workspaceMount
	// still wins.
	WorkspaceMount *mount.Mount
	// Mounts are added after the spec's and features' mounts, for example
	// a CLI --mount or a Git worktree's common folder. Up adds no host
	// path that is not in the spec or here.
	Mounts []mount.Mount
	// RemoteUserUIDUpdate, when non-nil, asks the host to remap the
	// container's remoteUser to HostUID/HostGID after the container
	// starts so bind-mounted files land with host-side ownership. The
	// spec's `updateRemoteUserUID: false` overrides and disables the
	// remap regardless. The package never reads os.Getuid itself —
	// embedders pass the IDs they want applied.
	RemoteUserUIDUpdate *RemoteUserUIDUpdate
	// contains filtered or unexported fields
}

UpRequest captures everything Up needs. The caller is responsible for: building/tagging the image (use pkg/builder), resolving feature metadata, and passing the host environment in for variable substitution. Up does not read os.Environ() itself — that's the CLI's responsibility, kept out of the package so a service or operator can supply a curated environment.

type UpResult

type UpResult struct {
	ContainerID     string
	CreatedThisCall bool
	StartedThisCall bool
	// Phases lists the lifecycle phases that actually ran (markers may
	// have skipped some).
	PhasesRun []lifecycle.Hook
}

UpResult reports what happened. Fields with a "thisCall" flag denote state changes attributable to *this* Up invocation, so a caller can log "created+started" vs "reused+lifecycle-only" cleanly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL