Documentation
¶
Overview ¶
Package currus provides a single, neutral API for the container lifecycle that auto-detects and drives Docker, Podman, or containerd through engine client APIs, never by shelling out to a CLI.
Architecture ¶
Currus follows the database/sql shape: a neutral interface on top, with pluggable engine drivers beneath. Engine selection is auto-detected by default (probing endpoints in priority order with a Ping validation), or explicit via WithEngine.
The neutral container model is explicitly Docker-like. Engines that do not natively implement it (notably containerd) are adapted to it. Where an engine cannot express part of the model, that part is surfaced via a capability interface and Caps rather than silently faked.
Quick start ¶
Zero-config: detect whatever engine is installed.
eng := currus.MustNew(ctx, currus.WithLogger(slog.Default()))
defer eng.Close()
if err := eng.PullImage(ctx, "docker.io/library/redis:7", currus.PullImageOpts{}); err != nil {
log.Fatal(err)
}
id, err := eng.CreateContainer(ctx, currus.ContainerSpec{
Image: "docker.io/library/redis:7",
Name: "cache",
})
if err != nil {
log.Fatal(err)
}
if err := eng.StartContainer(ctx, id); err != nil {
log.Fatal(err)
}
Explicit engine selection:
eng, err := currus.New(ctx, currus.WithEngine(currus.Podman))
Remote Docker over TLS:
eng, err := currus.New(ctx,
currus.WithEngine(currus.Docker),
currus.WithEndpoint(currus.Endpoint{
Host: "tcp://docker-host:2376",
TLS: &currus.TLSConfig{
CACert: caCertPEM,
Cert: certPEM,
Key: keyPEM,
},
}),
)
Engine interface ¶
Engine exposes the small core that every backend supports: identity, Ping, Close, and the universal container lifecycle (pull/create/start/ stop/remove/list). Non-universal features live behind optional capability interfaces discovered by type assertion:
if lg, ok := eng.(currus.Logger); ok {
rc, _ := lg.ContainerLogs(ctx, id, currus.ContainerLogsOpts{})
defer rc.Close()
io.Copy(os.Stdout, rc)
}
Network attachment ¶
Containers can join one or more networks at creation time by populating [ContainerSpec.Networks]. The first network is attached before the container starts; additional networks are connected immediately after create. Both Docker and Podman honor this field; containerd ignores it.
id, err := eng.CreateContainer(ctx, currus.ContainerSpec{
Image: "ghcr.io/example/sidecar:latest",
Name: "kind-sidecar",
Networks: []currus.NetworkAttachment{{Name: "kind"}},
})
The Networker capability also exposes [Networker.ConnectContainer] and [Networker.DisconnectContainer] for attaching and detaching after start.
Resolved endpoint ¶
The EndpointReporter capability exposes the URI the engine actually connected to. This is useful when deriving the host socket path for a bind-mount into a sidecar container:
sock := "/var/run/docker.sock"
if er, ok := eng.(currus.EndpointReporter); ok {
sock = strings.TrimPrefix(er.Endpoint().Host, "unix://")
}
Capability matrix ¶
The following table shows which capabilities each engine implements. A "—" means the engine does not implement the interface; type-asserting it yields ok == false.
Capability │ Docker │ Podman │ containerd ─────────────────┼────────┼────────┼─────────── Engine │ ✓ │ ✓ │ ✓ Logger │ ✓ │ ✓ │ — Execer │ ✓ │ ✓ │ — Inspector │ ✓ │ ✓ │ — Stater │ ✓ │ ✓ │ — Waiter │ ✓ │ ✓ │ — Eventer │ ✓ │ ✓ │ — Imager │ ✓ │ ✓ │ — Networker │ ✓ │ ✓ │ — Volumer │ ✓ │ ✓ │ — Copier │ ✓ │ ✓ │ — EndpointReporter │ ✓ │ ✓ │ ✓
Error handling ¶
Errors are normalized into a stable sentinel taxonomy (ErrNotFound, ErrAlreadyExists, ErrConflict, ErrNotImplemented, ErrUnsupported) usable with errors.Is / errors.As across every engine:
if errors.Is(err, currus.ErrNotFound) { ... }
Testing ¶
Swap the real engine for the in-memory fake from gopherly.dev/currus/currustest:
eng := currustest.New() // implements Engine + all capability interfaces
The gopherly.dev/currus/conformance package provides a shared behavioral test suite that verifies any Engine implementation. Driver maintainers can run it against both the in-memory fake and real daemons.
Platform support ¶
The Docker-API driver (serving Docker and Podman) is pure HTTP and builds on Linux, macOS, and Windows. The containerd driver is supported on Linux only (containerd is not available on macOS or Windows outside of a VM). Rootless Docker and rootless Podman are fully supported via auto-detection through the XDG_RUNTIME_DIR socket paths.
For more details, see https://pkg.go.dev/gopherly.dev/currus
Index ¶
- Variables
- type Caps
- type ConnectOpts
- type Container
- type ContainerID
- type ContainerInfo
- type ContainerLogsOpts
- type ContainerSpec
- type ContainerState
- type ContainerStats
- type Copier
- type CopyFromContainerOpts
- type CopyToContainerOpts
- type CreateNetworkOpts
- type CreateVolumeOpts
- type DisconnectOpts
- type Endpoint
- type EndpointReporter
- type Engine
- type EngineKind
- type Event
- type Eventer
- type ExecOpts
- type ExecResult
- type Execer
- type Image
- type Imager
- type Inspector
- type ListContainersOpts
- type ListImagesOpts
- type ListNetworksOpts
- type ListVolumesOpts
- type Logger
- type Mount
- type MountType
- type Network
- type NetworkAttachment
- type NetworkID
- type Networker
- type Option
- type Port
- type PullImageOpts
- type RemoveContainerOpts
- type RemoveImageOpts
- type RemoveNetworkOpts
- type RemoveVolumeOpts
- type Resources
- type RestartMode
- type RestartPolicy
- type Stater
- type StatsOpts
- type StopContainerOpts
- type TLSConfig
- type Volume
- type VolumeID
- type Volumer
- type WaitCondition
- type WaitContainerOpts
- type WaitResult
- type Waiter
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is returned when the requested resource does not exist. ErrNotFound = errors.New("not found") // ErrAlreadyExists is returned when a resource already exists and the // operation requires uniqueness. ErrAlreadyExists = errors.New("already exists") // ErrConflict is returned when an operation conflicts with the current // state of a resource. ErrConflict = errors.New("conflict") // ErrNotImplemented is returned by a driver that has not yet implemented // a particular method of a capability interface. ErrNotImplemented = errors.New("not implemented") // ErrUnsupported is returned when the underlying engine does not support // the requested operation at all. ErrUnsupported = errors.New("unsupported") // ErrNoEngine is returned by New when no reachable engine is found. ErrNoEngine = errors.New("no reachable container engine found") // ErrInvalidSpec is returned when a ContainerSpec or option field is // malformed or missing a required value (e.g. an empty Image field). ErrInvalidSpec = errors.New("invalid container spec") )
Sentinel errors returned by all Engine implementations. Use errors.Is to compare; all errors may be wrapped.
Functions ¶
This section is empty.
Types ¶
type Caps ¶
Caps holds informational, non-method-shaped engine traits.
Caps never mirrors a capability interface: method-shaped features (e.g. ContainerLogs) are discovered by type assertion against the capability interfaces (Logger, Execer, …). Caps holds only boolean or string descriptors of structural engine properties.
type ConnectOpts ¶ added in v0.2.0
type ConnectOpts struct {
// Aliases are optional extra DNS names for this container on the network.
Aliases []string
}
ConnectOpts controls ConnectContainer.
type Container ¶
type Container struct {
ID ContainerID
Name string
Image string
State string
Labels map[string]string
}
Container is a neutral representation of a running or stopped container.
type ContainerID ¶
type ContainerID string
ContainerID is the opaque identifier for a container returned by CreateContainer. Using a named type prevents accidental mix-ups with image refs, volume IDs, and other string identifiers.
type ContainerInfo ¶
type ContainerInfo struct {
ID ContainerID
Name string
Image string
ImageID string
Labels map[string]string
State ContainerState
Command []string
Env []string
WorkingDir string
Mounts []Mount
}
ContainerInfo holds the full details of a container as returned by Inspect.
type ContainerLogsOpts ¶
ContainerLogsOpts controls what the ContainerLogs stream returns.
type ContainerSpec ¶
type ContainerSpec struct {
// Image is the container image reference (e.g. "docker.io/library/alpine:3.20").
Image string
// Name is an optional human-readable name assigned to the container.
Name string
// Command overrides the image ENTRYPOINT. Leave nil to use the image default.
Command []string
// Args are the arguments passed to Command, or to the image ENTRYPOINT when
// Command is nil.
Args []string
// Env holds environment variables in KEY=VALUE form.
Env []string
// WorkingDir sets the working directory for the container process. Leave
// empty to use the image default.
WorkingDir string
// Labels are arbitrary key/value metadata attached to the container.
Labels map[string]string
// Mounts lists filesystem mounts attached to the container.
Mounts []Mount
// Ports lists port bindings between the container and the host.
Ports []Port
// Resources constrains the CPU and memory available to the container.
Resources Resources
// Restart is the restart policy applied when the container exits.
Restart RestartPolicy
// Networks lists the networks to join at creation time, in order.
Networks []NetworkAttachment
}
ContainerSpec is the desired state of a container.
It is built from the common core of OCI runtime spec, Docker container config, and Podman specgen. Growth is additive within the nested sub-structs so callers that set only a few fields are unaffected by new additions.
func (ContainerSpec) Validate ¶ added in v0.3.0
func (s ContainerSpec) Validate() error
Validate reports whether the spec is well-formed, returning a wrapped ErrInvalidSpec when a required field is missing. Drivers call it before create; callers may also use it to check a spec ahead of time.
type ContainerState ¶
type ContainerState struct {
Running bool
Paused bool
Restarting bool
ExitCode int
Error string
StartedAt time.Time
FinishedAt time.Time
}
ContainerState holds the runtime state of an inspected container.
type ContainerStats ¶
type ContainerStats struct {
CPUPercent float64
MemoryUsage uint64
MemoryLimit uint64
NetworkInput uint64
NetworkOutput uint64
}
ContainerStats holds point-in-time resource usage statistics for a container.
type Copier ¶
type Copier interface {
// CopyToContainer copies a TAR-archived content stream into the container
// at DestPath.
CopyToContainer(ctx context.Context, id ContainerID, o CopyToContainerOpts) error
// CopyFromContainer copies a path from the container's filesystem and
// returns a TAR-archived stream of the content.
CopyFromContainer(ctx context.Context, id ContainerID, o CopyFromContainerOpts) (io.ReadCloser, error)
}
Copier is the capability interface for copying files into and out of a container. Implemented by Docker and Podman; not available on containerd.
type CopyFromContainerOpts ¶
type CopyFromContainerOpts struct {
// SrcPath is the path inside the container to copy content from.
SrcPath string
}
CopyFromContainerOpts controls CopyFromContainer.
type CopyToContainerOpts ¶
type CopyToContainerOpts struct {
// DestPath is the path inside the container to copy the content to.
DestPath string
// Content is a TAR archive reader to copy into the container.
Content io.Reader
}
CopyToContainerOpts controls CopyToContainer.
type CreateNetworkOpts ¶
type CreateNetworkOpts struct {
Driver string
}
CreateNetworkOpts controls CreateNetwork.
type CreateVolumeOpts ¶
type CreateVolumeOpts struct {
Driver string
}
CreateVolumeOpts controls CreateVolume.
type DisconnectOpts ¶ added in v0.2.0
type DisconnectOpts struct {
// Force disconnects the container even if it is running.
Force bool
}
DisconnectOpts controls DisconnectContainer.
type Endpoint ¶
type Endpoint struct {
// Host is the endpoint URI (see above for supported schemes).
Host string
// Namespace is the containerd namespace to use. Ignored by Docker and
// Podman drivers. Empty defaults to "default".
Namespace string
// TLS carries the TLS configuration for tcp:// connections. Nil means
// no client-side TLS material (the server still uses TLS if configured).
// Ignored by unix:// and ssh:// schemes.
TLS *TLSConfig
}
Endpoint describes the connection details for an engine daemon.
The following URI schemes are supported:
- unix:///var/run/docker.sock — local Docker socket (default)
- tcp://host:2376 — remote Docker over TCP (use TLSConfig for mutual TLS)
- ssh://user@host — remote Podman or Docker over SSH (system SSH agent)
- npipe:////./pipe/docker_engine — Windows named pipe
For containerd, Host accepts either a raw socket path (/run/containerd/containerd.sock) or a unix:// URI; both forms work. The tcp://, ssh://, and npipe:// schemes are not supported by containerd.
type EndpointReporter ¶ added in v0.2.0
type EndpointReporter interface {
Endpoint() Endpoint
}
EndpointReporter reports the endpoint the engine actually connected to. Callers can use the resolved Host URI to, for example, derive the host socket path when bind-mounting it into a sidecar container:
sock := "/var/run/docker.sock"
if er, ok := eng.(currus.EndpointReporter); ok {
sock = strings.TrimPrefix(er.Endpoint().Host, "unix://")
}
Implemented by Docker, Podman, and containerd drivers.
type Engine ¶
type Engine interface {
Kind() EngineKind
Capabilities() Caps
Ping(ctx context.Context) error
Close() error
PullImage(ctx context.Context, ref string, o PullImageOpts) error
CreateContainer(ctx context.Context, spec ContainerSpec) (ContainerID, error)
StartContainer(ctx context.Context, id ContainerID) error
StopContainer(ctx context.Context, id ContainerID, o StopContainerOpts) error
RemoveContainer(ctx context.Context, id ContainerID, o RemoveContainerOpts) error
ListContainers(ctx context.Context, o ListContainersOpts) ([]Container, error)
}
Engine is the small core interface every backend implements.
It carries only what every backend supports: identity, Ping, Close, and the universal container lifecycle (pull / create / start / stop / remove / list). Non-universal features live behind optional capability interfaces discovered by type assertion:
if lg, ok := eng.(currus.Logger); ok { ... }
The full set of optional capability interfaces is: Logger, Execer, Inspector, Stater, Waiter, Eventer, Imager, Networker, Volumer, Copier, EndpointReporter. See [doc.go] or the README capability matrix for per-engine support.
This keeps Engine from growing into a 50-method interface and makes missing features a typed ok==false rather than a runtime error.
func MustNew ¶
MustNew is like New but panics if no engine is available.
Intended for package-level initialization or program startup where an unavailable engine is a programmer error, not a recoverable condition. Do not use MustNew in package code that might be called from tests or in contexts where the environment is not controlled.
func New ¶
New creates an Engine by detecting or constructing the appropriate backend.
With no options, New probes endpoints in priority order until a reachable engine responds to Ping:
- CONTAINER_ENGINE env var override
- Docker socket (unix:///var/run/docker.sock)
- Podman rootless socket (~/.local/share/containers/podman/machine/ podman.sock or $XDG_RUNTIME_DIR/podman/podman.sock)
- Podman rootful socket (unix:///run/podman/podman.sock)
- containerd socket (unix:///run/containerd/containerd.sock)
Use WithEngine to skip detection and select a backend explicitly. Use WithEndpoint to override the default socket path.
type EngineKind ¶
type EngineKind string
EngineKind is the typed selector for a specific container engine backend.
const ( // Docker selects the Docker-API driver backed by the moby client. Docker EngineKind = "docker" // Podman selects the Docker-API driver pointed at the Podman socket. // Podman speaks the Docker Engine API; the same moby driver is used. Podman EngineKind = "podman" // Containerd selects the containerd v2 client driver. Containerd EngineKind = "containerd" )
type Eventer ¶
Eventer is the capability interface for subscribing to engine events. Implemented by Docker and Podman; not available on containerd.
type ExecOpts ¶
type ExecOpts struct {
Cmd []string
Env []string
WorkingDir string
AttachStdout bool
AttachStderr bool
}
ExecOpts configures an Exec call.
type ExecResult ¶
ExecResult holds the outcome of an Exec call.
type Execer ¶
type Execer interface {
Exec(ctx context.Context, id ContainerID, o ExecOpts) (ExecResult, error)
}
Execer is the capability interface for running commands inside a container.
type Imager ¶
type Imager interface {
ListImages(ctx context.Context, o ListImagesOpts) ([]Image, error)
RemoveImage(ctx context.Context, ref string, o RemoveImageOpts) error
TagImage(ctx context.Context, src, dst string) error
}
Imager is the capability interface for image management beyond PullImage.
type Inspector ¶
type Inspector interface {
Inspect(ctx context.Context, id ContainerID) (ContainerInfo, error)
}
Inspector is the capability interface for reading full container metadata. Implemented by Docker and Podman; not available on containerd.
Usage:
if ins, ok := eng.(currus.Inspector); ok {
info, _ := ins.Inspect(ctx, id)
}
type ListContainersOpts ¶
type ListContainersOpts struct {
All bool
}
ListContainersOpts filters the ListContainers result.
type ListImagesOpts ¶
type ListImagesOpts struct {
All bool
}
ListImagesOpts filters the ListImages result.
type ListNetworksOpts ¶
type ListNetworksOpts struct{}
ListNetworksOpts filters the ListNetworks result.
type ListVolumesOpts ¶
type ListVolumesOpts struct{}
ListVolumesOpts filters the ListVolumes result.
type Logger ¶
type Logger interface {
ContainerLogs(ctx context.Context, id ContainerID, o ContainerLogsOpts) (io.ReadCloser, error)
}
Logger is the capability interface for reading container log streams.
This is a capability — not part of the core Engine — because containerd has no native container logs. Callers must check the assertion:
if lg, ok := eng.(currus.Logger); ok {
rc, _ := lg.ContainerLogs(ctx, id, currus.ContainerLogsOpts{})
defer rc.Close()
io.Copy(os.Stdout, rc)
}
The returned io.ReadCloser always produces plain, demultiplexed output. Implementations are responsible for stripping any transport framing (e.g. the Docker 8-byte stream headers) before returning the reader.
type Mount ¶
type Mount struct {
// Type identifies the kind of mount (bind, volume, or tmpfs).
Type MountType
// Source is the host path for bind mounts or the volume name for named volumes.
Source string
// Target is the absolute path inside the container where the mount is attached.
Target string
// ReadOnly makes the mount read-only when true.
ReadOnly bool
}
Mount describes a single filesystem mount attached to a container.
type NetworkAttachment ¶ added in v0.2.0
type NetworkAttachment struct {
// Name is the network name or ID to join (e.g. "kind").
Name string
// Aliases are optional extra DNS names for this container on the network.
Aliases []string
}
NetworkAttachment describes a single network a container should join.
type Networker ¶
type Networker interface {
CreateNetwork(ctx context.Context, name string, o CreateNetworkOpts) (NetworkID, error)
ListNetworks(ctx context.Context, o ListNetworksOpts) ([]Network, error)
RemoveNetwork(ctx context.Context, id NetworkID, o RemoveNetworkOpts) error
// ConnectContainer connects a running container to a network.
ConnectContainer(ctx context.Context, net NetworkID, id ContainerID, o ConnectOpts) error
// DisconnectContainer disconnects a container from a network.
DisconnectContainer(ctx context.Context, net NetworkID, id ContainerID, o DisconnectOpts) error
}
Networker is the capability interface for managing container networks. Implemented by Docker and Podman; not available on containerd.
type Option ¶
type Option func(*engineConfig)
Option is a functional option for New and MustNew.
func WithEndpoint ¶
WithEndpoint sets the connection endpoint for the engine daemon. See Endpoint for supported URI schemes.
func WithEngine ¶
func WithEngine(kind EngineKind) Option
WithEngine selects a specific engine backend instead of auto-detecting. Pass currus.Docker, currus.Podman, or currus.Containerd.
func WithLogger ¶
WithLogger attaches a *slog.Logger to the engine.
func WithTracerProvider ¶
func WithTracerProvider(tp trace.TracerProvider) Option
WithTracerProvider attaches an OpenTelemetry TracerProvider. Each engine operation is wrapped in a span named "currus.<method>".
type Port ¶
type Port struct {
// Container is the port number exposed by the container.
Container uint16
// Host is the host port to bind to. Zero lets the engine pick a free port.
Host uint16
// Protocol is the transport protocol ("tcp" or "udp"). Defaults to "tcp"
// when empty.
Protocol string
}
Port describes a single port binding between container and host.
type PullImageOpts ¶
type PullImageOpts struct {
Platform string
}
PullImageOpts controls the PullImage operation.
type RemoveContainerOpts ¶
RemoveContainerOpts controls the RemoveContainer operation.
type RemoveImageOpts ¶
type RemoveImageOpts struct {
Force bool
}
RemoveImageOpts controls the RemoveImage behavior.
type RemoveNetworkOpts ¶
type RemoveNetworkOpts struct {
Force bool
}
RemoveNetworkOpts controls RemoveNetwork.
type RemoveVolumeOpts ¶
type RemoveVolumeOpts struct {
Force bool
}
RemoveVolumeOpts controls RemoveVolume.
type Resources ¶
type Resources struct {
// NanoCPUs is the CPU limit expressed in units of 1e-9 CPUs (e.g. 500_000_000
// for half a CPU). Zero means no limit.
NanoCPUs int64
// MemoryBytes is the memory limit in bytes. Zero means no limit.
MemoryBytes int64
}
Resources constrains the CPU and memory available to a container.
type RestartMode ¶ added in v0.3.0
type RestartMode string
RestartMode describes the restart strategy for a container.
const ( // RestartNever disables automatic restarts. This is the default. RestartNever RestartMode = "" // RestartAlways restarts the container regardless of exit status. RestartAlways RestartMode = "always" // RestartOnFailure restarts the container only when it exits with a // non-zero exit code. Use MaxRetries to cap the number of attempts. RestartOnFailure RestartMode = "on-failure" // RestartUnlessStopped restarts the container unless it was explicitly // stopped by the user. RestartUnlessStopped RestartMode = "unless-stopped" )
type RestartPolicy ¶
type RestartPolicy struct {
// Mode is the restart strategy. Empty (RestartNever) means no restart.
Mode RestartMode
// MaxRetries is the maximum number of restart attempts. Only meaningful
// with RestartOnFailure; ignored otherwise.
MaxRetries int
}
RestartPolicy describes when and how many times the engine should restart a container after it exits.
type Stater ¶
type Stater interface {
Stats(ctx context.Context, id ContainerID, o StatsOpts) (ContainerStats, error)
}
Stater is the capability interface for reading point-in-time resource usage. Implemented by Docker and Podman; not available on containerd.
type StatsOpts ¶
type StatsOpts struct{}
StatsOpts controls the Stats call (reserved for future extension).
type StopContainerOpts ¶
StopContainerOpts controls the StopContainer operation.
type Volumer ¶
type Volumer interface {
CreateVolume(ctx context.Context, name string, o CreateVolumeOpts) (VolumeID, error)
ListVolumes(ctx context.Context, o ListVolumesOpts) ([]Volume, error)
RemoveVolume(ctx context.Context, id VolumeID, o RemoveVolumeOpts) error
}
Volumer is the capability interface for managing named volumes. Implemented by Docker and Podman; not available on containerd.
type WaitCondition ¶ added in v0.3.0
type WaitCondition string
WaitCondition describes the event to wait for in a WaitContainer call.
const ( // WaitConditionNotRunning waits until the container is no longer running. // This is the default when Condition is empty. WaitConditionNotRunning WaitCondition = "not-running" // WaitConditionNextExit waits for the container's next exit event. WaitConditionNextExit WaitCondition = "next-exit" // WaitConditionRemoved waits until the container has been removed. WaitConditionRemoved WaitCondition = "removed" )
type WaitContainerOpts ¶
type WaitContainerOpts struct {
// Condition is the event to wait for. Empty defaults to WaitConditionNotRunning.
Condition WaitCondition
}
WaitContainerOpts controls WaitContainer.
type WaitResult ¶
WaitResult is the outcome of a WaitContainer call.
type Waiter ¶
type Waiter interface {
WaitContainer(ctx context.Context, id ContainerID, o WaitContainerOpts) (<-chan WaitResult, error)
}
Waiter is the capability interface for blocking until a container exits. Implemented by Docker and Podman; not available on containerd.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package conformance provides the shared behavioral test suite that verifies any currus.Engine implementation against the neutral contract defined by the currus package.
|
Package conformance provides the shared behavioral test suite that verifies any currus.Engine implementation against the neutral contract defined by the currus package. |
|
Package currustest provides an in-memory fake currus.Engine for use in tests.
|
Package currustest provides an in-memory fake currus.Engine for use in tests. |
|
examples
|
|
|
basic
command
Command basic demonstrates the core container lifecycle: auto-detect the engine, pull an image, create and start a container, read its logs, then clean up.
|
Command basic demonstrates the core container lifecycle: auto-detect the engine, pull an image, create and start a container, read its logs, then clean up. |