Skip to main content
Version: 0.1.0

C4 Component: Container (C3)

This diagram zooms into the Container Engine Abstraction container from the C2 Container Diagram to show the internal components of the internal/container package. It reveals how Invowk provides a unified interface over Docker and Podman CLIs, handles Podman-specific concerns (SELinux, rootless), and transparently adapts to sandboxed environments (Flatpak, Snap).

Diagram

Diagram: architecture/c4-component-container

Interface

ComponentTechnologyResponsibility
EngineGo interface10-method contract for container operations: Name, Available, Version, Build, Run, Remove, ImageExists, RemoveImage, BinaryPath, BuildRunArgs. All factory functions return this type.

Implementations

ComponentTechnologyResponsibility
BaseCLIEngineGo structShared base for CLI-based engines. Provides argument builders (BuildArgs, RunArgs, ExecArgs) and command execution (RunCommand, CreateCommand). Configured via functional options.
DockerEngineGo structEmbeds *BaseCLIEngine. Implements Engine. Locates the docker binary via exec.LookPath.
PodmanEngineGo structEmbeds *BaseCLIEngine. Implements Engine. Searches for podman or podman-remote (fallback for immutable distros). Injects SELinux volume labels and rootless user namespace handling.
SandboxAwareEngineGo struct (decorator)Wraps any Engine. Detects Flatpak/Snap sandboxes and prefixes commands with flatpak-spawn --host or snap run --shell. Passes through when no sandbox is detected (zero overhead).

Functional Options

TypePurpose
BaseCLIEngineOptionfunc(*BaseCLIEngine) option type for constructor customization.
ExecCommandFuncInjection point for exec.CommandContext. Allows tests to mock command execution.
VolumeFormatFuncTransforms volume strings. Podman uses this to append SELinux labels (:z) on Linux.
RunArgsTransformerPost-processes run arguments. Podman uses this to inject --userns=keep-id for rootless compatibility.
SELinuxCheckFuncDetermines whether SELinux labeling should be applied. Injected into Podman's volume formatter.

Request/Response Types

TypePurpose
BuildOptionsInput for Engine.Build(): context directory, Dockerfile, tag, build args, cache control.
RunOptionsInput for Engine.Run(): image, command, work dir, env, volumes, ports, TTY, interactive mode.
RunResultOutput from Engine.Run(): container ID, exit code, error.
VolumeMountStructured volume mount specification with SELinux label support.
PortMappingStructured port mapping with protocol support.

Factory Functions

FunctionBehavior
NewEngineCreates the preferred engine (Docker or Podman) with automatic fallback. Wraps with SandboxAwareEngine.
AutoDetectEngineTries Podman first (rootless-friendly default), then Docker. Wraps with SandboxAwareEngine.

External Dependencies

DependencyPackageUsage
platform.DetectSandbox()pkg/platformReturns sandbox type (None, Flatpak, Snap) used by SandboxAwareEngine.
issue.NewErrorContext()internal/issueCreates actionable errors with operation context and user-facing suggestions.
os/execstdlibUnderlying CLI execution. exec.LookPath finds engine binaries; exec.CommandContext runs them.

Key Patterns

Composition via Embedding

DockerEngine and PodmanEngine both embed *BaseCLIEngine. The base provides all argument building and command execution, while concrete engines only implement Engine interface methods and engine-specific construction. This avoids code duplication without requiring inheritance.

Decorator Pattern

SandboxAwareEngine wraps any Engine to handle Flatpak/Snap sandboxes. It intercepts every method to optionally prefix CLI invocations with the sandbox's host spawn mechanism. When no sandbox is detected, NewSandboxAwareEngine returns the unwrapped engine directly, adding zero overhead.

Functional Options

BaseCLIEngineOption follows the Dave Cheney functional options pattern. The constructor sets sensible defaults (real exec.CommandContext, identity formatters), and options override them:

  • Testing: Inject a mock ExecCommandFunc to verify argument construction without running containers.
  • Engine-specific behavior: Podman's constructor prepends WithVolumeFormatter (SELinux) and WithRunArgsTransformer (rootless userns) before user-supplied options.

Auto-Detection with Fallback

Both NewEngine and AutoDetectEngine follow a try-preferred-then-fallback strategy. AutoDetectEngine defaults to Podman first because it is more commonly available in rootless setups (Fedora, immutable distros). Both factory functions always wrap the result with SandboxAwareEngine.

Design Rationale

Why embedding over traditional interfaces?

Docker and Podman share identical argument formats for most operations. Embedding *BaseCLIEngine lets concrete engines reuse argument building without boilerplate delegation. Engine-specific behavior is injected via functional options, keeping the base generic.

Why a decorator for sandbox handling?

Sandbox awareness is a cross-cutting concern orthogonal to engine type. A decorator keeps Docker/Podman implementations clean — they never need to know about Flatpak or Snap. The decorator also short-circuits in non-sandbox environments.

Why functional options?

Options like ExecCommandFunc exist primarily for test injection. A config struct would expose internals in the public API. Functional options keep constructors clean for production use while allowing fine-grained control for testing.

Why Podman-first in AutoDetectEngine?

Podman is the default container engine on Fedora and other Red Hat-based distributions. It runs rootless by default, requires no daemon, and works on immutable distros via podman-remote.