docker / docker/compose

Use annotations instead of labels for Compose-managed container runtime state

Open
#14,050 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

kind/feature needs-engine-feature
Dominant language
Go
Stars
38.2k
Forks
5.8k
Avg merge
2d 14h
Merged PRs (30d)
55

Description

Context

Spun off from https://github.com/docker/compose/pull/13959#issuecomment-5296188206 (@thaJeztah):

compose is already heavily depending on labels to store state, but we should start looking if we could transition to using Annotations, especially for state that shouldn't be persisted (doing a docker commit on a container, or running a container from an image that already has labels set could probably still provide "interesting" results)

Problem

Compose stores both identity and runtime state as container labels (com.docker.compose.project, service, container-number, oneoff, config-hash, image, depends_on, replace, version, project.working_dir, project.config_files, environment_file, …). Labels have two persistence problems annotations don't have:

  • docker commit on a Compose-managed container bakes all of this state into the resulting image;
  • containers inherit image labels, so running a container from such an image (or any image shipping com.docker.compose.* labels) makes it look like Compose-managed state: wrong config-hash comparisons, orphan detection, ps/down matching, etc.

HostConfig.Annotations is per-container, non-identifying runtime metadata — exactly the right home for state that must never leak into images.

Proposal

Split the current label set by role:

  • Runtime state → annotations: config-hash, image, depends_on, replace, container-number, oneoff, version, project.working_dir, project.config_files, environment_file — none of these should survive a docker commit.
  • Identity stays as labels (for now): project and service are used in server-side filters (docker ps --filter label=…, events, networks/volumes listing); the Engine API has no annotation-based filtering, and annotations don't exist on networks/volumes/images at all. Per the original comment, containerd namespaces may eventually be the right primitive for project scoping — that's engine-side work to track separately.
Backward compatibility

Migration must be invisible to users with running stacks:

  1. Phase 1 (dual write, fallback read): new containers get both labels and annotations; all readers prefer the annotation and fall back to the label. Stacks created by older Compose keep working (up recreates, down, ps).
  2. Phase 2 (annotation-first): stop writing the state labels on new containers; keep the fallback read path for at least a major-version window.
  3. Filters keep using the identity labels throughout; nothing changes for docker ps --filter label=com.docker.compose.project=… consumers, and third-party tooling relying on identity labels is unaffected.

A container recreated by a newer Compose gets the new layout; mixed projects (old + new containers) must reconcile correctly during phase 1, which the fallback read gives us.

Open questions
  • Exact split for container-number/oneoff: they are identity-adjacent but not used in server-side filters everywhere — audit each call site.
  • Annotations are only available for containers: networks, volumes and images keep labels regardless (their divergence-detection labels like com.docker.compose.network/volume and config-hash stay as-is).
  • Whether third-party ecosystems read the state labels (not just identity) — a deprecation note in release notes should cover phase 2.
Known direct label consumers

Compose labels were never a documented API, yet some external tools are known to read com.docker.compose.* labels directly from docker ps output instead of using compose ps or another supported interface — Docker Desktop has been identified as one such consumer. These consumers will need to be updated; the phase 1 dual-write window is their migration period, and does not turn the state labels into a compatibility commitment.

Blocker: the Engine API has no annotation filter

ContainerList returns annotations since Engine v27.0.0 (moby/moby ca0529f984, HostConfig.Annotations in each summary — verified empirically against v29.6.1 / API 1.55), so list-driven reads keep working once state moves to annotations. But server-side filtering by annotation does not exist (invalid filter 'annotation'; label only). This is a blocker for moving any filtered label, and Compose filters on more than identity today:

Label Filter kind Server-side usage
com.docker.compose.project equality + presence ContainerList (all lookups, ls project discovery), NetworkList, VolumeList, ImageList (pruner, watch), Events (up monitor), stats
com.docker.compose.service equality ContainerList (getDefaultFilters), ImageList (watch), stats
com.docker.compose.oneoff equality ContainerList (getDefaultFilters, start, monitor initial state), Events (up monitor)
com.docker.compose.config-hash presence ("compose-managed" marker) ContainerList: ls, getDefaultFilters (i.e. virtually every container lookup), monitor initial state
com.docker.compose.container-number equality (--index lookup) ContainerList (getSpecifiedContainer)
com.docker.compose.network equality NetworkList (down)

Consequence: two labels from the runtime state basket are filtered on today. Migrating them requires either an annotation filter landing engine-side, or Compose first rewriting those filter usages — the config-hash presence marker can be replaced by the project label presence, and the container-number equality by client-side selection over a service-scoped list. Both rewrites are Compose-local and should land as a phase 1 prerequisite; identity labels (project, service, oneoff, network) stay labels regardless until the engine offers annotation filtering.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by auditing Compose's label writers and readers, especially the ContainerList filtering and lookup paths listed in the issue, then verify the Engine API's annotation support and compatibility constraints. Done means a defined migration plan that preserves identity-label filtering, supports dual-write and fallback reads, and resolves or explicitly tracks the annotation-filter blocker.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, go
Domain
devops, infrastructure
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.