docker

package
v0.0.1-alpha.30 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package docker provides a thin Docker Engine API client.

Supported endpoints:

  • Unix socket: "/var/run/docker.sock" (Linux / macOS default)
  • Named pipe: "npipe:////./pipe/docker_engine" (Windows default)
  • TCP: "tcp://host:port" (DinD sidecars, all platforms)

This avoids pulling in the massive github.com/docker/docker SDK with its transitive dependencies (otel, protobuf, etc.). We only need a handful of API calls: create/start/stop/remove container, pull image, create/inspect network. The Docker Engine API is stable REST over a Unix socket or TCP.

Reference: https://docs.docker.com/engine/api/v1.45/

Index

Constants

View Source
const (
	// LabelManaged marks a resource as Overcast-managed.
	LabelManaged = "overcast.managed"
	// LabelService identifies which Overcast service owns the resource
	// (e.g. "lambda", "ecs", "rds", "ec2").
	LabelService = "overcast.service"
	// LabelResourceID identifies the logical resource that owns the
	// Docker resource (e.g. function name, ECS task ID, VPC ID).
	LabelResourceID = "overcast.resource-id"
)

Standard labels applied by Overcast services to Docker resources (containers and networks). The Docker watcher filters on LabelManaged so it only sees our resources.

Variables

This section is empty.

Functions

func DemuxStream

func DemuxStream(raw []byte) []byte

DemuxStream returns the payload of a multiplexed Docker stream with the frame headers removed, or raw unchanged when raw is not framed at all.

The declared payload length is only ever trusted as far as the bytes that are actually present. Callers bound their reads — ContainerLogs stops at 64 KiB — so the final frame of a long log routinely arrives cut short, and those are the newest lines, the ones someone opened the log to read. A remainder too short to hold a header is the other half of that cut: binary junk rather than output, so it is dropped instead of printed.

A header that does not validate part-way through ends the walk and the rest is returned as it is. A stream that desynchronised is still the container's own bytes, and swallowing them would lose exactly what is being looked for.

Use DemuxReader instead when the source is an io.Reader — it does the same job without holding the whole stream in memory.

func EndpointAliases

func EndpointAliases(addresses ...string) []string

EndpointAliases returns unique, non-IP hostnames suitable for Docker DNS aliases.

func IsConflict

func IsConflict(err error) bool

IsConflict reports whether an error is a Docker 409 Conflict response (e.g. container name already in use).

func IsImageMissingErr

func IsImageMissingErr(err error) bool

IsImageMissingErr reports whether a container-create failure means the image is gone from the daemon ("No such image": removed after it was pulled or verified present).

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether an error is a Docker 404 Not Found response.

Helpers in this package report a status two ways: doJSON and the by-name inspect build "…: 404: {body}", while every helper that drives doRequest itself builds "…: status 404". Matching only the first form meant this answered "no" for a 404 from StartContainer, StopContainer, ContainerLogs and the rest — so a start against a container Docker had already removed looked like an ordinary failure rather than the recoverable "it is gone, rebuild it" that it is.

func ManagedLabels

func ManagedLabels(service, resourceID string) map[string]string

ManagedLabels returns the standard Overcast labels for a Docker resource. All services should use this instead of constructing the map inline.

Types

type Client

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

Client is a lightweight Docker Engine API client.

func NewClient

func NewClient(endpoint string, logger *zap.Logger) *Client

NewClient creates a Docker client for the given endpoint.

The endpoint can be:

  • A Unix socket path: "/var/run/docker.sock" (Linux / macOS)
  • A Windows named pipe: "npipe:////./pipe/docker_engine" (Windows)
  • A TCP address: "tcp://host:port" (for DinD sidecars, all platforms)

Use the package-level defaultDockerSocket constant for the platform default.

func (*Client) Available

func (d *Client) Available(timeout time.Duration) bool

Available checks if the Docker daemon is reachable.

func (*Client) ConnectNetwork

func (d *Client) ConnectNetwork(ctx context.Context, networkID, containerID string) error

ConnectNetwork attaches a container to a network.

func (*Client) ConnectNetworkWithAliases

func (d *Client) ConnectNetworkWithAliases(ctx context.Context, networkID, containerID string, aliases []string) error

ConnectNetworkWithAliases attaches a container to a network with optional DNS aliases.

func (*Client) ConnectNetworkWithConfig

func (d *Client) ConnectNetworkWithConfig(ctx context.Context, networkID, containerID string, cfg *EndpointSettings) error

ConnectNetworkWithConfig attaches a container to a network with an explicit endpoint configuration — aliases, a pinned address, or both.

func (*Client) ContainerLogs

