Dockerfile as JSON
Table of contents
- Historic and boring context
- The problem
- Dockerfiles as JSONs
- Jockering with karavomarangos
- Design choices and limitations
- Tools I used during this project creation
Historic and boring context
In the beginning of 2019, I started a project called Limani (means Port in Greek).
I was managing CI of one of my project’s tools and each project had a similar pipeline behaviour:
- pull ubuntu:18:04
- install required tools for CI
- do things, like unit tests
- do more things, maybe in other stages
Every new pipeline deployed was downloading again the same dependencies as the previous one, every time. That was a waste of time I did not want to suffer.
So, I created this project that defines “base” images. A base image is meant to be a tiny image with required tools or software for a certain requirement. There was a base_nginx image, a base_redis image, and so on.
All those images have a familiar hierarchy defined. First image was called base, included my own repos and nothing more, base_deb_builder comes from base and got installed deb packaging utilities, base_deb_perl_builder comes from base_deb_builder and adds perl utilities for deb packaging. I used them to save time, a lot of time, during the CI process.
Like many of my projects, Daedalus Project was abandoned years ago but I am still using Limani as I am still thinking that it is a useful project to maintain, all my newer, and unfinished, projects use those base images in one way or another.
I think the structure of base Dockerfiles is very simple, let us take a look at it:
ARG BASE_IMAGE=harbor.windmaker.net/limani/base
FROM $BASE_IMAGE
MAINTAINER Álvaro Castellano Vela <alvaro@windmaker.net>
RUN \
apt-get update -qq && \
apt-get install -qq -o=Dpkg::Use-Pty=0 -y \
debhelper=13.14.1ubuntu5 \
fakeroot=1.33-1 \
build-essential=12.10ubuntu1 \
libdebhelper-perl=13.14.1ubuntu5 \
dpkg-dev=1.22.6ubuntu6.5 \
--no-install-recommends && \
apt-get purge -qq -o=Dpkg::Use-Pty=0 -y gnupg wget ca-certificates && \
apt-get autoremove -qq -o=Dpkg::Use-Pty=0 -y && \
apt-get autoclean -qq -o=Dpkg::Use-Pty=0 -y && \
rm -rf /var/lib/apt/lists/* && \
echo -e "DEBEMAIL=\"alvaro@windmaker.net\"\nDEBFULLNAME=\"Álvaro Castellano Vela\"\nexport DEBEMAIL DEBFULLNAME\n" >> ~/.bashrc.backup
Very simple, the only thing I want to point out is that all packages in this base image are pinned to required versions. Why? Because I don’t like surprises during development, a new version won’t break the app until I test it. And all tools will use the same bases. Also, any new build creates a timestamped release, I could use those bases during development and update the bases after finishing a release or change in order to check that it would work in new versions.
A current example comes with my golang projects, the development process uses golang 1.23.1, and after I finish the development I can upgrade the base golang image to 1.23.4 and check that everything is working.
The problem
So, how have I been updating those images all these years?
By hand…..
- BY HAND?
- Of course
- WHAT? “Orchestration Enthusiast” my ba****
You, me actually, are right about that. For the last 6 years I have been running the Limani pipeline and watching new image builds fail because a required package was no longer found after the latest update.
So my self-punishment consisted of:
- Running the pipeline
- Noticing that base_curl fails because curl has been updated and the latest version is no longer present in Ubuntu repos
- Fix, commit, push
- Noticing that another image has another outdated package
- repeat and enjoy this process so many times
I must confess that I never thought about this seriously.
Until now…
Dockerfiles as JSONs
May I write those packages and versions in a program-readable file that can look for new versions and update those Dockerfiles?
Yes.
Why don’t I define the entire Dockerfile as JSON?
Yes, of course.
Could it be possible to add readme doc for each base image in that JSON?
Why not.
And here it comes this project.
Dockerfile as JSON?
JOCKER?
Unfortunately I picked another name more related to ships and ports. Actually Jocker came to my mind days after creating the project.
Project is called karavomarangos and yes, Jocker was a funnier name.
So, this project allows you to define containers using JSON files, like this one:
{
"name": "base_valkey_server",
"base_image": "harbor.windmaker.net/limani/base",
"maintainer": {
"name": "Álvaro",
"surname": "Castellano Vela",
"email": "alvaro@windmaker.net"
},
"packages": [
{
"name": "valkey-server",
"version": "7.2.11+dfsg1-0ubuntu0.2"
}
],
"exposed_ports": [6379],
"command": ["valkey-server", "--protected-mode", "no"],
"readme": {
"description": "This image is based on [base image](/base) and comes with valkey-server installed.",
"additional_features": [
"Exposes port 6379.",
"Runs valkey-server with `--protected-mode no` by default."
]
}
}
This JSON file becomes a Dockerfile like this one:
# Dockerfile generated using karavomarangos - https://git.windmaker.net/a-castellano/karavomarangos
ARG BASE_IMAGE=harbor.windmaker.net/limani/base
FROM $BASE_IMAGE
MAINTAINER Álvaro Castellano Vela <alvaro@windmaker.net>
RUN \
apt-get update -qq && \
apt-get install -qq -o=Dpkg::Use-Pty=0 -y \
valkey-server=7.2.11+dfsg1-0ubuntu0.2 \
--no-install-recommends && \
apt-get purge -qq -o=Dpkg::Use-Pty=0 -y gnupg wget ca-certificates && \
apt-get autoremove -qq -o=Dpkg::Use-Pty=0 -y && \
apt-get autoclean -qq -o=Dpkg::Use-Pty=0 -y && \
rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["valkey-server", "--protected-mode", "no"]
This tool will also render the readme file, like this one
Jockering with karavomarangos
Wow, this is amazing, isn’t it? I prefer to think that at least it is useful, let’s see how to use it.
I recommend using podman for this tool, it also works with docker but the less privileged the better.
First of all, karavomarangos will download and run a docker image to check for listed packages in json files, so you should mount the podman socket to the container, like this:
systemctl --user enable --now podman.socket
Check that podman socket is running, it should be something like this:
srw-rw---- 1 user users 0 feb 26 21:19 /run/user/1000/podman/podman.sock
Then, you can run karavomarangos like this:
podman run --rm -it \
-v $XDG_RUNTIME_DIR/podman/podman.sock:/var/run/docker.sock \
-v ~/where/you/have/your/json/file/is:/project \
harbor.windmaker.net/karavomarangos/karavomarangos_ci \
/bin/bash
Can I use docker instead of podman? Of course, choose your own adventure.
Now, go to /project and run karavomarangos:
karavomarangos --json-file=examples/example.json
It will download the base image, check for listed packages and versions, and generate Dockerfile and README.md files in the same directory.
Don’t have json files? No problem, you can use any of the ones listed in Limani Project.
I also wrote a bash script to be run inside the container that will update all images in one run, you can find it here.
And here is the first commit after using this script: The Commit
Surprise, it worked, some images were updated, and were released to my registry.
Design choices and limitations
Karavomarangos is a tool that I created for my own needs, so it is not meant to be a general purpose tool, but it can be useful for other people with similar needs.
First, it is designed to work with Ubuntu/Debian based images, it assumes that apt is the package manager where new versions will be checked.
Second, it is designed to work with a specific structure of Dockerfiles, the ones I use for the time being. It also generates Dockerfiles from JSON files, not backwards, so it is not meant to be used with existing Dockerfiles.
Finally, even though this tool works only with apt and Ubuntu/Debian based images, the code is split into short and specific functions, it could be extended to use other base distros or package managers, I have not used Red Hat based distros for more than 6 years but who knows how dark the future could become.
Tools I used during this project creation
- Json schemas, for the time being, the JSON files I use do not need semantic structure, they only need a configuration so simple that it can be validated using a schema. No complex validation is needed. Does this JSON follow the schema? It’s ok, let’s generate the Dockerfile, pretty and easy.
- gomplate, If you know me, you know how much I try to avoid any Python related tool and the JSON schema validator was fairly enough here. This template tool can use a lot of data sources, including JSON files, I think it is easy to use after failing 45 times to render your first file.
- git-crypt, I am tired of writing all env variables of each project again and again, using an encrypted file that is shown as plain on my computer and is encrypted on commit is a marvelous solution for personal projects.
- harbor, I have been using GitLab Container registry for years but I wanted to not depend on GitLab, even my own instance, to store my images. I think harbor is two million light years ahead of Pulp, another tool that I tried to use for maintaining my own registry. 6 hours after trying I was unable to delete an old tag…… Harbor can be deployed with a security scan tool which I found very useful.