score-spec / score-spec/score-compose
[feature request] Support Docker Compose native init containers (pre_start)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 456
- Forks
- 62
- Avg merge
- 12h 20m
- Merged PRs (30d)
- 11
Description
Is your feature request related to a problem? Please describe.
Docker Compose v5.3.0 shipped native init containers via a new pre_start field on services (release notes, docker/compose#13862). score-compose has been emulating this since #321 / #454: each ready: complete container becomes its own compose service, and the before relationship is inverted into a depends_on: service_completed_successfully on the target. That still works fine, but the output is more verbose than it needs to be now that Compose has a native primitive for exactly this. Follow-up from a Slack discussion with @mathieu-benoit.
Describe the solution you'd like
An opt-in annotation on the workload, read from metadata.annotations at conversion time. Current output stays the default.
apiVersion: score.dev/v1b1
metadata:
name: myapp
annotations:
compose.score.dev/init-containers: native
For a workload myapp where a db-migrate container declares before: {main: {ready: complete}}, today we generate:
services:
myapp-db-migrate:
image: migrate/migrate:v4
command: ["up"]
myapp-main:
image: myapp:latest
hostname: myapp
depends_on:
myapp-db-migrate:
condition: service_completed_successfully
required: true
With the annotation set, this becomes:
services:
myapp-main:
image: myapp:latest
hostname: myapp
pre_start:
- image: migrate/migrate:v4
command: ["up"]
Eligibility rules
Not every init container can be converted. Anything that fails the rules below keeps the current service-based output, with a log line explaining why:
- all of the container's
beforeentries areready: complete.pre_startonly models run-to-completion, sohealthyandstartedstay asdepends_onservices either way and the output is hybrid by design. - exactly one target container. This is not a Compose limitation as @glours notes below, each service declares its own hook list, so duplicating a declaration across services is intentional there. It stays a score-compose rule for a Score-side reason: an init container whose
beforepoints at two containers runs once today, and duplicating the hook onto both targets would run it twice. That is a behaviour change, not just a change in output shape.
Two rules from the original proposal have been dropped after upstream confirmation:
no-commandsetpre_starthooks now inherit the fullContainerSpec, so a hook can set its owncommand/entrypoint.no- hooks can declare their own mounts rather than only inheriting the target's viafilesorvolumesof its ownVolumesFrom.
Implementation plan
The generic ContainerSpec support for hooks is not in a tagged compose-go release yet, so this lands in two phases:
- Against the currently pinned
compose-gov2.13.0. Wire up the annotation, the conversion path, and the fallback logging, with all four of the original eligibility rules in place. This already covers the plain image-only init container, which is the commondb-migratecase. - Once generic
ContainerSpecsupport tags. Bump the dependency and delete thecommandandfiles/volumesrules. A narrow diff on top of phase 1, plus tests.
Because the opt-in is an annotation rather than a CLI flag, phase 2 requires no user-facing change.
Open question
The hook / service volume merge reportedly keeps only the volumes specific to the hook. It is not yet clear whether a hook and its target service can both mount the same named volume or bind the "migration writes into a shared directory that the app then reads" pattern. If they cannot, the files / volumes rule shrinks rather than disappearing, and score-compose keeps a fallback for init containers that intentionally share a volume with the main container. This needs an answer before phase 2.
Describe alternatives you've considered
A --native-init-containers flag on generate. Rejected in favour of the annotation: generate accepts multiple Score files in one call (score-compose generate score.yaml *.score.yaml), so a flag would force the same mode onto every workload in that run, while an annotation lets each workload opt in on its own. It also matches existing precedent such as compose.score.dev/publish-port and k8s.score.dev/kind, and needs no change to the command's parameters.
Making native output the default, or auto-detecting it: not really possible. generate cannot know which binary will consume the file. It might be an older Compose, or podman-compose (see #129), and pre_start needs v5.3.0 or later. So this has to be opt-in.
A mode annotation instead of a boolean-ish value (services | native) leaves more room to grow if Compose later supports per_replica. The value is already a string here, so this stays open without extra design work.
Additional context
One semantic difference worth documenting: Kubernetes initContainers run per replica, while pre_start runs once per service per_replica: true is explicitly rejected in Compose today. Identical for the usual single-replica dev loop, but worth a line in the docs so score-k8s and score-compose expectations stay aligned. Support for it is expected to land upstream at some point.
Happy to implement this. Phase 1 can start as soon as there is agreement on the annotation name and the phasing.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the generate workload conversion path and the metadata.annotations handling, then inspect the pinned compose-go v2.13.0 API. Done means the native mode is opt-in, eligible single-target complete init containers become pre_start hooks, and ineligible cases retain service-based output with fallback logging while the default remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker-compose, go
- Domain
- devops, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100