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

docker

package
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: AGPL-3.0 Imports: 41 Imported by: 0

Documentation

Overview

Package docker implements the runtime adapter for local containers.

Observe reports facts and never remediates — an adapter that silently restarts things makes drift undetectable and breaks the reconciler's report path. Capacity is adapter-reported; core does not read /proc and has no concept of a host (R-243).

Each app runs on its own bridge network with Pando's container attached and nothing published (R-023, R-025, R-026). An app whose egress rules restrict anything runs instead on an internal network with no route out, behind a per-app egress gateway that its workloads reach through HTTP_PROXY and HTTPS_PROXY (R-187); one with no restriction runs exactly as it would with no egress controls (R-186). See egress.go.

Index

Constants

View Source
const DefaultPidsLimit = 1024

DefaultPidsLimit is how many processes and threads a container may hold at once when the adapter's configuration does not say [P]. Well above what a web app, a JVM or a database runs with, and far below what exhausts a host.

View Source
const Kind = "docker"

Kind is the adapter's kind string.

Variables

This section is empty.

Functions

func CheckNetworkPool added in v0.14.0

func CheckNetworkPool(raw string) error

CheckNetworkPool refuses an app network range outside private address space. App networks taken from public space would make the addresses in it unreachable from every app, and would sit where the egress gateway's private-range block (R-181) does not see them. An empty range, "off" and "docker" are left to networkPool, as is anything that is not an IPv4 range.

func DefaultNetworkPool added in v0.5.0

func DefaultNetworkPool() string

DefaultNetworkPool is the range app networks take their addresses from when none is set. The Incus runtime uses it inside each VM, where this adapter makes the networks (notes-incus-runtime.md).

func Info

func Info() api.KindInfo

Info describes this kind of adapter for the forms that configure one (api.KindInfo, R-261).

Types

type Adapter

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

Adapter runs workloads as Docker containers.

Its isolation class is `container`: a shared kernel. That is reported honestly rather than optimistically, because policy floors compare against it (R-024, R-114) and an adapter that overstated its class would let a hardened install run work it meant to exclude.

func New

func New() *Adapter

New builds an unconfigured adapter.

func NewWithClient

func NewWithClient(cli *client.Client, config Config) *Adapter

NewWithClient builds an adapter that talks to Docker through cli, with config as Configure would have read it. The multi-host adapter builds one per host, on a client authenticated to that host.

func (*Adapter) Apply

func (a *Adapter) Apply(ctx context.Context, p api.BundlePlan) (api.BundleHandle, error)

Apply converges the bundle toward the plan.

Idempotent by construction: each workload is compared against what exists and recreated only when it differs. Calling Apply with an already-satisfied plan touches nothing, which is what lets the reconciler call it freely.

func (*Adapter) ApplyEdge

func (a *Adapter) ApplyEdge(ctx context.Context, p api.EdgePlan) error

ApplyEdge converges an edge toward its plan.

func (*Adapter) Bundles

func (a *Adapter) Bundles(ctx context.Context) (map[string]bool, error)

Bundles names every bundle with a network or a volume on this daemon.

The multi-host adapter's record of where an app is (R-256): an app's network is made first in Apply, and its volumes outlive it (R-204), so a host holding either is the app's host. Nothing about placement is written anywhere else (R-027).

func (*Adapter) Capabilities

func (a *Adapter) Capabilities(ctx context.Context) (api.RuntimeCapabilities, error)

Capabilities reports what this adapter can do (R-254).

func (*Adapter) Capacity

func (a *Adapter) Capacity(ctx context.Context) (api.Capacity, error)

Capacity is adapter-reported (R-243).

func (*Adapter) Category

func (a *Adapter) Category() api.Category

func (*Adapter) CleanupTrials

func (a *Adapter) CleanupTrials(ctx context.Context) error

CleanupTrials removes anything a previous trial left behind.

Called at startup: a trial interrupted by Pando restarting would otherwise leave a running copy of someone's app with nothing tracking it.

func (*Adapter) Committed

func (a *Adapter) Committed(ctx context.Context) (api.Fit, error)