func (d *Client) ContainerLogs(ctx context.Context, id string, tail string) ([]byte, error)

ContainerLogs fetches container stdout+stderr logs (non-streaming).

func (*Client) ContainerLogsSince

func (d *Client) ContainerLogsSince(ctx context.Context, id string, since time.Time) (io.ReadCloser, error)

ContainerLogsSince fetches the full stdout+stderr log payload for a container starting from a given Unix timestamp (seconds). Used for reconciliation — after a streaming follower fails or on container teardown — to backfill any log frames that the streaming connection may have missed. Output includes per-line RFC3339Nano timestamps (timestamps=true) so the caller can deduplicate against events already delivered.

The response is a multiplexed Docker log stream identical in shape to ContainerLogsStream's body; wrap it in a DemuxReader to extract payload bytes.

func (*Client) ContainerLogsStream

func (d *Client) ContainerLogsStream(ctx context.Context, id string, since time.Time) (io.ReadCloser, error)

ContainerLogsStream opens a streaming connection to the container log endpoint with follow=true. The caller is responsible for closing the returned ReadCloser. When ctx is cancelled the underlying HTTP connection is closed automatically, which causes reads on the stream to return an error, making the reader goroutine exit cleanly without an explicit close call.

The since parameter (Unix seconds with nanosecond fraction) lets a caller resume after a stream failure without re-receiving lines that were already delivered. Pass time.Time{} for "from start of container".

func (*Client) ContainerMemoryUsage

func (d *Client) ContainerMemoryUsage(ctx context.Context, id string) (usageBytes int64, err error)

ContainerMemoryUsage returns the current memory usage (in bytes) of a container.

func (*Client) ContainerStatsOneShot

func (d *Client) ContainerStatsOneShot(ctx context.Context, id string) (ContainerStats, error)

ContainerStatsOneShot returns one stats sample for a container.

one-shot=true matters: with stream=false alone the daemon waits an extra collection cycle (~1–2 s) to pre-fill the CPU delta fields, which both put seconds on any synchronous caller and overran short caller timeouts on slow (Docker-in-Docker) hosts — surfacing as memory "0" everywhere it was used. One-shot returns the current sample immediately; the precpu fields it leaves zeroed are ones this client never read anyway.

func (*Client) CopyFileFromContainer

func (d *Client) CopyFileFromContainer(ctx context.Context, id, path string) ([]byte, error)

CopyFileFromContainer returns the raw bytes of a file path from inside a container using Docker's archive endpoint.

func (*Client) CopyToContainer

func (d *Client) CopyToContainer(ctx context.Context, id, destPath string, tarData io.Reader) error

CopyToContainer copies a tar archive into a container at the given path. This uses the Docker "Put Archive" API endpoint.

func (*Client) CreateContainer

func (d *Client) CreateContainer(ctx context.Context, name string, req *CreateContainerRequest) (string, error)

CreateContainer creates a container (does not start it).

func (*Client) CreateNetwork

func (d *Client) CreateNetwork(ctx context.Context, name string) (string, error)

CreateNetwork creates a Docker network. Returns the network ID. Ignores "already exists" errors.

func (*Client) CreateNetworkWithOptions

func (d *Client) CreateNetworkWithOptions(ctx context.Context, opts CreateNetworkOptions) (string, error)

CreateNetworkWithOptions creates a Docker network with full control over labels, CIDR, and internal mode. Returns the network ID. Ignores "already exists" errors.

func (*Client) CreateVolume

func (d *Client) CreateVolume(ctx context.Context, name string, labels map[string]string) error

CreateVolume creates a named Docker volume. Creating an existing name is a no-op on the daemon side (Docker returns the existing volume), which makes this safe to call from reconciliation paths.

func (*Client) DisconnectNetwork

func (d *Client) DisconnectNetwork(ctx context.Context, networkID, containerID string) error

DisconnectNetwork detaches a container from a network.

func (*Client) Exec

func (d *Client) Exec(ctx context.Context, id string, cmd, env []string) (ExecResult, error)

Exec runs a command inside a running container and waits for it to finish. It returns the command's own exit status and output — a non-zero ExitCode is not an error here, because "the command ran and refused" is an answer the caller has to be able to tell apart from "the command never ran".

cmd is passed to the container verbatim, with no shell in between: quoting and word splitting never happen, so an argument containing spaces, quotes or a `$` reaches the process as one argument exactly as written. env adds to the container's own environment for this command only.

The exec is created with a TTY, which is what `docker exec -t` does. It costs the ability to tell stdout from stderr — neither of which a caller of this method distinguishes — and buys a plain byte stream instead of Docker's 8-byte-framed multiplexed one.

func (*Client) GetContainerByName

