What this issue is about
pre_start hooks support a per_replica attribute in the compose-spec model (types.PreStartHook.PerReplica, compose-go's types/hooks.go): "runs the hook once per service replica instead of once per service." docker/compose's engine, however, does not implement it: runPreStart (pkg/compose/pre_start.go) validates every declared hook up front and rejects the whole service if any hook sets per_replica: true:
for i, hook := range service.PreStart {
if hook.PerReplica {
return fmt.Errorf("service %q pre_start[%d]: per_replica is not yet supported; remove per_replica or set it to false", service.Name, i)
}
}
So today, per_replica: true is accepted by the loader/schema but always a hard error at runtime — there is no path to actually use it.
Illustration
services:
app:
image: alpine
command: sleep infinity
deploy:
replicas: 3
pre_start:
- image: alpine
command: ["sh", "-c", "echo initializing replica"]
per_replica: true
$ docker compose up -d
service "app" pre_start[0]: per_replica is not yet supported; remove per_replica or set it to false
The only way to get this compose file to run at all today is to drop per_replica: true, at which point the hook runs once for the whole service (against the lowest-numbered replica only, per the current one-hook-one-runner model) instead of once per replica — a different, sometimes unacceptable behavior for hooks that need to prepare per-replica state (e.g. per-replica config files, per-replica volume initialization).
Suggested scope for an implementation
The reconciliation-plan work in #14221 (pre_start hook runners as first-class plan resources) planned one CreateHookContainer operation per declared hook, targeted at a single resolved replica. Supporting per_replica: true would mean planning one runner per hook × per replica instead, with:
- a deterministic name/label scheme distinguishing replicas (e.g. extending the existing
com.docker.compose.hook-index label with a replica index, or a dedicated label),
VolumesFrom resolved against that replica rather than the lowest-numbered one,
- the corresponding execution-phase change in
runPreStart/execPreStartHook to run and account for one runner per replica.
This issue only tracks the feature gap; the reconciliation-plan groundwork from #14221 already discussed and worked around it (see its planPreStartHookRunners, which explicitly skips planning entirely for a service with any per_replica: true hook, to avoid orphaning runners that could never execute).
What this issue is about
pre_starthooks support aper_replicaattribute in the compose-spec model (types.PreStartHook.PerReplica, compose-go'stypes/hooks.go): "runs the hook once per service replica instead of once per service." docker/compose's engine, however, does not implement it:runPreStart(pkg/compose/pre_start.go) validates every declared hook up front and rejects the whole service if any hook setsper_replica: true:So today,
per_replica: trueis accepted by the loader/schema but always a hard error at runtime — there is no path to actually use it.Illustration
The only way to get this compose file to run at all today is to drop
per_replica: true, at which point the hook runs once for the whole service (against the lowest-numbered replica only, per the current one-hook-one-runner model) instead of once per replica — a different, sometimes unacceptable behavior for hooks that need to prepare per-replica state (e.g. per-replica config files, per-replica volume initialization).Suggested scope for an implementation
The reconciliation-plan work in #14221 (pre_start hook runners as first-class plan resources) planned one
CreateHookContaineroperation per declared hook, targeted at a single resolved replica. Supportingper_replica: truewould mean planning one runner per hook × per replica instead, with:com.docker.compose.hook-indexlabel with a replica index, or a dedicated label),VolumesFromresolved against that replica rather than the lowest-numbered one,runPreStart/execPreStartHookto run and account for one runner per replica.This issue only tracks the feature gap; the reconciliation-plan groundwork from #14221 already discussed and worked around it (see its
planPreStartHookRunners, which explicitly skips planning entirely for a service with anyper_replica: truehook, to avoid orphaning runners that could never execute).