Committed is the CPU and memory every running app container on this daemon is limited to, summed: what R-242's arithmetic counts as taken, from the containers' own limits rather than from Pando's database, so each host of a multi-host install answers for itself. A container with no limit commits nothing. Running only, as R-242 counts only running apps: a stopped app holds nothing, and counting it would refuse deploys to make room for something that is not there.

func (*Adapter) CommittedBy

func (a *Adapter) CommittedBy(ctx context.Context, bundleID string) (api.Fit, error)

CommittedBy is what one bundle's running containers are limited to.

func (*Adapter) Configure

func (a *Adapter) Configure(_ context.Context, raw json.RawMessage) error

func (*Adapter) CreateVolume

func (a *Adapter) CreateVolume(ctx context.Context, req api.VolumeRequest) (api.VolumeHandle, error)

func (*Adapter) Destroy

func (a *Adapter) Destroy(ctx context.Context, ref api.BundleRef, opts api.DestroyOptions) error

Destroy removes the bundle's containers and network.

Volumes are kept unless explicitly asked otherwise: they outlive the apps that mount them (R-204), and destroying them is a separate, deliberate act.

func (*Adapter) DestroyVolume

func (a *Adapter) DestroyVolume(ctx context.Context, h api.VolumeHandle) error

func (*Adapter) DetachProxy

func (a *Adapter) DetachProxy(ctx context.Context, bundleID string) error

DetachProxy disconnects the container this adapter joins to app networks from a torn-down bundle's networks, and removes them, so they do not wait for that container to be replaced before ReclaimNetworks can collect them.

Only for a container named in ProxyContainer — the multi-host adapter's forwarding agent on an app host — and never for Pando's own: disconnecting a running container drops its published ports on Docker Desktop, which is why Destroy leaves Pando attached (see Destroy). The agent's published port is on a network outside the app range, which carries no bundle label and is never touched here: only a network labeled as this bundle's is.

func (*Adapter) EdgeVolumes

func (a *Adapter) EdgeVolumes(ctx context.Context) ([]api.VolumeHandle, error)

EdgeVolumes is every volume an edge owns, for the full-host backup.

func (*Adapter) Edges

func (a *Adapter) Edges(ctx context.Context) ([]string, error)

Edges names every edge this runtime has.

func (*Adapter) Exec

Exec opens a session. Core has already checked app.exec, consulted policy, and written the audit event before this is called (R-084, R-085, R-228) — the adapter does not authorize.

func (*Adapter) HasImage added in v0.5.0

func (a *Adapter) HasImage(ctx context.Context, ref string) (bool, error)

HasImage reports whether the daemon still has an image, by its ID or name. Not found is false and no error; any other failure is the daemon's.

func (*Adapter) HealthCheck

func (a *Adapter) HealthCheck(ctx context.Context) error

func (*Adapter) ImportImage

func (a *Adapter) ImportImage(ctx context.Context, r io.Reader) (string, error)

ImportImage loads an image tarball into the daemon and returns the loaded image's ID.

The reference is read back from the daemon's own response rather than assumed, because the tag the build asked for and the tag the daemon actually recorded are not guaranteed to match, and running the wrong one would be silent.

The ID, not the tag, is what is returned: it is content-addressed, so what a deployment records cannot be changed by a later build. A build refused after it was loaded (by the security scan or the port check) used to move pando/<app>:latest, and a workload recreated afterwards ran the refused image (R-146).

func (*Adapter) InUse

func (a *Adapter) InUse(ctx context.Context) (api.InUse, error)

InUse sums what every app workload on the daemon is using now (R-245).

Pando's workloads only, found by their bundle label: the figure to read beside what Pando has committed to them. Two adapters on one daemon each report the same sum, as they report the same machine. Read in parallel, so it takes about a second however many there are.

func (*Adapter) Kind

func (a *Adapter) Kind() string

func (*Adapter) LargestFitFor

func (a *Adapter) LargestFitFor(context.Context, string) (*api.Fit, error)

LargestFitFor is nil on one host: it is one place, and R-242's check of the totals, which already leaves out the app being planned, says everything this would. The multi-host adapter asks RoomFor instead.

func (*Adapter) Logs