func (d *Client) GetContainerByName(ctx context.Context, name string) (*ContainerInspect, error)

GetContainerByName looks up a container by its name (without the leading "/"). Returns (nil, nil) if no container with that name exists.

func (*Client) ImageExists

func (d *Client) ImageExists(ctx context.Context, image string) (bool, error)

ImageExists checks if an image exists locally.

func (*Client) ImageMatchesPlatform

func (d *Client) ImageMatchesPlatform(ctx context.Context, image, platform string) (bool, error)

ImageMatchesPlatform reports whether the local image tag exists and matches the requested Docker platform. Empty platform preserves ImageExists behavior.

func (*Client) Info

func (d *Client) Info(ctx context.Context) (*SystemInfo, error)

Info returns the daemon's system information (GET /info).

func (*Client) InspectContainer

func (d *Client) InspectContainer(ctx context.Context, id string) (*ContainerInspect, error)

InspectContainer returns container details.

func (*Client) InspectNetwork

func (d *Client) InspectNetwork(ctx context.Context, nameOrID string) (*NetworkInspect, error)

InspectNetwork returns network details.

func (*Client) ListContainers

func (d *Client) ListContainers(ctx context.Context, service string) ([]ContainerSummary, error)

ListContainers returns all containers (running and stopped) that carry overcast.managed=true and optionally overcast.service=<service>. Pass an empty service string to list across all services.

func (*Client) ListNetworks

func (d *Client) ListNetworks(ctx context.Context, service string) ([]NetworkSummary, error)

ListNetworks returns all Overcast-managed networks, optionally filtered by service.

func (*Client) ListVolumes

func (d *Client) ListVolumes(ctx context.Context, service string) ([]VolumeSummary, error)

ListVolumes returns managed volumes, optionally filtered to one service.

func (*Client) Ping

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

Ping checks Docker daemon connectivity.

func (*Client) PruneDanglingImages

func (d *Client) PruneDanglingImages(ctx context.Context) error

PruneDanglingImages removes all dangling (untagged) images. Equivalent to `docker image prune -f`.

Do NOT call this after a pull. "Dangling" means untagged, and an image pulled by digest ("repo@sha256:…") is untagged by definition, so a prune deletes the image the pull just fetched — the pull reports success and the container create that follows fails with "No such image". This used to run after every pull and made EFS's digest-pinned NFS export image unusable on any daemon with the classic image store (Docker Desktop's containerd store does not report digest-referenced images as dangling, so it only ever failed in CI).

The blast radius is wider than that one case: the filter is daemon-wide, so it also deletes the *user's* untagged images, which Overcast does not own, and it can race any service that has pulled an image but not yet created its container. Reclaiming disk is not worth either. Call it explicitly, if ever, and never on a path that is about to use an image.

func (*Client) PullImage

func (d *Client) PullImage(ctx context.Context, image string) error

PullImage pulls an image. This blocks until the pull is complete.

func (*Client) PullImageForPlatform

func (d *Client) PullImageForPlatform(ctx context.Context, image, platform string) error

PullImageForPlatform pulls an image for a specific Docker platform such as linux/amd64. Docker Engine expects platform in the images/create query string, not in a JSON body.

func (*Client) RemoveContainer

func (d *Client) RemoveContainer(ctx context.Context, id string, force bool) error

RemoveContainer removes a container. force=true kills it first if running.

func (*Client) RemoveContainerForce

func (d *Client) RemoveContainerForce(id string) error

RemoveContainerForce removes a container using a background context with a deadline, ensuring cleanup always succeeds even when the request context is cancelled. Use this for teardown/cleanup paths only.

func (*Client) RemoveNetwork

func (d *Client) RemoveNetwork(ctx context.Context, nameOrID string) error

RemoveNetwork removes a Docker network by name or ID.

func (*Client) RemoveVolume

func (d *Client) RemoveVolume(ctx context.Context, name string, force bool) error

RemoveVolume removes a named Docker volume. A missing volume is not an error, mirroring RemoveContainer's cleanup-friendly semantics.

func (*Client) StartContainer

func (d *Client) StartContainer(ctx context.Context, id string) error

StartContainer starts a previously created container.

func (*Client) StopContainer

func (d *Client) StopContainer(ctx context.Context, id string, timeoutSec int) error

StopContainer stops a running container with a timeout.

func (*Client) UpdateContainerResources

func (d *Client) UpdateContainerResources(ctx context.Context, id string, update *UpdateResourcesRequest) error

UpdateContainerResources updates resource limits on a running container. Only the non-zero fields in the request are applied; zero values are ignored by the Docker daemon. Mirrors POST /containers/{id}/update.

func (*Client) WaitContainer

