docker

package
v0.0.0-...-c2515dd Latest Latest
Warning

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

Go to latest
Published: May 10, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const (
	LabelPlatform   = "io.locorum.platform"
	LabelSite       = "io.locorum.site"
	LabelRole       = "io.locorum.role"
	LabelVersion    = "io.locorum.version"
	LabelConfigHash = "io.locorum.confighash"

	// PlatformValue is the constant value carried on every Locorum-owned
	// resource by LabelPlatform. Filtering by this label is the canonical
	// way to enumerate things we created.
	PlatformValue = "locorum"
)

Container, network and volume labels used to identify Locorum-owned resources. Reading these is preferred over name-prefix matching: labels survive renames, are visible in `docker inspect`, and let us scope cleanup precisely without false positives from unrelated user containers that happen to share a prefix.

View Source
const GlobalNetwork = "locorum-global"

GlobalNetwork is the bridge network shared by the global router, mail, adminer, and every per-site web/php container. Backends resolve each other by container alias on this network.

Variables

View Source
var (
	// ErrNotFound is returned by Engine methods when an inspected resource
	// does not exist. Wraps Docker SDK 404s.
	ErrNotFound = errors.New("docker resource not found")

	// ErrAlreadyExists is returned when a Create call raced with another
	// caller and the resource is already present. Idempotent helpers like
	// EnsureContainer translate this into a no-op; callers see it only when
	// they invoke a primitive create directly.
	ErrAlreadyExists = errors.New("docker resource already exists")

	// ErrContainerNotReady is returned by WaitReady when a container does
	// not become healthy within its deadline. The wrapped error message
	// includes the last N lines of the container's logs.
	ErrContainerNotReady = errors.New("container not ready")

	// ErrTransient marks an error the engine considers retryable but has
	// exhausted its retry budget on. Wraps the original cause; callers
	// should treat it as a final failure but may surface a "this might be
	// flaky, try again later" hint.
	ErrTransient = errors.New("transient docker error")

	// ErrSpecAmbiguous is returned by spec validation when a Mount has more
	// than one of {Bind, Volume, Tmpfs} populated, or none of them.
	ErrSpecAmbiguous = errors.New("mount spec must have exactly one of bind/volume/tmpfs")

	// ErrDaemonUnreachable is returned by Ping (and wrapped by lifecycle
	// methods) when the Docker daemon socket is unreachable — Docker
	// Desktop closed, the systemd unit stopped, the user's group is wrong,
	// etc. UI code branches on errors.Is(err, ErrDaemonUnreachable) to
	// surface a banner action ("Show how to start Docker") instead of a
	// raw stack trace.
	ErrDaemonUnreachable = errors.New("docker daemon unreachable")
)

Engine error sentinels. Callers can branch with errors.Is(err, …); humans see the wrapped context via fmt.Errorf("%w") chains. New code should not compare against Docker SDK errors directly — wrap them in one of these.

Functions

func PHPUserGroup

func PHPUserGroup() (int, int)

PHPUserGroup returns the uid:gid the PHP container should run as. On Windows os.Getuid()/Getgid() return -1; we fall back to 1000:1000 which matches the wodby image's default user.

func PlatformLabels

func PlatformLabels(role, site, appVersion string) map[string]string

PlatformLabels returns the label set applied to every Locorum-managed Docker resource. Pass site == "" for resources that are not tied to a specific site (e.g., the global router).

func SPXKeyINIPath

func SPXKeyINIPath(homeDir, slug string) string

SPXKeyINIPath is the canonical on-disk location of the per-site INI fragment that supplies SPX with its `spx.http_key` secret. The PHPSpec bind-mounts this file when SPX is enabled; the EnsureSPXStep generates it before the container starts.

Centralising the path here means both producer (sitestep) and consumer (PHPSpec) reference one constant, so a renamed directory can never silently desync the two halves.

func SiteContainerName

func SiteContainerName(slug, role string) string

SiteContainerName is the canonical helper for per-site container names. One place to change if we ever rename the prefix.

func SiteNetworkName

func SiteNetworkName(slug string) string

SiteNetworkName is the canonical name for a site's internal bridge network.

func SiteVolumeName

func SiteVolumeName(slug string) string

SiteVolumeName is the canonical name for a site's database data volume.

Types

type BindMount

type BindMount struct {
	Source   string
	Target   string
	ReadOnly bool
}

BindMount mounts a host path into the container.