func (a *Adapter) Logs(ctx context.Context, ref api.WorkloadRef, opts api.LogOptions) (io.ReadCloser, error)

func (*Adapter) Observe

func (a *Adapter) Observe(ctx context.Context, ref api.BundleRef) (api.ObservedBundle, error)

Observe reports what exists. It never remediates (design 05 §2.1).

func (*Adapter) ObserveEdge

func (a *Adapter) ObserveEdge(ctx context.Context, name string) (api.EdgeState, error)

ObserveEdge reports what exists. It never remediates (design 05).

func (*Adapter) PullImage

func (a *Adapter) PullImage(ctx context.Context, ref string) error

PullImage implements api.SelfUpgrader.

func (*Adapter) ReclaimNetworks

func (a *Adapter) ReclaimNetworks(ctx context.Context, owns func(bundleID string) bool) (int, error)

ReclaimNetworks removes bundle networks that nothing is attached to.

Called at startup, which is the one moment this is safe: a network held by a previous Pando container has a dead endpoint on it, so Docker removes it without anybody disconnecting anything. Doing the same while serving would mean detaching the running Pando, and on Docker Desktop that drops its published ports.

The cost of the timing is honest and worth stating: a network belonging to an app deleted since the last restart is reclaimed at the next one, not immediately. Docker's default pool holds about thirty, so an install that deletes thirty apps between restarts can still run out — better than leaking them permanently, and the remedy is a restart rather than a docker command.

owns says whether a bundle belongs to this installation. A network whose bundle it does not own is another install's on the same Docker host, and is left alone; nil owns everything.

func (*Adapter) RejoinNetworks

func (a *Adapter) RejoinNetworks(ctx context.Context, owns func(bundleID string) bool) (int, error)

RejoinNetworks puts this Pando container back on the network of every app that is still running (R-023, R-025).

The set of networks Pando's container belongs to *is* the set of apps its proxy can reach, and that membership belongs to a container, not to an install. Replacing the Pando container — an upgrade, a `compose up --build`, any recreate — therefore starts one that is on none of them, while every app container carries on running perfectly. The apps are up; nothing can reach them; the reconciler sees a converged world and does nothing, because from its side the world *is* converged. Every app answers 502 until something happens to redeploy it.

ensureNetwork already re-attaches, but only on the way through a deploy, which is the one thing that is not going to happen to an app that is already running the spec it is pinned to. So the attachment has to be restored at the moment it was lost: startup.

Ordered after ReclaimNetworks deliberately. Reclaim removes the networks of apps that no longer exist, and it recognizes them by their being empty — joining first would put an endpoint on every one of them and make each look busy, turning a reclaim into a leak.

owns is as for ReclaimNetworks: another install's app network is not joined.

func (*Adapter) RemoveEdge

func (a *Adapter) RemoveEdge(ctx context.Context, name string) error

RemoveEdge removes an edge's container. Its volumes are kept: a certificate store outlives a restart of Pando with the edge briefly switched off.

func (*Adapter) RemoveHelpers

func (a *Adapter) RemoveHelpers(ctx context.Context) error

RemoveHelpers removes upgrade helpers that have finished. The new Pando calls it once it has read the outcome.

func (*Adapter) RestoreVolume

func (a *Adapter) RestoreVolume(ctx context.Context, h api.VolumeHandle, src io.Reader) error

RestoreVolume extracts a tar archive produced by SnapshotVolume into the volume.

Destructive by design: the volume is emptied first. A restore that merged into whatever was already there would produce a volume that matches neither the backup nor the previous state, which is the worst of the three outcomes.

func (*Adapter) RoomFor

func (a *Adapter) RoomFor(ctx context.Context, bundleID string) (*api.Fit, error)

RoomFor is what this host has free for a bundle: what is left of the totals, plus what the bundle's own running containers hold, since a redeploy replaces them. Nil when the totals are not known.

func (*Adapter) Self

func (a *Adapter) Self(ctx context.Context) (api.SelfWorkload, error)

Self implements api.SelfUpgrader.

func (*Adapter) SnapshotVolume

func (a *Adapter) SnapshotVolume(ctx context.Context, h api.VolumeHandle, dst io.Writer) error