func (d *Client) WaitContainer(ctx context.Context, id string) (int, error)

WaitContainer blocks until a container exits. Returns the exit code.

type ContainerConfig

type ContainerConfig struct {
	Image        string              `json:"Image"`
	Env          []string            `json:"Env,omitempty"`
	Cmd          []string            `json:"Cmd,omitempty"`
	Entrypoint   []string            `json:"Entrypoint,omitempty"`
	WorkingDir   string              `json:"WorkingDir,omitempty"`
	User         string              `json:"User,omitempty"`
	ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"`
	Labels       map[string]string   `json:"Labels,omitempty"`
}

ContainerConfig describes the container's runtime configuration.

type ContainerInspect

type ContainerInspect struct {
	ID     string            `json:"Id"`
	Name   string            `json:"Name"` // e.g. "/overcast-rds-mydb"
	Labels map[string]string `json:"Labels"`
	Config struct {
		Labels map[string]string `json:"Labels"`
	} `json:"Config"`
	State struct {
		Status     string `json:"Status"` // "created", "running", "exited", etc.
		Running    bool   `json:"Running"`
		ExitCode   int    `json:"ExitCode"`
		Error      string `json:"Error"`     // runtime error, e.g. "OCI runtime create failed: ..."
		OOMKilled  bool   `json:"OOMKilled"` // true if the kernel OOM-killer terminated the container
		StartedAt  string `json:"StartedAt"`
		FinishedAt string `json:"FinishedAt"`
	} `json:"State"`
	HostConfig struct {
		Binds []string `json:"Binds"`
	} `json:"HostConfig"`
	NetworkSettings struct {
		// Networks is keyed by network *name*; ContainerNetwork.NetworkID is
		// the only way to match an entry against a network known by ID.
		Networks map[string]ContainerNetwork `json:"Networks"`
		// Ports maps "containerPort/proto" → list of host bindings.
		// e.g. "3306/tcp" → [{"HostIp":"0.0.0.0","HostPort":"33060"}]
		Ports map[string][]PortBinding `json:"Ports"`
	} `json:"NetworkSettings"`
}

ContainerInspect holds container state and networking details.

func (*ContainerInspect) HasOvercastLabels

func (c *ContainerInspect) HasOvercastLabels(service, resourceID string) bool

HasOvercastLabels reports whether the container was created by Overcast with the given service name and resource ID. Use this before reusing a container found by name to avoid accidentally attaching to a user-created container that happens to share the same name.

type ContainerNetwork

type ContainerNetwork struct {
	NetworkID string `json:"NetworkID"`
	IPAddress string `json:"IPAddress"`
}

ContainerNetwork is one entry of a container's NetworkSettings.Networks.

type ContainerStats

type ContainerStats struct {
	// MemoryUsageBytes is cgroup usage minus inactive file cache — the same
	// number `docker stats` shows, and closer to what a process actually
	// occupies than the raw cgroup counter, which grows with page cache.
	MemoryUsageBytes int64
	// CPUTotalUsage is cumulative container CPU time in nanoseconds.
	CPUTotalUsage uint64
	// SystemCPUUsage is cumulative host CPU time in nanoseconds.
	SystemCPUUsage uint64
	// OnlineCPUs is the number of CPUs available to the container.
	OnlineCPUs int
}

ContainerStats is a single point-in-time resource sample for a container. CPU counters are cumulative; a rate needs two samples (see the docker CLI formula: Δcpu_total / Δsystem_cpu × online_cpus × 100).

type ContainerSummary

type ContainerSummary struct {
	ID     string            `json:"Id"`
	Names  []string          `json:"Names"` // e.g. ["/overcast-rds-mydb"]
	Image  string            `json:"Image"`
	State  string            `json:"State"`  // "running", "exited", "created", etc.
	Status string            `json:"Status"` // human-readable, e.g. "Up 2 hours"
	Labels map[string]string `json:"Labels"`
	Ports  []struct {
		HostPort      int    `json:"PublicPort"`
		ContainerPort int    `json:"PrivatePort"`
		Type          string `json:"Type"`
	} `json:"Ports"`
}

ContainerSummary is the lightweight container representation returned by GET /containers/json (list endpoint), as opposed to the full ContainerInspect returned by GET /containers/{id}/json.

func (*ContainerSummary) FirstName

func (c *ContainerSummary) FirstName() string

FirstName returns the primary container name without the leading slash.

func (*ContainerSummary) ResourceID

func (c *ContainerSummary) ResourceID() string

ResourceID returns the overcast.resource-id label value (empty string if not set).

func (*ContainerSummary) Service

func (c *ContainerSummary) Service() string

Service returns the overcast.service label value (empty string if not set).

