Analysing some Dockerfiles [weekly-2026-w37]
- September 12, 2026
- Patrick Ferris
After spending a few weeks thinking about the semantics of POSIX shells, this week saw me analysing lots of Dockerfiles looking for some evidence for how they are being used. It also saw environment variables landing in Eio thanks to Thomas and Anil. We also had some issues in ppxlib migrations.
Dockerfile Analysis [msh-dockerfile-analysis]
- September 12, 2026
- Patrick Ferris
Part of my research at the moment is thinking about how shells are used. What commands are run? What constructs are used (e.g. redirections, compound commands...)? Dockerfiles are a defacto standard means for building container images and for the most part they are a shell with weird semantics and some meta-commands (sounds familiar!).
I had some assumptions about the kinds of shell commands people use (myself included) when writing Dockerfiles. For example, making use of AND-lists for grouping commands in RUN instructions. But I had no evidence to back this up. So I ran some analysis over the dataset from this paper from Henkel et al..
Total Dockerfiles: 218_253 (errors: 808) Total Docker Instructions: 2_729_118 Total RUN instructions: 657_909 (errors: 4_847) ======== Count (Windows Dockerfiles): 841 / 218_253 Count (ENV commands): 268_898 / 2_729_118 Count (COPY commands): 165_968 / 2_729_118 Count (ADD commands): 86_552 / 2_729_118 Count (WORKDIR commands): 104_112 / 2_729_118 Count (using at least one &&): 267_113 / 657_909 Count (uses of exec): 25 / 657_909 Count (system pkg manager: [apt, apt-get, dnf, apk, pacman, nix, brew, yum]): 192_092 / 657_909 Count (internet retrievers: [curl, wget]): 95_433 / 657_909 Count (might use python): 9_082 / 657_909 Counts: [(rm, 169_386), (echo, 113_768), (mkdir, 100_924), (cd, 74_904), (chmod, 55_998), (set, 25_738), (mv, 24_034), (sed, 21_232), (chown, 18_610), (grep, 16_604), (cp, 15_637), (export, 13_154), ([, 10_411), (touch, 3_152), (exit, 2_288), (printf, 2_275), (awk, 2_010), (., 1_077), (test, 1_004), (source, 904), (pwd, 199), (unset, 105), (diff, 40), (trap, 2)]
I think there's some evidence here to support some assumptions I already had:
A significant proportion of
RUNinstructions in Dockerfiles invoke package managers.A significant proportion of
RUNinstructions invokecurlorwget.There is something rather Zipfian about how shell built-in utilities are used.
There is limited used of
exec(and therefore limited use of opening file descriptors directly via the shell, as opposed to a command running in a subprocess).A lot of time is spent changing directories, either temporally (via
cdin aRUNinstruction, or by usingWORKDIR).Lots of
ENVsetting.
Parsing Dockerfiles
- September 12, 2026
- Patrick Ferris
For reasons that are probably quite clear, whenever I end up doing a small task like this, I always reach for OCaml to do it. The idea is that I'm invested in that community and hope to see it thrive. And if that means contributing some fixes, libraries or advice along the way, it seems to me that that is time well spent.
When I started, I assumed ocaml-dockerfile had support for parsing files; it turns out it doesn't, but I found Thomas G.'s port that does. So I took it for a spin and the results were less than I had hoped for. Of the 219,061 Dockerfiles, 97,123 were not parseable! After a series of commits I managed to get that number down to 808.
Eio process environment
- September 12, 2026
- Patrick Ferris
What started out seemingly straightforward (add a capability-based API to Eio for accessing environment variables) ended up being a more nuanced discussion about what the best API would be. In the end, Thomas added an Eio.Process.Env API to manipulate soon-to-be opaque environments. This paved the way for the portable environment support.
The API Eio exposes is readonly so we do not have to worry too much about race conditions (even though users are free to Unix.putenv).
One quirk of this work was reaffirming something I had already stumbled across in Merry. Accessing and searching large environments has a cost! For my rather large Nix environment, we can see the slowdown pretty clearly.
name, major-allocated, minor-allocated, monotonic-clock
get_env/all 10, 346.619865, 38577.227215, 29248.828738
get_env/all 50, 1677.003302, 181788.755923, 138307.102538
get_env/all 100, 3323.770915, 361218.802888, 274768.577259
get_env/all_once 10, 121.955078, 14694.900546, 11652.096583
get_env/all_once 50, 563.133065, 61951.683658, 48161.605588
get_env/all_once 100, 1113.238600, 121431.915770, 93960.646400
get_env/direct 10, 0.000000, 0.000000, 974.649587
get_env/direct 50, 0.000000, 0.000000, 4369.486360
get_env/direct 100, 0.000000, 0.000000, 8753.393503The all benchmark calls environment every time and then finds a variable using Eio.Process.Env.get_opt, all_once calls environment once and then Eio.Process.Env.get_opt every time and direct uses the Eio.Process.get_opt directly every time. The differences are a lot less stark when the environment is pretty small.
Ppxlib Migrations
- September 12, 2026
- Patrick Ferris
Thankfully, ppxlib has been mostly stable for a number of months. In part, thanks to the diligent efforts of Nathan. However, David uncovered some unpleasant bugs in the migration code for some new 5.4 AST features. Unfortunately, it is only for attributes which are a source of pain for ppxlib. I spent some time reviewing the fixes, and it looks like we still need some more work to ensure the attributes attached to package types roundtrip cleanly.
Misc.
- September 12, 2026
- Patrick Ferris
Some other work I recorded separately and include here for the eager reader.
OCaml Roundup: August 2026 [ocaml-roundup-august-2026]
- July 12, 2026
- Patrick Ferris
Much of August was spent thinking about POSIX shell semantics, and in particular extending notions from smoosh to include side effects (and maybe concurrency). This is still nascent work, but some ideas and flavours are described below.
Merging Shells
- July 12, 2026
- Patrick Ferris
The work came from wanting to merge two shell sessions into one. Setting aside the practicalities of an implementation, it makes sense to start first theoretically. When can I merge two shell executions, and when can I not?
It would be nice to think of unset DOWNLOAD_DIR as a function. This function takes the shell state and the operating state as arguments and returns a modified shell state and an "unmodified" operating system state (at least for our purposes). Meanwhile, branch-b may have a lot more side effects, but when focusing on the environment only we can see in a sense the two branches are conflict free. Well, sorta. We also need to ensure we do not also read DOWNLOAD_DIR in branch-b if we intend to remove it in their merge.
This work, in part, is borrowing ideas from MRDTs and can reuse void processes to help reason about when side effects can occur, as well as open-trace (which is now becoming os-trace).
I hope to continue this work, see my latest weekly for more on this.
Eio in the browser
- July 12, 2026
- Patrick Ferris
I spent some time carving out Eio_browser into a standalone library, something I depend on for Merry to work in the browser.
Eio_browser [eio_browser]
- September 12, 2026
- Patrick Ferris
- https://git.sirref.org/eio_browser
Eio_browser is a backend for Eio that works in the browser. It reuses work I started at Tarides that picked up some momentum but has since been a little dormant.
Crucially, it provides a functional standard environment like Eio_main or any of the OS-specific backends.
module Stdenv : sig
type base =
< stdout : Eio_unix.sink_ty r
; stderr : Eio_unix.sink_ty r
; clock : float Eio.Time.clock_ty r
; mono_clock : Eio.Time.Mono.ty r
; secure_random : Eio.Flow.source_ty r
; debug : Eio.Debug.t
; backend_id : string >
end
val run : (Stdenv.base -> unit) -> unit
(** [run fn] runs [fn] passing it the {! Stdenv.base} environment.
See {! Backend.run} for more information about how effects are handled.*)