SnapshotVolume streams the volume's contents to dst as a tar archive.

func (*Adapter) StartHelper

func (a *Adapter) StartHelper(ctx context.Context, spec api.HelperSpec) (string, error)

StartHelper implements api.SelfUpgrader.

func (*Adapter) Stop

func (a *Adapter) Stop(ctx context.Context, ref api.BundleRef) error

func (*Adapter) Trial

func (a *Adapter) Trial(ctx context.Context, req api.TrialRequest) (api.TrialResult, error)

Trial starts a workload once, in throwaway isolation, and reports what it did (R-097).

The point is that ports are discovered by watching, not by asking. R-005 says the person deploying may not know what a port is, so a question about one is a question they cannot answer — and this is the mechanism that avoids having to ask it.

Everything created here is labeled with the trial ID and removed at the end, including on the failure paths. A trial that leaks a container leaks a running copy of a stranger's app.

func (*Adapter) Upstream

func (a *Adapter) Upstream(_ context.Context, ref api.WorkloadRef, port int) (api.Upstream, error)

Upstream is the workload's container name on its bundle network (R-023).

The name, not the workload's alias. Pando's container is joined to every bundle network (attachProxy), so "web" would mean a different container on each of them; the container name is unique across all of them. It is also a name rather than an address, so it survives the container being recreated.

Computed, not looked up: this runs on every proxied request.

func (*Adapter) Usage

func (a *Adapter) Usage(ctx context.Context, ref api.BundleRef) (api.BundleUsage, error)

Usage reads what an app's containers are using now (R-245).

CPU is sampled by the daemon over about a second — the stats call without streaming waits for a second reading to diff against — so the containers are read in parallel, and the whole reading takes about a second however many parts the app has. A stopped container reports no CPU or memory, only the disk it wrote.

func (*Adapter) WatchBundles

func (a *Adapter) WatchBundles(ctx context.Context, sink func(api.BundleEvent)) error

WatchBundles follows the daemon's event stream for Pando's containers (O-52). It reports what happened and does nothing about it (R-148).

One stream per daemon, filtered by the daemon to containers carrying Pando's label, so a host running other work sends nothing for it. It returns when the stream ends: the daemon restarting, the connection dropping. Reconnecting is the caller's, which then looks at every app again, since nothing between the two streams was seen.

type Config

type Config struct {
	// Host is the Docker endpoint. Empty uses the environment, which is what
	// the bundled Compose file relies on.
	Host string `json:"host,omitempty"`

	// TotalCPUMillis and TotalMemoryBytes let an operator tell Pando how much
	// of the machine it may use.
	//
	// Capacity is adapter-reported, never host-inspected (R-243): core does not
	// read /proc and has no concept of a host. When these are unset the adapter
	// asks the Docker daemon what it has, which is the same principle — the
	// adapter answers for itself.
	TotalCPUMillis   int   `json:"total_cpu_millis,omitempty"`
	TotalMemoryBytes int64 `json:"total_memory_bytes,omitempty"`
	TotalDiskBytes   int64 `json:"total_disk_bytes,omitempty"`

	// PidsLimit is how many processes and threads each app container may
	// hold at once (hardening.go). Zero is DefaultPidsLimit; negative is no
	// limit, for a host that needs it and accepts that one app can then
	// exhaust the process table every app shares.
	PidsLimit int64 `json:"pids_limit,omitempty"`

	// ProxyContainer is the container Pando's proxy runs in, which this adapter
	// attaches to every bundle network it creates.
	//
	// Empty means "this process's own container", detected from the hostname —
	// which is what Docker sets to the container ID, and what the bundled
	// Compose topology relies on.
	ProxyContainer string `json:"proxy_container,omitempty"`

	// NetworkPool is the IPv4 range app networks take their addresses from.
	// Empty uses 10.213.0.0/16; "off" leaves allocation to Docker's default
	// pool, which holds about thirty networks (see subnets.go). Any range up to
	// a /24 is accepted, so a wider one such as 10.208.0.0/12 holds more apps.
	NetworkPool string `json:"network_pool,omitempty"`

	// NetworkBlockBits is the size of each app network as a prefix length,
	// 24 to 29. Zero means 28: 13 containers, 4,096 networks in the default
	// pool. A bundle with more workloads than fit gets a larger block.
	NetworkBlockBits int `json:"network_block_bits,omitempty"`

	// OCIRuntime is the runtime Docker starts app containers with, by the name
	// the daemon registered it under: "runsc" for gVisor, "kata" for Kata
	// Containers. Empty is the daemon's default, which is runc.
	//
	// It decides the isolation class this adapter reports (isolationOf), and
	// that class is what host policy floors compare against (R-024, R-114).
	// So the class is derived from the name here, never stated by the operator,
	// and HealthCheck refuses a name the daemon does not have — a policy floor
	// of `sandboxed` satisfied by a runtime that was never installed would
	// admit exactly the work it was set to keep out.
	OCIRuntime string `json:"oci_runtime,omitempty"`

	// EgressGatewayImage is the image a restricted app's egress gateway runs
	// from (R-187, egress.go): any image with Pando's binary at
	// /usr/local/bin/pando. Empty is the image Pando's own container runs,
	// which is right for the shipped Compose file. Pando run on the host has
	// no container to take it from, and without this set the adapter reports
	// that it cannot restrict egress (R-186).
	EgressGatewayImage string `json:"egress_gateway_image,omitempty"`

	// VolumeRoot, when set, makes each app volume a bind of the directory
	// VolumeRoot/<volume ID> rather than storage of the engine's own. The
	// Incus runtime sets it: its volumes are Incus volumes attached to the
	// app's VM there, so they outlive the VM (R-204, notes-incus-runtime.md).
	// Not a setting of this adapter's own (Info).
	VolumeRoot string `json:"-"`
}