type CreateContainerRequest

type CreateContainerRequest struct {
	*ContainerConfig
	HostConfig       *HostConfig       `json:"HostConfig,omitempty"`
	NetworkingConfig *NetworkingConfig `json:"NetworkingConfig,omitempty"`
	Platform         string            `json:"-"`
}

CreateContainerRequest combines all container creation parameters.

type CreateContainerResponse

type CreateContainerResponse struct {
	ID       string   `json:"Id"`
	Warnings []string `json:"Warnings,omitempty"`
}

CreateContainerResponse is the response from container creation.

type CreateNetworkOptions

type CreateNetworkOptions struct {
	Name     string
	Labels   map[string]string // nil = no labels
	Subnet   string            // CIDR, e.g. "10.0.0.0/16"; empty = Docker default
	Internal bool              // true = no outbound internet access
}

CreateNetworkOptions configures a Docker network.

type DaemonConnectedPayload

type DaemonConnectedPayload struct {
	Client      *Client `json:"-"`
	Reconnected bool    `json:"reconnected"`
}

DaemonConnectedPayload identifies the Docker client whose event stream has connected. A process can watch multiple daemon sockets, so services compare this pointer with the client they were wired to before reconciling.

type DemuxReader

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

DemuxReader strips Docker's frame headers from a multiplexed stream as it is read, so a bufio.Scanner or bufio.Reader on top of it sees the container's own bytes and can assemble lines across frame boundaries — which it has to, because one line is not one frame: a JSON record from a structured logger routinely spans several.

Reads are served from the caller's own buffer and the type allocates nothing per Read; every line of every Lambda invocation goes through here.

An unframed (TTY) stream is recognised at the first header-sized read and copied through verbatim from then on.

func NewDemuxReader

func NewDemuxReader(r io.Reader) *DemuxReader

NewDemuxReader wraps r, which may be a framed stream or a TTY one.

func (*DemuxReader) Read

func (d *DemuxReader) Read(p []byte) (int, error)

type EndpointIPAMConfig

type EndpointIPAMConfig struct {
	IPv4Address string `json:"IPv4Address,omitempty"`
}

EndpointIPAMConfig requests a specific address on a network. Docker rejects the connect outright when the address is outside the network's subnet or already taken, so callers that cannot guarantee either must be prepared to retry without it.

type EndpointSettings

type EndpointSettings struct {
	// Empty settings are enough to attach to a network. Aliases are advertised by
	// Docker's embedded DNS to containers on the same user-defined network.
	Aliases []string `json:"Aliases,omitempty"`
	// IPAMConfig pins the address the container gets on this network. Left nil,
	// Docker's own IPAM picks one.
	IPAMConfig *EndpointIPAMConfig `json:"IPAMConfig,omitempty"`
}

EndpointSettings describes a container's attachment to a Docker network.

type ExecResult

type ExecResult struct {
	// ExitCode is the command's exit status. Zero means it succeeded.
	ExitCode int
	// Output is stdout and stderr interleaved, as a terminal would show them,
	// bounded by maxExecOutputBytes. It is what a caller quotes when the
	// command failed, so it is the command's own explanation and not a
	// paraphrase of it.
	Output string
}

ExecResult reports what a command run inside a container did.

type GC

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

GC manages async Docker container cleanup. Services schedule containers for removal and the GC handles stop+remove in background goroutines:

  • StopNow: fires immediately in a dedicated goroutine (non-blocking). A running container can still execute code — stop it ASAP.
  • ScheduleRemove: enqueued and processed at leisure by the background loop. Failures are re-enqueued for retry (up to 3 attempts).

DrainAndSweep is called at shutdown: it drains the remove queue and then removes every managed container (Docker-level sweep), catching any orphans.

Zero value is invalid — use NewGC.

func NewGC

func NewGC(client *Client, logger *zap.Logger, keepContainers bool) *GC

NewGC creates a GC tied to a Docker client. keepContainers=true means containers are never removed — stop only (useful for debugging / post-mortem inspection).

func (*GC) DrainAndSweep

func (g *GC) DrainAndSweep(ctx context.Context, service string)

DrainAndSweep shuts down the GC and removes every managed container for the given service. service="" matches all services. Blocks until complete or ctx expires during the drain phase.

Call from each service's Stop() method — this is the safety net that catches any container whose store record was already deleted but whose Docker container was never cleaned up.

Once DrainAndSweep returns the GC is inert; further StopNow / ScheduleRemove calls are no-ops.

func (*GC) ScheduleRemove

func (g *GC) ScheduleRemove(containerID string)

ScheduleRemove enqueues a container for async removal. The background loop picks it up when it can — removal is not urgent once the container is stopped. Non-blocking. If the remove queue is full the request is dropped (logged).

