score-spec / score-spec/score-compose

[feature request] Support Docker Compose native init containers (pre_start)

Open
#522 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

discussion enhancement question
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 before entries are ready: complete. pre_start only models run-to-completion, so healthy and started stay as depends_on services 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 before points 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 command set - pre_start hooks now inherit the full ContainerSpec, so a hook can set its own command / entrypoint.
  • no files or volumes of its own - hooks can declare their own mounts rather than only inheriting the target's via VolumesFrom.

Implementation plan

The generic ContainerSpec support for hooks is not in a tagged compose-go release yet, so this lands in two phases:

  1. Against the currently pinned compose-go v2.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 common db-migrate case.
  2. Once generic ContainerSpec support tags. Bump the dependency and delete the command and files / volumes rules. 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

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.