Config is the adapter's configuration.

type Old

type Old struct {
	ID    string
	Name  string
	Image string // the image ID it ran
	// contains filtered or unexported fields
}

Old is the container being replaced, as it was before anything changed.

type Replacer

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

Replacer is the helper's half: the container steps of R-359, in order. It holds no state of Pando's; the helper calls core between steps.

func NewReplacer

func NewReplacer() (*Replacer, error)

NewReplacer connects to the runtime the helper was started beside.

func (*Replacer) Discard

func (r *Replacer) Discard(ctx context.Context, id string)

Discard removes the new container, after a failed check.

func (*Replacer) Finish

func (r *Replacer) Finish(ctx context.Context, old Old, image, tag string) error

Finish points the deployment's moving tag at the new image and removes the old container. Only now: a tag moved before the check, then a rollback, and the next `docker compose up` starts the version that just failed.

func (*Replacer) Inspect

func (r *Replacer) Inspect(ctx context.Context, id string) (Old, error)

Inspect reads the container to be replaced.

func (*Replacer) Logs

func (r *Replacer) Logs(ctx context.Context, id string) string

Logs returns the end of a container's output, for the report of why an upgrade was rolled back.

func (*Replacer) Ready

func (r *Replacer) Ready(ctx context.Context, id string, port string, timeout time.Duration) error

Ready waits until the new container answers /readyz, which it does only after migrations and the audit-log check have passed, or until timeout or its exit.

func (*Replacer) Recreate

func (r *Replacer) Recreate(ctx context.Context, old Old, image string) (string, error)

Recreate creates and starts a container with the old one's configuration under image, and returns its ID.

Environment variables and labels the old image set are dropped rather than copied: they belong to the image, and copying them would pin the new container to the old image's values — its version label, its PATH.

func (*Replacer) Restore

func (r *Replacer) Restore(ctx context.Context, old Old) error

Restore gives the old container its name back and starts it.

func (*Replacer) Stop

func (r *Replacer) Stop(ctx context.Context, old Old) error

Stop stops the old container and moves its name aside, so the new one can take it — Compose finds its service's container by name and labels.

func (*Replacer) Version added in v0.14.0

func (r *Replacer) Version(ctx context.Context, id string) (string, error)

Version is the version the image a container runs says it is, read from the image rather than the container, whose labels a deployment may set; empty when the image has no version label.

Jump to

Keyboard shortcuts

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