func (*GC) StartRemoveLoop

func (g *GC) StartRemoveLoop(ctx context.Context)

StartRemoveLoop begins the background remove worker. It blocks until ctx is cancelled or the GC is shut down. Safe to call multiple times — each call starts an independent worker goroutine tracked by the internal WaitGroup.

func (*GC) StopAndScheduleRemove

func (g *GC) StopAndScheduleRemove(containerID string)

StopAndScheduleRemove stops a container immediately (to halt any code running inside) and then queues it for deferred removal with exponential backoff. The stop fires in a dedicated goroutine so the caller can proceed without waiting for Docker to respond. The deferred removal retries indefinitely until the GC shuts down or the container is gone.

func (*GC) StopNow

func (g *GC) StopNow(containerID string)

StopNow fires an async StopContainer in its own goroutine and returns immediately. Call from a delete handler before returning the response. Failures are logged at debug level — the remove loop will force-remove the container regardless of stop state.

func (*GC) Sweep

func (g *GC) Sweep(service string)

Sweep removes every managed container for the given service without closing the GC. Call at startup to clean up orphaned containers from prior runs. service="" matches all services. Non-blocking — runs in a goroutine.

func (*GC) SweepExcept

func (g *GC) SweepExcept(service string, keep func(resourceID string) bool)

SweepExcept is Sweep with a veto. keep is asked about each candidate's resource ID and reports whether something live still owns it; those are left alone. Pass nil to sweep every non-running container, which is Sweep.

A stopped container is not automatically litter. Compute that is recreated on demand — an ECS task, a Lambda runtime — has no attachment to the container it last ran in, so state alone is a fair test there. A database does: a stopped RDS DB instance is a resource the user still owns and expects to start again, and its container has to outlive an Overcast restart. Sweeping it left the instance record pointing at an ID Docker no longer had, after which every start failed, every log fetch 404'd, and the instance still claimed to be available.

type HostConfig

type HostConfig struct {
	Binds        []string                 `json:"Binds,omitempty"`
	NetworkMode  string                   `json:"NetworkMode,omitempty"`
	Memory       int64                    `json:"Memory,omitempty"`     // bytes
	MemorySwap   int64                    `json:"MemorySwap,omitempty"` // bytes (-1 = unlimited)
	NanoCPUs     int64                    `json:"NanoCPUs,omitempty"`   // 1e9 = 1 CPU
	AutoRemove   bool                     `json:"AutoRemove,omitempty"`
	PortBindings map[string][]PortBinding `json:"PortBindings,omitempty"`
	Privileged   bool                     `json:"Privileged,omitempty"` // required by k3s
	Tmpfs        map[string]string        `json:"Tmpfs,omitempty"`      // tmpfs mounts (path → options)
	// CapAdd grants individual Linux capabilities on top of Docker's default
	// set, named without the "CAP_" prefix (e.g. "DAC_READ_SEARCH"). Prefer it
	// to Privileged: one capability is auditable, --privileged is not.
	CapAdd []string `json:"CapAdd,omitempty"`
	// ExtraHosts are "hostname:target" entries written into the container's
	// /etc/hosts, where target is an IP or Docker's "host-gateway". /etc/hosts
	// wins over DNS in glibc and musl, so an entry here shadows a public record
	// for the same name inside this container only.
	ExtraHosts []string `json:"ExtraHosts,omitempty"`
	// Dns sets the container's resolvers. Docker keeps its own embedded
	// resolver (127.0.0.11) in front and uses these as its upstream, so
	// container-name service discovery is unaffected — but names these servers
	// claim are answered by them, including wildcard subdomains that ExtraHosts
	// cannot express. See internal/dns.
	Dns []string `json:"Dns,omitempty"`
	// Mounts is the structured alternative to Binds. Required for named-volume
	// mounts that need VolumeOptions (e.g. Subpath); plain binds can stay in
	// Binds — Docker merges both.
	Mounts []Mount `json:"Mounts,omitempty"`
}

HostConfig describes the host-side container configuration.

type ImageInspect

type ImageInspect struct {
	Architecture string `json:"Architecture"`
	OS           string `json:"Os"`
}

ImageInspect holds the platform metadata returned by Docker image inspect.

type ImagePuller

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

ImagePuller deduplicates Docker image pulls. It ensures each image is pulled at most once per process lifetime. Services that run containers (RDS, ECS, Lambda) should share an ImagePuller rather than duplicating the sync.Map + sync.Once pattern.

func NewImagePuller

func NewImagePuller(c *Client) *ImagePuller

NewImagePuller creates a puller backed by the given Docker client.

