Skip to content

docker, tini: link docker-init statically to fix docker run --init - #1661

Open
dcasota wants to merge 1 commit into
vmware:5.0from
dcasota:fix/docker-init-static-5.0
Open

dcasota wants to merge 1 commit into
vmware:5.0from
dcasota:fix/docker-init-static-5.0

Conversation

@dcasota

@dcasota dcasota commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Part of #1605.

docker run --init (and compose init: true) fails on any container whose rootfs has no glibc loader:

exec /sbin/docker-init: no such file or directory

/sbin/docker-init is not a host path

It is the hardcoded in-container destination. From moby daemon/oci_linux.go:

const inContainerInitPath = "/sbin/" + dconfig.DefaultInitBinary  // "/sbin/docker-init"

s.Process.Args = append([]string{inContainerInitPath, "--", c.Path}, c.Args...)
path, err := daemonCfg.LookupInitPath() // resolves the HOST binary
s.Mounts = append(s.Mounts, specs.Mount{
    Destination: inContainerInitPath,   // /sbin/docker-init INSIDE the container
    Type: "bind", Source: path, Options: []string{"bind", "ro"},
})

LookupInitPath() searches the libexec dirs and then $PATH, finds /usr/bin/docker-init, and bind-mounts it (the symlink is resolved by the mount) into the container, which then execs it as PID 1.

Why it fails

/usr/bin/docker-init is a dynamic PIE, so the kernel must load /lib64/ld-linux-x86-64.so.2 from the container's rootfs. Alpine only ships /lib/ld-musl-x86_64.so.1. execve reports a missing ELF interpreter as ENOENT, which surfaces as "no such file or directory" for a file that is plainly present.

Reproduced on Photon 5.0 with docker-29.5.3-1.ph5 / tini-0.19.0-2.ph5:

# docker run --rm --init nginx:1-alpine true
exec /sbin/docker-init: no such file or directory

# docker run --rm --init debian:12 true            <- glibc image
(succeeds)

# docker run --rm -v /usr/bin/tini:/t:ro alpine:3 sh -c '/t --version'
sh: /t: not found                                   <- same ENOENT, no --init involved

# docker run --rm alpine:3 ls /lib64/ld-linux-x86-64.so.2
ls: /lib64/ld-linux-x86-64.so.2: No such file or directory

debian:12 succeeding is the control: a wrong host path would break every image, not only those without a glibc loader.

Where it regressed

Commit b3da2fa ("tini: Initial commit / Also seperate out tini from docker spec"), released as docker-27.3.1-5.ph5. Before it, docker.spec built tini itself and installed a statically linked binary:

make tini-static %{?_smp_mflags}
cp tini-static "$GOPATH/bin/docker-init"
install -p -m 755 bin/docker-init %{buildroot}%{_bindir}/docker-init

after it, docker-init became a symlink to the dynamically linked /usr/bin/tini, and the new SPECS/tini/tini.spec builds tini-static only to discard it with %exclude %{_bindir}/tini-static.

27.3.1-6 and 27.3.1-7 were an unrelated jq bump and a licence cleanup. master/dev/4.0 are unaffected — they still build docker-init statically.

Upstream moby links docker-init statically on purpose: hack/dockerfile/install/tini.installer runs make tini-static, and the Dockerfile asserts it with xx-verify --static. Fedora does the same, shipping a tini-static subpackage that moby-engine depends on.

Changes

SPECS/tini/tini.spec (0.19.0-2 → 0.19.0-3)

  • Package tini-static in a new subpackage instead of excluding it. The binary is already built and installed into the buildroot by %cmake_build / %cmake_install; the %exclude existed only to stop RPM's unpackaged-files check from failing the build.
  • Restore tini-disable-git.patch, also dropped by b3da2fa. tini's CMakeLists.txt overwrites tini_VERSION_GIT and git_version_check_ret via execute_process(), clobbering the -D values the spec passes. An RPM builds from a tarball with no .git, so the git call fails and the version suffix is silently dropped. moby parses docker-init --version expecting tini version X.Y.Z - git.COMMIT, so docker info currently reports an empty init version:.

SPECS/docker/docker.spec (29.8.0-1 → 29.8.0-2) and SPECS/90/docker/docker.spec (28.2.2-7.4.1 → 28.2.2-7.4.2)

5.0 gates docker by subrelease — SPECS/docker builds for photon_subrelease >= 91, SPECS/90/docker for <= 90 — and both carry the same ln -srv .../tini .../docker-init, so both are fixed. SPECS/tini/tini.spec is not gated, so the single tini-static subpackage covers every subrelease.

In each:

  • Point docker-init at tini-static.
  • Add Requires: tini-static >= 0.19.0-3 to docker-engine, the subpackage that actually ships /usr/bin/docker-init.

