Documentation
¶
Index ¶
- Constants
- Variables
- func PHPUserGroup() (int, int)
- func PlatformLabels(role, site, appVersion string) map[string]string
- func SPXKeyINIPath(homeDir, slug string) string
- func SiteContainerName(slug, role string) string
- func SiteNetworkName(slug string) string
- func SiteVolumeName(slug string) string
- type BindMount
- type ContainerInfo
- type ContainerSpec
- func AdminerSpec() ContainerSpec
- func ApacheWebSpec(site *types.Site, homeDir string) ContainerSpec
- func MailSpec() ContainerSpec
- func NginxWebSpec(site *types.Site, homeDir string) ContainerSpec
- func PHPSpec(site *types.Site, homeDir string) ContainerSpec
- func RedisSpec(site *types.Site) ContainerSpec
- func WebSpec(site *types.Site, homeDir string) ContainerSpec
- type DiskReport
- type Docker
- func (d *Docker) CheckDockerAvailable(ctx context.Context) error
- func (d *Docker) ChownPath(ctx context.Context, hostPath string, uid, gid int) error
- func (d *Docker) ChownVolume(ctx context.Context, volumeName string, uid, gid int) error
- func (d *Docker) ContainerExists(ctx context.Context, name string) (bool, error)
- func (d *Docker) ContainerIsRunning(ctx context.Context, name string) (bool, error)
- func (d *Docker) ContainerLogs(ctx context.Context, name string, lines int) (string, error)
- func (d *Docker) ContainersByLabel(ctx context.Context, match map[string]string) ([]ContainerInfo, error)
- func (d *Docker) CreateContainer(ctx context.Context, name, ref string, cfg *container.Config, ...) error
- func (d *Docker) CreateNetwork(ctx context.Context, name string, internal bool, labels map[string]string) error
- func (d *Docker) DiskUsage(ctx context.Context) (DiskReport, error)
- func (d *Docker) EnsureContainer(ctx context.Context, spec ContainerSpec) (string, error)
- func (d *Docker) EnsureNetwork(ctx context.Context, spec NetworkSpec) (string, error)
- func (d *Docker) EnsureVolume(ctx context.Context, spec VolumeSpec) (string, error)
- func (d *Docker) ExecInContainer(ctx context.Context, containerName string, cmd []string) (string, error)
- func (d *Docker) ExecInContainerStream(ctx context.Context, containerName string, opts ExecOptions, ...) (int, error)
- func (d *Docker) ExecInContainerWriter(ctx context.Context, containerName string, opts ExecOptions, ...) (int, error)
- func (d *Docker) ExecInContainerWriterStdin(ctx context.Context, containerName string, opts ExecOptions, stdin io.Reader, ...) (int, error)
- func (d *Docker) HasClient() bool
- func (d *Docker) NetworksByLabel(ctx context.Context, match map[string]string) ([]NetworkInfo, error)
- func (d *Docker) Ping(ctx context.Context) error
- func (d *Docker) ProviderInfo(ctx context.Context) (ProviderInfo, error)
- func (d *Docker) PublishedHostPort(ctx context.Context, name string, containerPort int) (int, error)
- func (d *Docker) PullImage(ctx context.Context, ref string, onProgress func(PullProgress)) error
- func (d *Docker) ReconcileNetworks(ctx context.Context) error
- func (d *Docker) RefreshProviderInfo()
- func (d *Docker) RemoveByLabel(ctx context.Context, match map[string]string) error
- func (d *Docker) RemoveContainer(ctx context.Context, name string) error
- func (d *Docker) RemoveContainersByLabel(ctx context.Context, match map[string]string) error
- func (d *Docker) RemoveNetworksByLabel(ctx context.Context, match map[string]string) error
- func (d *Docker) RemoveVolumesByLabel(ctx context.Context, match map[string]string) error
- func (d *Docker) RunOneShotCapture(ctx context.Context, name, image string, cmd []string, mounts []OneShotMount) (OneShotResult, error)
- func (d *Docker) SetClient(cli *client.Client)
- func (d *Docker) StartContainer(ctx context.Context, name string) error
- func (d *Docker) StopContainer(ctx context.Context, name string, grace time.Duration) error
- func (d *Docker) StreamContainerLogs(ctx context.Context, name string, since time.Time) (<-chan LogLine, error)
- func (d *Docker) WaitReady(ctx context.Context, name string, timeout time.Duration) error
- type Engine
- type EnvSecret
- type ExecLineHandler
- type ExecOptions
- type Healthcheck
- type LogLine
- type LogStream
- type Mount
- type NetworkAttachment
- type NetworkInfo
- type NetworkSpec
- type OneShotMount
- type OneShotResult
- type PortMap
- type ProviderInfo
- type PullProgress
- type Resources
- type RestartPolicy
- type Role
- type SecurityOptions
- type TmpfsMount
- type Ulimit
- type VolumeMount
- type VolumeSpec
Constants ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SiteContainerName is the canonical helper for per-site container names. One place to change if we ever rename the prefix.
func SiteNetworkName ¶
SiteNetworkName is the canonical name for a site's internal bridge network.
func SiteVolumeName ¶
SiteVolumeName is the canonical name for a site's database data volume.
Types ¶
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 ¶
CheckDockerAvailable is a backward-compatible alias for Ping that logs failure. New code should call Ping directly.
func (*Docker) ChownPath ¶
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 ¶
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 ¶
ContainerExists reports whether a container with the given name exists.
func (*Docker) ContainerIsRunning ¶
ContainerIsRunning reports whether the container exists and is running.
func (*Docker) ContainerLogs ¶
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 ¶
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 ¶
EnsureNetwork creates the named network if absent. Idempotent — returns the existing network's ID if it's already present.
func (*Docker) EnsureVolume ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
RemoveByLabel is a backward-compatible alias retained until call sites are migrated. New code should use RemoveContainersByLabel.
func (*Docker) RemoveContainer ¶
RemoveContainer force-removes a container by name. No-op if absent.
func (*Docker) RemoveContainersByLabel ¶
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 ¶
RemoveNetworksByLabel removes every network matching the given label set. NotFound errors during removal are tolerated.
func (*Docker) RemoveVolumesByLabel ¶
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 ¶
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 ¶
StartContainer starts an existing container. No-op if already running.
func (*Docker) StopContainer ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
NetworkAttachment associates the container with a Docker network plus optional aliases. The first attachment is treated as the primary network.
type NetworkInfo ¶
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 ¶
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 ¶
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
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 ¶
TmpfsMount mounts a tmpfs at the given target path.
type Ulimit ¶
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 ¶
VolumeMount mounts a Docker named volume.
type VolumeSpec ¶
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.