type ContainerInfo

type ContainerInfo struct {
	ID     string
	Names  []string
	Image  string
	State  string
	Status string
	Labels map[string]string
}

ContainerInfo is the package-level view of a container. Keeps callers from importing docker SDK types directly.

type ContainerSpec

type ContainerSpec struct {
	Name       string
	Image      string
	Cmd        []string
	Entrypoint []string
	Env        []string
	EnvSecrets []EnvSecret
	User       string
	WorkingDir string
	Tty        bool
	Labels     map[string]string

	Ports      []PortMap
	Mounts     []Mount
	Networks   []NetworkAttachment
	ExtraHosts []string

	Healthcheck *Healthcheck
	Resources   Resources
	Security    SecurityOptions
	Init        bool
	Restart     RestartPolicy
}

ContainerSpec is the full declarative description of a container Locorum intends to create. It replaces the loose four-arg createContainer signature and lets the engine make idempotency, security, and resource decisions from a single source of truth.

Zero values are deliberately safe: an empty Security is "drop ALL caps, no-new-privileges"; an empty Resources is "10m × 3 log files, 1024 pids"; an empty Restart is "no". See specs_builders.go for the role-specific presets.

func AdminerSpec

func AdminerSpec() ContainerSpec

AdminerSpec builds the global adminer container spec.

func ApacheWebSpec

func ApacheWebSpec(site *types.Site, homeDir string) ContainerSpec

ApacheWebSpec builds the per-site Apache web container spec.

func MailSpec

func MailSpec() ContainerSpec

MailSpec builds the global mailhog container spec. Joined to the global network only — the router routes mail.localhost here.

func NginxWebSpec

func NginxWebSpec(site *types.Site, homeDir string) ContainerSpec

NginxWebSpec builds the per-site nginx web container spec.

func PHPSpec

func PHPSpec(site *types.Site, homeDir string) ContainerSpec

PHPSpec builds the per-site PHP-FPM container spec. Database credentials flow through EnvSecrets so the password is redacted from any error message Locorum itself emits.

LOCORUM_* env vars are read at PHP request time by wp-config-locorum.php to resolve WP_HOME/WP_SITEURL. They participate in the container's config hash, so a domain or docroot change forces a recreate.

SPX (php-spx) is gated by site.SPXEnabled. The spx.ini is bind-mounted unconditionally — it's inert until the extension loads and keeping the mount stable avoids hash churn when only the toggle flips. Activating SPX adds env vars + a per-site profile-data bind, both of which DO participate in the hash so the toggle correctly forces a recreate.

func RedisSpec

func RedisSpec(site *types.Site) ContainerSpec

RedisSpec builds the per-site Redis container spec.

func WebSpec

func WebSpec(site *types.Site, homeDir string) ContainerSpec

WebSpec dispatches to the right builder based on site.WebServer.

func (ContainerSpec) ConfigHash

func (s ContainerSpec) ConfigHash() string

ConfigHash returns a stable SHA-256 of the spec's intent. Two specs that would produce the same Docker container shape (identical image, env, mounts, security options, etc.) hash identically; any change that would require recreating the container changes the hash.