func (*ImagePuller) CreateContainerWithRetry

func (p *ImagePuller) CreateContainerWithRetry(ctx context.Context, name string, req *CreateContainerRequest) (string, error)

CreateContainerWithRetry creates a container and, when the daemon reports its image missing — removed behind our back after the pull was recorded — forgets the stale pull record, re-pulls, and retries the create once. Without this, `docker rmi` of a task or DB image makes every later launch fail until restart, because the spent pull record short-circuits Ensure.

func (*ImagePuller) Ensure

func (p *ImagePuller) Ensure(ctx context.Context, image string) error

Ensure pulls image if it hasn't been pulled yet. Concurrent calls for the same image block until the first pull completes and share its result. A FAILED pull drops the entry, so the next launch attempt retries instead of serving the cached error until restart — pulls are driven by user actions (RunTask, StartDBInstance, Invoke), not loops, so this cannot hammer a registry; what it prevents is one transient network failure bricking an image for the process lifetime.

func (*ImagePuller) Invalidate

func (p *ImagePuller) Invalidate(image string)

Invalidate forgets that image was pulled, so the next Ensure pulls again. Call when the daemon proves the cached knowledge wrong — a create failing with "No such image" after the image was removed behind our back.

func (*ImagePuller) Prewarm

func (p *ImagePuller) Prewarm(image string)

Prewarm starts Ensure in a background goroutine using a detached context so the pull is not tied to any caller's request deadline. Safe to call from request handlers at resource-creation time (CreateFunction, RegisterTaskDefinition, CreateDBInstance). If the same image is requested again on the invoke path, the caller blocks on the same sync.Once and reuses the in-flight pull.

type Mount

type Mount struct {
	// Type is "volume", "bind", or "tmpfs"; Overcast uses "volume".
	Type     string `json:"Type"`
	Source   string `json:"Source"` // volume name (for Type "volume")
	Target   string `json:"Target"` // absolute path inside the container
	ReadOnly bool   `json:"ReadOnly,omitempty"`
	// VolumeOptions apply when Type is "volume".
	VolumeOptions *MountVolumeOptions `json:"VolumeOptions,omitempty"`
}

Mount is one entry of HostConfig.Mounts (Engine API mounts specification).

type MountVolumeOptions

type MountVolumeOptions struct {
	// Subpath mounts only the named subdirectory of the volume (Engine API
	// v1.45+). The subdirectory must already exist in the volume — the daemon
	// rejects the mount otherwise.
	Subpath string `json:"Subpath,omitempty"`
}

MountVolumeOptions holds volume-mount-specific options.

type NetworkIPAM

type NetworkIPAM struct {
	Config []NetworkIPAMConfig `json:"Config"`
}

NetworkIPAM describes IP address management for a Docker network.

type NetworkIPAMConfig

type NetworkIPAMConfig struct {
	Subnet  string `json:"Subnet"`
	Gateway string `json:"Gateway"`
}

NetworkIPAMConfig describes one IPAM pool.

type NetworkInspect

type NetworkInspect struct {
	ID       string            `json:"Id"`
	Name     string            `json:"Name"`
	Internal bool              `json:"Internal"`
	Labels   map[string]string `json:"Labels"`
	IPAM     NetworkIPAM       `json:"IPAM"`
}

NetworkInspect holds Docker network details.

type NetworkSummary

type NetworkSummary struct {
	ID     string            `json:"Id"`
	Name   string            `json:"Name"`
	Labels map[string]string `json:"Labels"`
	IPAM   NetworkIPAM       `json:"IPAM"`
}

NetworkSummary is a lightweight network representation used by ListNetworks.

func (*NetworkSummary) ResourceID

func (n *NetworkSummary) ResourceID() string

ResourceID returns the overcast.resource-id label value (empty string if not set).

func (*NetworkSummary) Service

func (n *NetworkSummary) Service() string

Service returns the overcast.service label value (empty string if not set).

func (*NetworkSummary) Subnet

func (n *NetworkSummary) Subnet() string

Subnet returns the first IPAM subnet for the network, or empty if unset.

type NetworkingConfig

type NetworkingConfig struct {
	EndpointsConfig map[string]*EndpointSettings `json:"EndpointsConfig,omitempty"`
}

NetworkingConfig specifies the container's networking configuration.

type PortBinding

type PortBinding struct {
	HostIP   string `json:"HostIp,omitempty"`
	HostPort string `json:"HostPort,omitempty"`
}

PortBinding represents a host-to-container port mapping.

type ProbeResult

type ProbeResult struct {
	Client    *Client
	NetworkID string // Docker network ID
}

ProbeResult is returned by Probe on success.

func Probe