The version bound is deliberate. I simulated an upgrade in which docker-engine lands while an older tini without tini-static is installed: the dangling symlink reproduces the identical error message, so an unversioned dependency would leave that window open.

Requires: tini stays on the docker metapackage. docker is currently the only package requiring tini, so removing it would orphan /usr/bin/tini to tdnf autoremove — out of scope for this fix.

Verification

Built both packages and confirmed:

rpm -qlp tini-0.19.0-3.rpm         -> /usr/bin/tini
rpm -qlp tini-static-0.19.0-3.rpm  -> /usr/bin/tini-static
file /usr/bin/tini-static          -> ELF 64-bit LSB executable, statically linked
readelf -l /usr/bin/tini-static    -> 0 INTERP segments
/usr/bin/tini-static --version     -> tini version 0.19.0 - git.de40ad0

With docker-init symlinked to tini-static exactly as the spec creates it:

# docker run --rm --init nginx:1-alpine true
(succeeds)
# docker run --rm --init nginx:1-alpine ps -o pid,comm
PID   COMMAND
    1 docker-init

Note on scope of testing: the RPM build was done locally, not through the Photon build system, so a builder run is still worth doing before merge.

The build and runtime checks above were done on docker 29.5.3. The branch has since been rebased onto 29.8.0 (f7e204f); the two lines this PR changes in docker.spec are identical in 29.8.0, and the spec passes the spec checker, but it has not been rebuilt against 29.8.0.

`docker run --init` fails on any container whose rootfs has no glibc loader:

    exec /sbin/docker-init: no such file or directory

/sbin/docker-init is not a host path. It is the hardcoded in-container
destination (inContainerInitPath in moby daemon/oci_linux.go); the daemon
resolves the host binary via LookupInitPath() and bind-mounts it there, then
execs it as PID 1 inside the container. Because /usr/bin/docker-init is a
dynamic PIE, the kernel must load /lib64/ld-linux-x86-64.so.2 from the
*container's* rootfs. Alpine/musl, scratch and distroless images do not have
it, and execve reports the missing interpreter as ENOENT - which surfaces as
"no such file or directory" for a file that plainly exists.

This regressed in 27.3.1-5 (commit b3da2fa, "tini: Initial commit / Also
seperate out tini from docker spec"). Before it, docker.spec built tini itself
and installed a statically linked binary:

    make tini-static
    cp tini-static "$GOPATH/bin/docker-init"

after it, docker-init became a symlink to the dynamically linked /usr/bin/tini
from the new standalone tini package, whose spec builds tini-static and then
discards it with %exclude.

Upstream moby links docker-init statically on purpose
(hack/dockerfile/install/tini.installer runs `make tini-static`, and the
Dockerfile asserts it with `xx-verify --static`). Fedora does the same: its
tini package ships a tini-static subpackage that moby-engine depends on.
photon master/dev/4.0 are unaffected, they still build it statically.

Changes:

  tini: package tini-static in a new subpackage instead of excluding it. The
  binary is already built and installed into the buildroot by %cmake_build /
  %cmake_install - the %exclude only existed to stop RPM's unpackaged-files
  check from failing the build.

  tini: restore tini-disable-git.patch, dropped by b3da2fa. tini's
  CMakeLists.txt overwrites tini_VERSION_GIT and git_version_check_ret from
  execute_process(), clobbering the -D values the spec passes. An RPM builds
  from a tarball with no .git, so the git call fails and the version suffix is
  silently dropped. moby parses `docker-init --version` expecting
  "tini version X.Y.Z - git.COMMIT", so `docker info` showed an empty
  "init version:". With the patch both binaries report
  "tini version 0.19.0 - git.de40ad0" again.

  docker: 5.0 gates docker by subrelease, so both copies are fixed -
  SPECS/docker/docker.spec (photon_subrelease >= 91) and
  SPECS/90/docker/docker.spec (photon_subrelease <= 90). SPECS/tini/tini.spec
  is not gated, so the tini-static subpackage covers every subrelease.
  Both point docker-init at tini-static, and add a versioned
  Requires: tini-static >= 0.19.0-3 to docker-engine, which is the subpackage
  that actually ships /usr/bin/docker-init. The version is required: an
  unversioned dependency could be satisfied by an older tini that has no
  tini-static, leaving the symlink dangling and reproducing the identical
  error message.

Verified on Photon 5.0 with docker-29.5.3-1.ph5 / tini-0.19.0-2.ph5:

  before: docker run --init nginx:1-alpine  -> exec /sbin/docker-init: ...
          docker run --init debian:12       -> works (glibc image)
  after:  both work, and `ps` in the container shows PID 1 = docker-init

Change-Id: I0b08ef69c111b299fad06874c7fb1e306a040622
Signed-off-by: Daniel Casota <dcasota@gmail.com>
@dcasota
dcasota force-pushed the fix/docker-init-static-5.0 branch from f97ac7d to 38c9bde Compare September 14, 2026 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants