Closure size small, dockerTools image large

This follows a discussion on this gist by @NobbZ.

I’m trying to produce a minimal docker image for a newly-generated Elixir Phoenix project.

The closure size of my app, like @NobbZ says in the gist discussion, is minimised because only the bits required by the mix release are linked. This is good!

However, when creating a docker image from the results of the mixRelease, the entirety of those dependent nix store derivations are copied into the resulting image. So even with beam_minimal, I get all of erlang, including wx and its extensive HTML documentation.

Is this an expected behaviour, or should I be casting a spell that says “make the docker image smaller”? My approach so far has been to deliberately shrink the size of erlang by using overrides, but it sounds like that’s not the royal path. Even if it was, it’s a fragile approach and means I can’t take advantage of the binary cache.

Any help appreciated! I’m also happy to provide more examples than the ones I spammed onto the gist.

If I build your docker image from the flake you linked in the comments, I get an image tarball of 65MiB.

The unpacked image size seems to be ~150 MiB:

$ docker image inspect 378a485931a7| jq '.[].Size' | numfmt --to-unit=Mi --format='%.2f MiB'
147.63 MiB

Can you please elaborate what is in there what you do not expect?

1 Like

That version used a modified erlang, with explicit exclusion of wx and documentation.

I’ve pushed another version that inlines the premature library extraction and uses regular beam_minimal without overrides.

So now I get:

$ docker image inspect mygreatdocker/image:0.0.1 | jq '.[].Size' | numfmt --to-unit=Mi --format='%.2f MiB'
367.08 MiB

If you dive the result of that #dockerImage, you should see that wx is included inside erlang.

I’ve added another commit that removes runtime_tools from mix.exs as you suggested, but that doesn’t shrink erlang in the docker image. The only remaining reference to erlang-25 in the default package is lib/elixir-1.14.5/ebin/elixir_parser.beam.

$ strings result/lib/elixir-1.14.5/ebin/elixir_parser.beam | grep erlang-25

The main trick is to use nix why-depends -sSrh on the package closure to figure out why things are needed, then rework the build to strip those. Not on the image because you won’t get an interesting output, but on the package that will be added to the image.

1 Like

@zimbatm all that why-depends tells me is that elixir wants erlang (I don’t know what you meant by the -sSrh flags as these don’t seem to be flags for why-depends:

$ nix why-depends --precise . /nix/store/vyxc3xa3n8idxqwcmlp7by1l15l8mfxc-erlang-
    → /nix/store/vyxc3xa3n8idxqwcmlp7by1l15l8mfxc-erlang-
└───lib/elixir-1.14.5/ebin/elixir_parser.beam: …c/elixir_parser.yrl.o/nix/store/vyxc3xa3n8idxqwcmlp7by1l15l8mfxc-erlang-…

Presumably elixir_parser.beam should be referencing something in ERTS? If I delete that file in postInstall I get a very broken app, but no erlang and the container image shrinks massively. I don’t know where in a ‘normal’ mix release the erlang and elixir stuff gets baked into the release, but it seems that when that happens we’re accidentally getting a reference to the full erlang in the nix store from the elixir_parser.

This might be related to beam debug symbol stripping…

I did a PR about that once and another PR changed the default behaviour, though now as you are mentioning this, I remember that it was significant to reduce the closure sizes…


It absolutely is!

106M container tarball with stripDebug = false
29M container tarball with stripDebug = true

It’s taken weeks to get to the point where mixRelease produces something I can deploy. I guess I’ll work on putting a large notice in the beam languages doc.

Thank you @NobbZ! This makes deployment with a fully nix-based build at work much more feasible.