The hash deliberately ignores fields that vary independently of intent:

  • Map iteration order in Labels and Env (canonicalised by sorting).
  • LabelVersion (a Locorum upgrade alone must not force every container to recreate; we hash *intent*, not Locorum's own version stamp).
  • LabelConfigHash itself (would otherwise be self-referential).
  • EnvSecret values (the keys are part of the intent, the secret values belong to the runtime environment, and rotating a password should not be conflated with shape drift).

type DiskReport

type DiskReport struct {
	// LayersSize is the on-disk footprint of all Docker image layers.
	LayersSize int64

	// BuildCacheSize is the BuildKit cache size (separate from layers).
	BuildCacheSize int64

	// VolumeSize is the *reported* size of all named volumes — only
	// populated when the daemon's volume driver implements the
	// optional size measurement (the local driver does for Linux,
	// not on macOS). Zero on platforms where it's unavailable.
	VolumeSize int64

	// ImageCount is the number of unique tagged images.
	ImageCount int

	// ContainerCount is the number of containers (running or stopped).
	ContainerCount int

	// VolumeCount is the number of named volumes.
	VolumeCount int
}

DiskReport is the engine-level summary of `docker system df`. We expose a small, stable struct rather than the SDK's full DiskUsage so the health package doesn't end up depending on docker/api/types.

All sizes are bytes. A field of zero is "no data on this kind of object", not "we failed to look it up" — DiskUsage as a whole returns an error in the failure case and empties this struct.

type Docker

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

Docker is the production Engine implementation. The struct is goroutine-safe for concurrent use — the Docker SDK client is internally synchronised; the only mutable state owned by this type is the provider-info cache, guarded by pmu.

func New

func New() *Docker

New constructs a new Docker engine. The client must be set via SetClient before any method that talks to the daemon is called — main.go does this once at startup.

func (*Docker) CheckDockerAvailable

func (d *Docker) CheckDockerAvailable(ctx context.Context) error

CheckDockerAvailable is a backward-compatible alias for Ping that logs failure. New code should call Ping directly.

func (*Docker) ChownPath

func (d *Docker) ChownPath(ctx context.Context, hostPath string, uid, gid int) error

ChownPath does the same for a host path mounted into a one-shot container. Used for the site's FilesDir so wp-content/uploads has the right ownership before nginx/PHP touch it.

func (*Docker) ChownVolume

func (d *Docker) ChownVolume(ctx context.Context, volumeName string, uid, gid int) error

ChownVolume runs a one-shot privileged container that recursively chowns every entry in the named volume to uid:gid. Used before service start so PHP-FPM/MySQL can write into the bind without running as root themselves.

func (*Docker) ContainerExists

func (d *Docker) ContainerExists(ctx context.Context, name string) (bool, error)

ContainerExists reports whether a container with the given name exists.

func (*Docker) ContainerIsRunning

func (d *Docker) ContainerIsRunning(ctx context.Context, name string) (bool, error)

ContainerIsRunning reports whether the container exists and is running.

func (*Docker) ContainerLogs

func (d *Docker) ContainerLogs(ctx context.Context, name string, lines int) (string, error)

ContainerLogs returns the last `lines` lines of the container's logs. The Docker logs API multiplexes stdout+stderr into a single stream when the container was created with Tty:false, and returns un-multiplexed bytes when Tty:true. We inspect the container first so we know which reader to use — getting this wrong produces garbage output, which then hides the actual reason a container exited.

func (*Docker) ContainersByLabel

func (d *Docker) ContainersByLabel(ctx context.Context, match map[string]string) ([]ContainerInfo, error)

ContainersByLabel lists all containers (running or stopped) whose labels match every entry in the given map. An empty value matches any value for that label key.

func (*Docker) CreateContainer

func (d *Docker) CreateContainer(
	ctx context.Context,
	name, ref string,
	cfg *container.Config,
	hostCfg *container.HostConfig,
	netCfg *network.NetworkingConfig,
) error

CreateContainer is a thin SDK passthrough used by router/traefik to build its single container from raw container.Config + HostConfig + NetworkingConfig. It pulls the image if missing and starts the container after create.

New code should prefer Engine.EnsureContainer with a ContainerSpec; this method exists because the router's static config is hand-crafted.

func (*Docker) CreateNetwork

func (d *Docker) CreateNetwork(ctx context.Context, name string, internal bool, labels map[string]string) error

CreateNetwork ensures a Docker bridge network with the given name and labels exists. Idempotent. New code should prefer Engine.EnsureNetwork.

func (*Docker) DiskUsage

func (d *Docker) DiskUsage(ctx context.Context) (DiskReport, error)

DiskUsage returns the daemon's `system df` report. The call is **slow**: the daemon walks every container/image/volume/build-cache record, and on a busy workstation it can take >5 s. We mitigate via:

  • singleflight: concurrent callers share one in-flight RPC.
  • context propagation: the caller's deadline bounds the call.
  • shape conversion: the caller never sees the full SDK types, so adding a field doesn't break the contract.

The 30-second outer deadline mentioned in the plan is enforced by the **caller**, not here — the health.disk-low check passes a context with that deadline. This keeps DiskUsage usable from other call sites that have their own timing budget.

func (*Docker) EnsureContainer

func (d *Docker) EnsureContainer(ctx context.Context, spec ContainerSpec) (string, error)

EnsureContainer creates the container described by spec if absent, or recreates it if the existing container's ConfigHash label differs. Returns the container ID. Idempotent. The hash exclusion of LabelVersion means a Locorum upgrade alone does not force every container to recreate.

func (*Docker) EnsureNetwork

func (d *Docker) EnsureNetwork(ctx context.Context, spec NetworkSpec) (string, error)

EnsureNetwork creates the named network if absent. Idempotent — returns the existing network's ID if it's already present.

func (*Docker) EnsureVolume

func (d *Docker) EnsureVolume(ctx context.Context, spec VolumeSpec) (string, error)

EnsureVolume creates the named volume if absent. Returns its name. Idempotent.

func (*Docker) ExecInContainer

func (d *Docker) ExecInContainer(ctx context.Context, containerName string, cmd []string) (string, error)

ExecInContainer runs a one-shot command in a running container and returns its combined output. Errors include the exit code; non-zero exits return the captured output and a wrapped error.

Used by SiteManager helpers (multisite convert, mysqldump for clone/export, wp-cli wrapper). New streaming consumers should use ExecInContainerStream.

func (*Docker) ExecInContainerStream

func (d *Docker) ExecInContainerStream(ctx context.Context, containerName string, opts ExecOptions, onLine ExecLineHandler) (int, error)

ExecInContainerStream runs cmd inside containerName with separated stdout and stderr streams, invoking onLine for each captured line. It blocks until the command completes or ctx is cancelled.

Returns the command's exit code and an error. A non-zero exit code is NOT reported as an error — the caller decides how to interpret it. err is non-nil only for genuine plumbing failures (failed to create exec, attach, or connection broken before exit).

Unlike ExecInContainer, this path uses Tty:false so stdout and stderr are demultiplexed independently. That makes it suitable for hooks where the runner must distinguish failure messages from normal output.

func (*Docker) ExecInContainerWriter

func (d *Docker) ExecInContainerWriter(ctx context.Context, containerName string, opts ExecOptions, stdoutW, stderrW io.Writer) (int, error)

ExecInContainerWriter runs cmd inside containerName and pipes the container's stdout into stdoutW and stderr into stderrW byte-for-byte. It is the streaming counterpart of ExecInContainerStream for callers that need raw bytes (database dumps, file extracts) rather than line-oriented output.

Returns the command's exit code. Plumbing failures (failed to create exec, attach, write) are returned as err. A non-zero exit code is NOT reported as an error — the caller decides how to interpret it.

Either writer may be nil; nil means "discard". stdin is not attached; callers needing to feed input use ExecInContainerWriterStdin below.

func (*Docker) ExecInContainerWriterStdin

func (d *Docker) ExecInContainerWriterStdin(ctx context.Context, containerName string, opts ExecOptions, stdin io.Reader, stdoutW, stderrW io.Writer) (int, error)

ExecInContainerWriterStdin is ExecInContainerWriter plus a stdin source piped into the container. Used by Restore implementations that pipe a dump file into `mysql` / `psql`.

func (*Docker) HasClient

func (d *Docker) HasClient() bool

HasClient reports whether SetClient has been called yet. Background pollers that start before app.Initialize completes can use this to short-circuit instead of dereferencing a nil daemon client.

func (*Docker) NetworksByLabel

func (d *Docker) NetworksByLabel(ctx context.Context, match map[string]string) ([]NetworkInfo, error)

NetworksByLabel lists all networks whose labels match every entry in the given map.

func (*Docker) Ping

func (d *Docker) Ping(ctx context.Context) error

Ping verifies the engine is reachable. A nil client (SetClient has not been called yet) and any underlying transport failure are wrapped in ErrDaemonUnreachable so callers can branch with errors.Is.

func (*Docker) ProviderInfo

func (d *Docker) ProviderInfo(ctx context.Context) (ProviderInfo, error)

ProviderInfo returns Docker daemon identification, cached after first successful call. Concurrent callers share the same lookup; cache misses re-fetch.

func (*Docker) PublishedHostPort

func (d *Docker) PublishedHostPort(ctx context.Context, name string, containerPort int) (int, error)

PublishedHostPort returns the host-side TCP port assigned to the given container's containerPort, or 0 if the port isn't published. Used by SiteManager to surface the ephemeral DB port to the user when db.publish_port is enabled.

func (*Docker) PullImage

func (d *Docker) PullImage(ctx context.Context, ref string, onProgress func(PullProgress)) error

PullImage pulls ref, streaming progress to onProgress. No-op if already present locally. onProgress may be nil (progress is dropped). Idempotent and retry-wrapped for the BuildKit snapshot race.

func (*Docker) ReconcileNetworks

func (d *Docker) ReconcileNetworks(ctx context.Context) error

ReconcileNetworks removes orphaned Locorum-owned networks before recreate. Defensive — Docker daemon restarts and crash recovery can leave networks without containers attached, which then block recreate by name.

func (*Docker) RefreshProviderInfo

func (d *Docker) RefreshProviderInfo()

RefreshProviderInfo invalidates the cached provider info. Call after the daemon may have changed (e.g. user restarted Docker Desktop).

func (*Docker) RemoveByLabel

func (d *Docker) RemoveByLabel(ctx context.Context, match map[string]string) error

RemoveByLabel is a backward-compatible alias retained until call sites are migrated. New code should use RemoveContainersByLabel.

func (*Docker) RemoveContainer

func (d *Docker) RemoveContainer(ctx context.Context, name string) error

RemoveContainer force-removes a container by name. No-op if absent.

func (*Docker) RemoveContainersByLabel

func (d *Docker) RemoveContainersByLabel(ctx context.Context, match map[string]string) error

RemoveContainersByLabel force-removes every container matching the given label set. NotFound errors during removal are tolerated — another caller may have removed the container concurrently.

func (*Docker) RemoveNetworksByLabel

func (d *Docker) RemoveNetworksByLabel(ctx context.Context, match map[string]string) error

RemoveNetworksByLabel removes every network matching the given label set. NotFound errors during removal are tolerated.

func (*Docker) RemoveVolumesByLabel

func (d *Docker) RemoveVolumesByLabel(ctx context.Context, match map[string]string) error

RemoveVolumesByLabel removes every volume whose labels match the given set. Used by the three-way "purge" delete path. Idempotent — NotFound errors during removal are tolerated.

func (*Docker) RunOneShotCapture

func (d *Docker) RunOneShotCapture(ctx context.Context, name, image string, cmd []string, mounts []OneShotMount) (OneShotResult, error)

RunOneShotCapture launches a transient container, runs cmd, captures stdout / stderr, and removes the container. Used by EnsureMarkerStep to read the volume marker before the database container boots — which is too late to detect engine drift.

Idempotent in the sense that callers can re-invoke after a failure; the helper force-removes any leftover container with the same name.

func (*Docker) SetClient

func (d *Docker) SetClient(cli *client.Client)

SetClient injects the Docker SDK client. Separated from New so the App layer (which owns daemon-connection lifecycle) can construct the client once and share it.

func (*Docker) StartContainer

func (d *Docker) StartContainer(ctx context.Context, name string) error

StartContainer starts an existing container. No-op if already running.

func (*Docker) StopContainer

func (d *Docker) StopContainer(ctx context.Context, name string, grace time.Duration) error

StopContainer stops a container with a grace period. No-op if absent.

func (*Docker) StreamContainerLogs

func (d *Docker) StreamContainerLogs(ctx context.Context, name string, since time.Time) (<-chan LogLine, error)

StreamContainerLogs follows the named container's combined stdout + stderr. The since argument selects the starting timestamp: pass the zero time.Time to stream from the live tail (with the last 100 lines of historical context); pass a real time to resume after a reconnect.

The returned channel closes when ctx is cancelled, the container exits, or an unrecoverable read error occurs. Callers ranging over the channel MUST cancel the supplied context before returning to release the underlying SDK connection.

The first error (if any) is reported through the optional second channel; nil means "channel closed cleanly." Returns ErrNotFound when the container does not exist at start; transient mid-stream read failures are logged via slog and result in the channel closing.

func (*Docker) WaitReady

func (d *Docker) WaitReady(ctx context.Context, name string, timeout time.Duration) error

WaitReady blocks until the container is healthy or timeout elapses. On timeout, returns ErrContainerNotReady wrapped with the last 50 log lines so the GUI can surface what actually failed.

LOCORUM_HEALTH_TIMEOUT_MULT (float, default 1.0) scales the timeout — useful on slow CI hardware where MySQL's first-start init can take 60-90s instead of the local-dev typical 20s.

type Engine

type Engine interface {
	// EnsureContainer creates the container described by spec if absent, or
	// recreates it if the existing container's ConfigHash label differs.
	// Returns the container ID. Idempotent.
	EnsureContainer(ctx context.Context, spec ContainerSpec) (string, error)

	// StartContainer starts an existing container by name. No-op if already
	// running. Idempotent.
	StartContainer(ctx context.Context, name string) error

	// StopContainer stops a container with a grace period before SIGKILL.
	// No-op if absent or already stopped. Idempotent.
	StopContainer(ctx context.Context, name string, grace time.Duration) error

	// RemoveContainer force-removes a container by name. No-op if absent.
	// Idempotent.
	RemoveContainer(ctx context.Context, name string) error

	// EnsureNetwork creates the named bridge network if absent. Returns its ID.
	// Idempotent.
	EnsureNetwork(ctx context.Context, spec NetworkSpec) (string, error)

	// EnsureVolume creates the named volume if absent. Returns its name.
	// Idempotent.
	EnsureVolume(ctx context.Context, spec VolumeSpec) (string, error)

	// PullImage pulls the image, streaming progress to onProgress. No-op if
	// the image is already present locally. onProgress may be nil.
	// Idempotent.
	PullImage(ctx context.Context, ref string, onProgress func(PullProgress)) error

	// WaitReady blocks until the container is healthy (per its Healthcheck)
	// or timeout elapses. Returns wrapped ErrContainerNotReady on timeout
	// with the last 50 log lines appended.
	WaitReady(ctx context.Context, name string, timeout time.Duration) error

	// ContainerLogs returns the last lines of the container's combined
	// stdout+stderr, demultiplexed for human reading.
	ContainerLogs(ctx context.Context, name string, lines int) (string, error)

	// StreamContainerLogs follows a container's stdout+stderr. The caller
	// MUST cancel ctx to release the underlying SDK connection. Pass the
	// zero time.Time to start at the live tail; pass a real time to
	// resume after a reconnect. The channel closes when ctx is cancelled,
	// the container exits, or an unrecoverable read error occurs.
	StreamContainerLogs(ctx context.Context, name string, since time.Time) (<-chan LogLine, error)

	// ChownVolume runs a privileged one-shot alpine container that
	// recursively chowns every file in the volume to uid:gid. Used before
	// service start so PHP-FPM/MySQL can write into the bind without
	// running as root themselves.
	ChownVolume(ctx context.Context, volumeName string, uid, gid int) error

	// ChownPath does the same for a host path mounted into a one-shot
	// container. Used for the site's FilesDir so wp-content/uploads has
	// the right ownership before nginx/PHP touch it.
	ChownPath(ctx context.Context, hostPath string, uid, gid int) error

	// ContainersByLabel lists containers (running or stopped) whose labels
	// match every entry in the given map. An empty value matches any value
	// for that label key.
	ContainersByLabel(ctx context.Context, match map[string]string) ([]ContainerInfo, error)

	// RemoveContainersByLabel force-removes every container matching the
	// given label set. Idempotent.
	RemoveContainersByLabel(ctx context.Context, match map[string]string) error

	// NetworksByLabel lists networks whose labels match every entry.
	NetworksByLabel(ctx context.Context, match map[string]string) ([]NetworkInfo, error)

	// RemoveNetworksByLabel removes every network matching the label set.
	// Idempotent.
	RemoveNetworksByLabel(ctx context.Context, match map[string]string) error

	// ContainerExists reports whether a container with the given name exists,
	// regardless of state.
	ContainerExists(ctx context.Context, name string) (bool, error)

	// ContainerIsRunning reports whether the container exists and is in the
	// "running" state.
	ContainerIsRunning(ctx context.Context, name string) (bool, error)

	// ProviderInfo returns Docker daemon identification, cached after first
	// call. Use RefreshProviderInfo to force a re-fetch.
	ProviderInfo(ctx context.Context) (ProviderInfo, error)

	// Ping verifies the engine is reachable. Used by health checks.
	Ping(ctx context.Context) error

	// RunOneShotCapture launches a transient container, captures stdout
	// + stderr + exit code, and removes the container. Used by
	// EnsureMarkerStep to inspect a volume before the service container
	// owning it has booted.
	RunOneShotCapture(ctx context.Context, name, image string, cmd []string, mounts []OneShotMount) (OneShotResult, error)

	// DiskUsage returns a high-level summary of `docker system df`. Slow
	// (multi-second on busy hosts); callers should pass a context with
	// a deadline (~30s) and gate via a circuit breaker if they call it
	// repeatedly. Concurrent callers are coalesced via singleflight.
	DiskUsage(ctx context.Context) (DiskReport, error)
}

Engine is the surface every consumer above internal/docker uses. *Docker implements it; tests use internal/docker/fake.Engine. New code should depend on Engine, not on the concrete *Docker — it keeps the test/fake path open and forces per-call context plumbing.

Every method takes a context.Context as its first argument. There is NO shared "current context" on the engine; deadlines and cancellation thread from the caller all the way down to the daemon socket.

Every method is documented as idempotent unless explicitly stated otherwise — calling it twice in a row must succeed.

type EnvSecret

type EnvSecret struct {
	Key   string
	Value string
}

EnvSecret is an environment variable whose value must never appear in log lines, error messages, or activity events. The engine sets it on the container exactly like Env, but redacts it from anything Locorum itself emits. We cannot hide it from `docker inspect`; that's documented.

type ExecLineHandler

type ExecLineHandler func(line string, stderr bool)

ExecLineHandler receives a single line of stdout (stderr=false) or stderr (stderr=true) from a streaming exec. The trailing newline is stripped.

Implementations must be cheap; they are invoked from the streaming goroutine while output is still arriving. Heavy work should be deferred to another goroutine via a buffered channel.

type ExecOptions

type ExecOptions struct {
	// Cmd is the argv to execute. The first element must be an absolute path
	// or available on PATH inside the container. Required.
	Cmd []string
	// Env is a list of "KEY=VALUE" strings appended to the container's env.
	// Already-set keys in the image are overridden.
	Env []string
	// User is the optional "uid[:gid]" to run as. Empty preserves the
	// container's default user.
	User string
	// WorkingDir overrides the container's working directory. Empty preserves
	// the default.
	WorkingDir string
}

ExecOptions configures a streaming exec invocation.

type Healthcheck

type Healthcheck struct {
	Test        []string
	Interval    time.Duration
	Timeout     time.Duration
	Retries     int
	StartPeriod time.Duration
}

Healthcheck describes the container readiness probe Docker runs. WaitReady polls the inspected health state on a tighter cadence than Docker's own checks so the GUI surfaces transitions immediately.

type LogLine

type LogLine struct {
	Time   time.Time
	Stream LogStream
	Text   string
}

LogLine is one line of streamed output. Time is the container-emitted timestamp (truncated to millisecond resolution); Stream identifies the origin; Text is the line contents *without* trailing newline.

type LogStream

type LogStream uint8

LogStream identifies which container stream a LogLine came from. Knowing stdout vs stderr lets the UI colour stderr lines red without applying a fragile regex to the text.

const (
	LogStreamStdout LogStream = iota
	LogStreamStderr
)

type Mount

type Mount struct {
	Bind   *BindMount
	Volume *VolumeMount
	Tmpfs  *TmpfsMount
}

Mount is the typed parent for the three mount kinds Locorum uses. Exactly one of BindMount/VolumeMount/TmpfsMount fields is non-zero; the engine rejects ambiguity at apply time.

type NetworkAttachment

type NetworkAttachment struct {
	Network string
	Aliases []string
}

NetworkAttachment associates the container with a Docker network plus optional aliases. The first attachment is treated as the primary network.

type NetworkInfo

type NetworkInfo struct {
	ID     string
	Name   string
	Driver string
	Labels map[string]string
}

NetworkInfo is the package-level view of a Docker network.

type NetworkSpec

type NetworkSpec struct {
	Name     string
	Internal bool
	Driver   string // empty → "bridge"
	Labels   map[string]string
}

NetworkSpec describes a Docker network we want to ensure exists.

func GlobalNetworkSpec

func GlobalNetworkSpec() NetworkSpec

GlobalNetworkSpec is the spec for the global bridge network.

func SiteNetworkSpec

func SiteNetworkSpec(site *types.Site) NetworkSpec

SiteNetworkSpec is the spec for a site's internal bridge network.

type OneShotMount

type OneShotMount struct {
	Volume   string // named Docker volume
	Bind     string // host path
	Target   string // in-container target
	ReadOnly bool
}

OneShotMount describes one mount attached to a transient run-and-remove container. Either Volume or Bind must be set, never both.

type OneShotResult

type OneShotResult struct {
	Stdout   []byte
	Stderr   []byte
	ExitCode int
}

OneShotResult is the captured output of RunOneShotCapture.

type PortMap

type PortMap struct {
	HostIP        string
	HostPort      string
	ContainerPort string
	Proto         string // "tcp" (default) or "udp"
}

PortMap is one host→container port binding. HostIP defaults to "0.0.0.0"; set "127.0.0.1" for loopback-only admin endpoints (e.g. router admin API).

type ProviderInfo

type ProviderInfo struct {
	Name            string // e.g. "Docker Desktop", "Colima", "OrbStack", "docker"
	OperatingSystem string
	OSType          string // "linux", "windows"
	Architecture    string
	ServerVersion   string                      // raw, as the daemon reports it
	ServerVersionP  version.DockerServerVersion // parsed view; .IsZero() if unparseable
	Rootless        bool
	IsDockerDesktop bool
	NCPU            int
	MemTotal        int64
}

ProviderInfo identifies the Docker daemon Locorum is talking to. Used to surface platform-specific warnings (Apple Silicon Rosetta, slow VirtioFS on Docker Desktop macOS, rootless Podman quirks, etc.).

type PullProgress

type PullProgress struct {
	Image      string
	Status     string
	Current    int64
	Total      int64
	LayerCount int
}

PullProgress is one tick of image-pull progress. Aggregated across layers into bytes-downloaded / bytes-total / status. Status is human-readable ("Pulling fs layer", "Extracting", "Pull complete"); the engine updates it on every JSON message received from Docker.

type Resources

type Resources struct {
	MemoryLimit int64
	CPUShares   int64
	LogMaxSize  string
	LogMaxFiles int
	PidsLimit   int64
	Ulimits     []Ulimit
}

Resources caps a container's footprint. Defaults applied by the engine when a field is left zero are:

LogMaxSize:  "10m"
LogMaxFiles: 3
PidsLimit:   1024
MemoryLimit: 0  (unlimited; revisited per role in a follow-up)

type RestartPolicy

type RestartPolicy string

RestartPolicy governs the Docker daemon's container-level restart loop. Locorum manages lifecycle itself, so the default is "no" — a restart loop on a service container hides startup bugs from the user.

const (
	RestartNo            RestartPolicy = "no"
	RestartOnFailure     RestartPolicy = "on-failure"
	RestartUnlessStopped RestartPolicy = "unless-stopped"
)

type Role

type Role = string

Role is the string identifier for a containerised component. Used both as the value of LabelRole on live resources and to look up per-role resource caps in roleResources.

const (
	RoleRouter   Role = "router"
	RoleWeb      Role = "web"
	RolePHP      Role = "php"
	RoleDatabase Role = "database"
	RoleRedis    Role = "redis"
	RoleMail     Role = "mail"
	RoleAdminer  Role = "adminer"

	RoleGlobalNetwork Role = "global-network"
	RoleSiteNetwork   Role = "site-network"
	RoleDatabaseData  Role = "database-data"

	// RoleDefault is the sentinel passed to roleResources when no
	// per-role override applies. resourceDefaults() uses it.
	RoleDefault Role = ""
)

Container/network role names. Stable strings — written to live container labels and used for filtering during cleanup, so changing a value is a breaking change.

type SecurityOptions

type SecurityOptions struct {
	CapDrop         []string
	CapAdd          []string
	NoNewPrivileges bool
	ReadOnlyRootFS  bool
	SecurityOpt     []string
}

SecurityOptions hardens the container kernel surface. The engine treats a zero value as "CapDrop=ALL, NoNewPrivileges=true" — opt out by setting fields explicitly. Only opt out with a recorded reason.

type TmpfsMount

type TmpfsMount struct {
	Target  string
	Options string // "size=64m,uid=1000" etc.
}

TmpfsMount mounts a tmpfs at the given target path.

type Ulimit

type Ulimit struct {
	Name string
	Soft int64
	Hard int64
}

Ulimit is a single getrlimit(2)-style limit applied to the container's processes. Mirrors the Docker SDK shape but kept inside our package so callers don't import docker/api/types directly.

type VolumeMount

type VolumeMount struct {
	Name     string
	Target   string
	ReadOnly bool
}

VolumeMount mounts a Docker named volume.

type VolumeSpec

type VolumeSpec struct {
	Name   string
	Labels map[string]string
}

VolumeSpec describes a Docker named volume we want to ensure exists.

func SiteVolumeSpec

func SiteVolumeSpec(site *types.Site) VolumeSpec

SiteVolumeSpec is the spec for a site's database data volume.

Directories

Path Synopsis
Package fake provides an in-memory docker.Engine for unit tests.
Package fake provides an in-memory docker.Engine for unit tests.

Jump to

Keyboard shortcuts

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