func Probe(socketPath, network string, logger *zap.Logger) (*ProbeResult, error)

Probe creates a Docker client, verifies connectivity with retries, and ensures the named network exists. This is the common bootstrap pattern shared by Lambda, ECS, and RDS.

Returns nil with a logged warning (not an error) when Docker is unreachable — callers degrade gracefully (metadata ops work, container ops return errors).

type ServiceConfig

type ServiceConfig struct {
	// Name is used for logging ("rds", "ecs", "lambda").
	Name string
	// Socket is the Docker daemon socket path (e.g. /var/run/docker.sock).
	Socket string
	// Network is the Docker network to create for this service.
	Network string
}

ServiceConfig describes a single service's Docker requirements. The Supervisor uses this to probe the socket, create the network, and wire the Docker client into the service.

type ServiceResult

type ServiceResult struct {
	Name      string
	Client    *Client
	NetworkID string
}

ServiceResult is returned per-service after a successful probe.

type Supervisor

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

Supervisor centralises Docker lifecycle management for the entire process. It deduplicates probes (one per unique socket path), creates per-service networks, runs a single event watcher per Docker daemon, and provides startup reconciliation.

Usage:

sup := docker.NewSupervisor(bus, logger)
results := sup.Probe(ctx, []ServiceConfig{...})
// wire results into services
sup.Run(ctx)   // blocks — starts watchers; returns when ctx is done
sup.Close()    // called during shutdown

func NewSupervisor

func NewSupervisor(bus *events.Bus, logger *zap.Logger) *Supervisor

NewSupervisor creates a Supervisor that will publish Docker container events on the provided bus.

func (*Supervisor) Close

func (s *Supervisor) Close()

Close signals all background goroutines (including Probe blockers and watchers) to stop. Safe to call before, during, or after Run.

func (*Supervisor) Probe

func (s *Supervisor) Probe(ctx context.Context, configs []ServiceConfig) []ServiceResult

Probe probes Docker for each ServiceConfig. Configs sharing the same socket path reuse a single client connection and share a single availability probe. Each config gets its own network created. Returns one ServiceResult per successful config. Configs that fail to probe are logged and skipped.

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context)

Run starts one Watcher goroutine per unique Docker client. It blocks until ctx is cancelled or Close is called. Call this from a goroutine after Probe.

type SystemInfo

type SystemInfo struct {
	// NCPU is the number of logical CPUs available to the daemon.
	NCPU int `json:"NCPU"`
	// MemTotal is the total memory available to the daemon, in bytes.
	MemTotal int64 `json:"MemTotal"`
}

SystemInfo is the subset of GET /info Overcast reads: the resources of the machine the daemon runs containers on. That machine is not necessarily the one the Overcast process runs on — with Docker Desktop it is the desktop VM, with a DinD sidecar or a tcp:// endpoint it is another host entirely — so sizing decisions about containers must come from here, never from runtime.NumCPU() or the process's own view of memory.

type UpdateResourcesRequest

type UpdateResourcesRequest struct {
	NanoCPUs   int64 `json:"NanoCPUs,omitempty"`   // 1e9 = 1 CPU
	Memory     int64 `json:"Memory,omitempty"`     // bytes
	MemorySwap int64 `json:"MemorySwap,omitempty"` // bytes (-1 = unlimited)
}

UpdateResourcesRequest contains the resource fields that can be changed on a running container via the Docker Engine API POST /containers/{id}/update.

type VolumeSummary

type VolumeSummary struct {
	Name   string            `json:"Name"`
	Labels map[string]string `json:"Labels"`
}

VolumeSummary is one entry from GET /volumes.

func (*VolumeSummary) ResourceID

func (v *VolumeSummary) ResourceID() string

ResourceID returns the owning resource ID from the managed labels.

func (*VolumeSummary) Service

func (v *VolumeSummary) Service() string

Service returns the owning service name from the managed labels.

type Watcher

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

Watcher listens to the Docker Engine events stream and publishes typed events on an events.Bus. Only Overcast-managed resources (those with the overcast.managed label) are tracked — both containers and networks.

Usage:

w := docker.NewWatcher(client, bus, logger)
go w.Run(ctx) // blocks until ctx is cancelled

func NewWatcher

func NewWatcher(client *Client, bus *events.Bus, logger *zap.Logger) *Watcher

NewWatcher creates a Watcher that translates Docker container and network events into bus events. Call Run to start watching.

func (*Watcher) Run

func (w *Watcher) Run(ctx context.Context)

Run connects to the Docker events stream and publishes bus events for managed containers. It reconnects automatically with exponential backoff when the stream drops. Run blocks until ctx is cancelled.

Jump to

Keyboard shortcuts

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