Devlog 2026-01-04
Measuring Container Builds
2026-01-04 (updated 2026-01-05)Posts | 7 min read
#cicd #containers #devops #docker
Series: devlog
Over the holidays I’ve been diving deep into build systems in general and container builds in particular.
I work with containers a lot and usualy have a pretty good gut feeling on how to make them fast.
Order of operations matters, splitting dependency and source installs usually speeds things up and allows for better caching. All the little things you pick up over time.
Measuring Things
But gut feeling is not really good enough when one could instead measure things! So I’ve been hacking away on properly measuring container build timings, how it might be visualized and even compared over time.
For now I’m calling it buildbench and it works something like this:
podman build --no-cache --file Dockerfile --tag example:latest . | \
buildbench --engine podman --report-file nearbench-no-cache.json
This spits out a json file looking like this
{
"start_time": "2026-01-04T19:26:37.633104+01:00",
"end_time": "2026-01-04T19:27:01.102914+01:00",
"stages": [
{
"stage_number": 1,
"cached": false,
"start_time": "2026-01-04T19:26:37.633104+01:00",
"end_time": "2026-01-04T19:27:00.647691+01:00",
"duration_ms": 23014,
"steps": [
{
"index": 0,
"description": "FROM robotichead/nearbeach-base:main",
"cached": true,
"start_time": "2026-01-04T19:26:37.633104+01:00",
"end_time": "2026-01-04T19:26:37.655233+01:00",
"duration_ms": 22,
"lines": [
"STEP 1/14: FROM robotichead/nearbeach-base:main",
"STEP 2/14: RUN echo \"**** install NearBeach Latest ****\" \u0026\u0026 pip install NearBeach"
]
},
{
"index": 1,
"description": "RUN echo \"**** install NearBeach Latest ****\" \u0026\u0026 pip install NearBeach",
"cached": false,
"start_time": "2026-01-04T19:26:37.655233+01:00",
"end_time": "2026-01-04T19:26:49.909785+01:00",
"duration_ms": 12254,
"lines": [
"STEP 2/14: RUN echo \"**** install NearBeach Latest ****\" \u0026\u0026 pip install NearBeach",
"**** install NearBeach Latest ****",
"Collecting NearBeach",
" Downloading nearbeach-0.31.105-py3-none-any.whl.metadata (6.5 kB)",
"Requirement already satisfied: azure-identity in /usr/local/lib/python3.13/site-packages (from NearBeach) (1.25.1)",
"Requirement already satisfied: azure-storage-blob in /usr/local/lib/python3.13/site-packages (from NearBeach) (12.27.1)",
"Collecting boto3==1.35.99 (from NearBeach)",
" Downloading boto3-1.35.99-py3-none-any.whl.metadata (6.7 kB)",
"Requirement already satisfied: django-otp in /usr/local/lib/python3.13/site-packages (from NearBeach) (1.6.3)",
"Requirement already satisfied: django-two-factor-auth in /usr/local/lib/python3.13/site-packages (from NearBeach) (1.18.1)",
"Requirement already satisfied: django\u003e=4.2.17 in /usr/local/lib/python3.13/site-packages (from NearBeach) (5.2.7)",
"Requirement already satisfied: djangorestframework in /usr/local/lib/python3.13/site-packages (from NearBeach) (3.16.1)",
"Requirement already satisfied: drf-spectacular in /usr/local/lib/python3.13/site-packages (from NearBeach) (0.29.0)",
"Requirement already satisfied: pillow\u003e=10.0.1 in /usr/local/lib/python3.13/site-packages (from NearBeach) (12.0.0)",
"Requirement already satisfied: pip\u003e=19.2 in /usr/local/lib/python3.13/site-packages (from NearBeach) (25.3)",
"Requirement already satisfied: simplejson in /usr/local/lib/python3.13/site-packages (from NearBeach) (3.20.2)",
"Requirement already satisfied: urllib3 in /usr/local/lib/python3.13/site-packages (from NearBeach) (2.5.0)",
"Collecting botocore\u003c1.36.0,\u003e=1.35.99 (from boto3==1.35.99-\u003eNearBeach)",
" Downloading botocore-1.35.99-py3-none-any.whl.metadata (5.7 kB)",
"Requirement already satisfied: jmespath\u003c2.0.0,\u003e=0.7.1 in /usr/local/lib/python3.13/site-packages (from boto3==1.35.99-\u003eNearBeach) (1.0.1)",
"Collecting s3transfer\u003c0.11.0,\u003e=0.10.0 (from boto3==1.35.99-\u003eNearBeach)",
" Downloading s3transfer-0.10.4-py3-none-any.whl.metadata (1.7 kB)",
"Requirement already satisfied: python-dateutil\u003c3.0.0,\u003e=2.1 in /usr/local/lib/python3.13/site-packages (from botocore\u003c1.36.0,\u003e=1.35.99-\u003eboto3==1.35.99-\u003eNearBeach) (2.9.0.post0)",
"Requirement already satisfied: six\u003e=1.5 in /usr/local/lib/python3.13/site-packages (from python-dateutil\u003c3.0.0,\u003e=2.1-\u003ebotocore\u003c1.36.0,\u003e=1.35.99-\u003eboto3==1.35.99-\u003eNearBeach) (1.17.0)",
"Requirement already satisfied: asgiref\u003e=3.8.1 in /usr/local/lib/python3.13/site-packages (from django\u003e=4.2.17-\u003eNearBeach) (3.10.0)",
"Requirement already satisfied: sqlparse\u003e=0.3.1 in /usr/local/lib/python3.13/site-packages (from django\u003e=4.2.17-\u003eNearBeach) (0.5.3)",
"Requirement already satisfied: azure-core\u003e=1.31.0 in /usr/local/lib/python3.13/site-packages (from azure-identity-\u003eNearBeach) (1.36.0)",
"Requirement already satisfied: cryptography\u003e=2.5 in /usr/local/lib/python3.13/site-packages (from azure-identity-\u003eNearBeach) (46.0.3)",
"Requirement already satisfied: msal\u003e=1.30.0 in /usr/local/lib/python3.13/site-packages (from azure-identity-\u003eNearBeach) (1.34.0)",
"Requirement already satisfied: msal-extensions\u003e=1.2.0 in /usr/local/lib/python3.13/site-packages (from azure-identity-\u003eNearBeach) (1.3.1)",
"Requirement already satisfied: typing-extensions\u003e=4.0.0 in /usr/local/lib/python3.13/site-packages (from azure-identity-\u003eNearBeach) (4.15.0)",
"Requirement already satisfied: requests\u003e=2.21.0 in /usr/local/lib/python3.13/site-packages (from azure-core\u003e=1.31.0-\u003eazure-identity-\u003eNearBeach) (2.32.5)",
"Requirement already satisfied: cffi\u003e=2.0.0 in /usr/local/lib/python3.13/site-packages (from cryptography\u003e=2.5-\u003eazure-identity-\u003eNearBeach) (2.0.0)",
"Requirement already satisfied: pycparser in /usr/local/lib/python3.13/site-packages (from cffi\u003e=2.0.0-\u003ecryptography\u003e=2.5-\u003eazure-identity-\u003eNearBeach) (2.23)",
"Requirement already satisfied: PyJWT\u003c3,\u003e=1.0.0 in /usr/local/lib/python3.13/site-packages (from PyJWT[crypto]\u003c3,\u003e=1.0.0-\u003emsal\u003e=1.30.0-\u003eazure-identity-\u003eNearBeach) (2.10.1)",
"Requirement already satisfied: charset_normalizer\u003c4,\u003e=2 in /usr/local/lib/python3.13/site-packages (from requests\u003e=2.21.0-\u003eazure-core\u003e=1.31.0-\u003eazure-identity-\u003eNearBeach) (3.4.4)",
"Requirement already satisfied: idna\u003c4,\u003e=2.5 in /usr/local/lib/python3.13/site-packages (from requests\u003e=2.21.0-\u003eazure-core\u003e=1.31.0-\u003eazure-identity-\u003eNearBeach) (3.11)",
"Requirement already satisfied: certifi\u003e=2017.4.17 in /usr/local/lib/python3.13/site-packages (from requests\u003e=2.21.0-\u003eazure-core\u003e=1.31.0-\u003eazure-identity-\u003eNearBeach) (2025.10.5)",
"Requirement already satisfied: isodate\u003e=0.6.1 in /usr/local/lib/python3.13/site-packages (from azure-storage-blob-\u003eNearBeach) (0.7.2)",
"Requirement already satisfied: qrcode\u003c9 in /usr/local/lib/python3.13/site-packages (from django-two-factor-auth-\u003eNearBeach) (8.2)",
"Requirement already satisfied: django-phonenumber-field\u003c9 in /usr/local/lib/python3.13/site-packages (from django-two-factor-auth-\u003eNearBeach) (8.3.0)",
"Requirement already satisfied: django-formtools in /usr/local/lib/python3.13/site-packages (from django-two-factor-auth-\u003eNearBeach) (2.5.1)",
"Requirement already satisfied: phonenumberslite\u003c8.99,\u003e=7.0.9 in /usr/local/lib/python3.13/site-packages (from django-two-factor-auth[phonenumberslite]-\u003eNearBeach) (8.13.55)",
"Requirement already satisfied: uritemplate\u003e=2.0.0 in /usr/local/lib/python3.13/site-packages (from drf-spectacular-\u003eNearBeach) (4.2.0)",
"Requirement already satisfied: PyYAML\u003e=5.1 in /usr/local/lib/python3.13/site-packages (from drf-spectacular-\u003eNearBeach) (6.0.3)",
"Requirement already satisfied: jsonschema\u003e=2.6.0 in /usr/local/lib/python3.13/site-packages (from drf-spectacular-\u003eNearBeach) (4.25.1)",
"Requirement already satisfied: inflection\u003e=0.3.1 in /usr/local/lib/python3.13/site-packages (from drf-spectacular-\u003eNearBeach) (0.5.1)",
"Requirement already satisfied: attrs\u003e=22.2.0 in /usr/local/lib/python3.13/site-packages (from jsonschema\u003e=2.6.0-\u003edrf-spectacular-\u003eNearBeach) (25.4.0)",
"Requirement already satisfied: jsonschema-specifications\u003e=2023.03.6 in /usr/local/lib/python3.13/site-packages (from jsonschema\u003e=2.6.0-\u003edrf-spectacular-\u003eNearBeach) (2025.9.1)",
"Requirement already satisfied: referencing\u003e=0.28.4 in /usr/local/lib/python3.13/site-packages (from jsonschema\u003e=2.6.0-\u003edrf-spectacular-\u003eNearBeach) (0.37.0)",
"Requirement already satisfied: rpds-py\u003e=0.7.1 in /usr/local/lib/python3.13/site-packages (from jsonschema\u003e=2.6.0-\u003edrf-spectacular-\u003eNearBeach) (0.28.0)",
"Requirement already satisfied: drf-spectacular-sidecar in /usr/local/lib/python3.13/site-packages (from drf-spectacular[sidecar]-\u003eNearBeach) (2025.10.1)",
"Downloading nearbeach-0.31.105-py3-none-any.whl (24.4 MB)",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 24.4/24.4 MB 7.3 MB/s 0:00:03",
"Downloading boto3-1.35.99-py3-none-any.whl (139 kB)",
"Downloading botocore-1.35.99-py3-none-any.whl (13.3 MB)",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.3/13.3 MB 11.9 MB/s 0:00:01",
"Downloading s3transfer-0.10.4-py3-none-any.whl (83 kB)",
"Installing collected packages: botocore, s3transfer, boto3, NearBeach",
" Attempting uninstall: botocore",
" Found existing installation: botocore 1.40.64",
" Uninstalling botocore-1.40.64:",
" Successfully uninstalled botocore-1.40.64",
" Attempting uninstall: s3transfer",
" Found existing installation: s3transfer 0.14.0",
" Uninstalling s3transfer-0.14.0:",
" Successfully uninstalled s3transfer-0.14.0",
" Attempting uninstall: boto3",
" Found existing installation: boto3 1.40.64",
" Uninstalling boto3-1.40.64:",
" Successfully uninstalled boto3-1.40.64",
"",
"Successfully installed NearBeach-0.31.105 boto3-1.35.99 botocore-1.35.99 s3transfer-0.10.4",
"WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.",
"--\u003e 067510306616"
]
},
{
"index": 2,
"description": "RUN echo \"**** copy over the crontab configuration ****\"",
"cached": false,
"start_time": "2026-01-04T19:26:49.914935+01:00",
"end_time": "2026-01-04T19:26:51.000351+01:00",
"duration_ms": 1085,
"lines": [
"STEP 3/14: RUN echo \"**** copy over the crontab configuration ****\"",
"**** copy over the crontab configuration ****",
"--\u003e fe33fa07f39b"
]
},
{
"index": 3,
"description": "COPY --chown=nearbeach:nearbeach crontab /etc/crontabs/nearbeach",
"cached": false,
"start_time": "2026-01-04T19:26:51.004498+01:00",
"end_time": "2026-01-04T19:26:52.004502+01:00",
"duration_ms": 1000,
"lines": [
"STEP 4/14: COPY --chown=nearbeach:nearbeach crontab /etc/crontabs/nearbeach",
"--\u003e 7c86b9f03305"
]
},
{
"index": 4,
"description": "RUN crontab /etc/crontabs/nearbeach",
"cached": false,
"start_time": "2026-01-04T19:26:52.011388+01:00",
"end_time": "2026-01-04T19:26:53.077934+01:00",
"duration_ms": 1066,
"lines": [
"STEP 5/14: RUN crontab /etc/crontabs/nearbeach",
"--\u003e 31d8d86544eb"
]
},
{
"index": 5,
"description": "RUN echo \"**** setup of working directory ****\"",
"cached": false,
"start_time": "2026-01-04T19:26:53.083621+01:00",
"end_time": "2026-01-04T19:26:54.125724+01:00",
"duration_ms": 1042,
"lines": [
"STEP 6/14: RUN echo \"**** setup of working directory ****\"",
"**** setup of working directory ****",
"--\u003e 21069ef90cb6"
]
},
{
"index": 6,
"description": "WORKDIR /oceansuite",
"cached": false,
"start_time": "2026-01-04T19:26:54.132744+01:00",
"end_time": "2026-01-04T19:26:54.170045+01:00",
"duration_ms": 37,
"lines": [
"STEP 7/14: WORKDIR /oceansuite",
"--\u003e 0705125a899b"
]
},
{
"index": 7,
"description": "RUN chown nearbeach:nearbeach /oceansuite",
"cached": false,
"start_time": "2026-01-04T19:26:54.175272+01:00",
"end_time": "2026-01-04T19:26:55.331229+01:00",
"duration_ms": 1155,
"lines": [
"STEP 8/14: RUN chown nearbeach:nearbeach /oceansuite",
"--\u003e cc05366cf586"
]
},
{
"index": 8,
"description": "USER nearbeach",
"cached": false,
"start_time": "2026-01-04T19:26:55.337921+01:00",
"end_time": "2026-01-04T19:26:55.431752+01:00",
"duration_ms": 93,
"lines": [
"STEP 9/14: USER nearbeach",
"--\u003e 46a27e54cba6"
]
},
{
"index": 9,
"description": "RUN echo \"**** copy everything into the destination ****\"",
"cached": false,
"start_time": "2026-01-04T19:26:55.43845+01:00",
"end_time": "2026-01-04T19:26:56.750112+01:00",
"duration_ms": 1311,
"lines": [
"STEP 10/14: RUN echo \"**** copy everything into the destination ****\"",
"**** copy everything into the destination ****",
"--\u003e 7abef7f9b96b"
]
},
{
"index": 10,
"description": "COPY --chown=nearbeach:nearbeach . .",
"cached": false,
"start_time": "2026-01-04T19:26:56.756063+01:00",
"end_time": "2026-01-04T19:26:58.036047+01:00",
"duration_ms": 1279,
"lines": [
"STEP 11/14: COPY --chown=nearbeach:nearbeach . .",
"--\u003e c8e0ebc6cf03"
]
},
{
"index": 11,
"description": "RUN chmod u+x setup_db_and_run_server.sh",
"cached": false,
"start_time": "2026-01-04T19:26:58.043371+01:00",
"end_time": "2026-01-04T19:26:59.318538+01:00",
"duration_ms": 1275,
"lines": [
"STEP 12/14: RUN chmod u+x setup_db_and_run_server.sh",
"--\u003e 529502cf948c"
]
},
{
"index": 12,
"description": "RUN echo \"**** run the setup database and run server scripts ****\"",
"cached": false,
"start_time": "2026-01-04T19:26:59.324018+01:00",
"end_time": "2026-01-04T19:27:00.530772+01:00",
"duration_ms": 1206,
"lines": [
"STEP 13/14: RUN echo \"**** run the setup database and run server scripts ****\"",
"**** run the setup database and run server scripts ****",
"--\u003e 0323c9d2a2e9"
]
},
{
"index": 13,
"description": "CMD './setup_db_and_run_server.sh'",
"cached": false,
"start_time": "2026-01-04T19:27:00.535917+01:00",
"end_time": "2026-01-04T19:27:00.626558+01:00",
"duration_ms": 90,
"lines": [
"STEP 14/14: CMD './setup_db_and_run_server.sh'",
"COMMIT local:nearbeach-local"
]
},
{
"index": 14,
"description": "COMMIT",
"cached": false,
"start_time": "2026-01-04T19:27:00.626558+01:00",
"end_time": "2026-01-04T19:27:00.647691+01:00",
"duration_ms": 21,
"lines": [
"COMMIT local:nearbeach-local",
"--\u003e a3aaad29b49d"
]
}
]
}
],
"duration_ms": 23469
}
Under the hood I’m, using a finite state machine to generate this data from the stdout stream of podman or docker build.
It already works with multi stage builds, however not with multi architecture or buildx, both of which I want to add before releasing this properly.
Visualization
I’m not great with anything visual, so this is mainly me prompting my way to an early prototype, but I do like the direction.
We have a overview/header:

And a stage by stage / step by step breakdown, which can also compare two reports side by side:

Whats Next?
I plan to have another look at the buildbench cli over the next few weeks,
clean things up a bit and open-source it for everyone to use.
On the visualization side I will most likely play with some more ideas, trying to get something presentable to open-source as well.
And then? I will finally be able to write the container build guide all of this started with!