Jonathan Altman

Dotcom Thousandaire

May 25, 2020

TL;DR

It is possible without having a heavier-weight container engine like docker or kubernetes installed and without using Dockerfiles to create container images that will run as a container from the command line and from scratch.

There is value in doing so:

  • It is educational
  • It opens up new ways to think about how container images are constructed, packaged, and dependency-managed

Why?

I had 2 goals in starting to do this. First was to understand more about the actual structure of a container image. How it was constructed and what the pieces were. The Open Container Initiative specifications made this task somewhat easier, not by providing me a specification to analyze and understand, but allowing a bunch of excellent tooling written to that standard to be created that could be used to explore the process by both inspecting functioning containers and trying to create one by hand.

The second goal was to explore alternate patterns in container construction. When I started I knew that container image filesystems used an overlay filesystem (overlayFS) that effectively sequentially unpacked gzipped tar images of a disk layout and then deltas (adds, modifies, and deletes) against that first layout. In many instances those container images are or can be heavily shared and the container management system (a docker or a kubernetes) is typically efficient at caching and re-using layers that are identical where it can.

Given that, I wanted to see if reducing the build process to just constructing the layers in as efficient a manner as possible and without the overhead of a container orchestration engine that could run the container layers would open up new ways of thinking about container image construction, its packaging, and dependency management. More on that later.

Setup

Prerequisites

Ideally, do this on Linux because the tools we'll use all build/run/release most easily there. Most of this does work on OSX, but it was quicker/easier to get it up on Linux. multipass is good stuff for this on OSX. Instructions for installing it are at https://multipass.run/

Tools To Install

Install the following tools:

  • umoci: umoci is the tool that actually creates configures, and manipulates container image layouts on disk. Install it by downloading a release from their releases page and installing it somewhere on your path
  • skopeo (installation instructions): skopeo is a command line utility we will use to manage, pack, unpack, and inspect Open Container Initiative (OCI) container images, and move containers into/out of and between registries and/or local disk. This is what we use to take the umoci-manipulated containers and package them up to be accessible to a container engine like docker or a container registry, and to handle moving container images we create around between local storage and container registries
  • runc: runc is a standard container runner that trades performance and features for standards-compliance against the OCI specification. runc is useful to validate that the built containers function without needing all of a docker or kubernetes available, but is not required to actually build a container
  • [Optional][go compiler](https://golang.org/). If you need/want to write or compile programs for the images you create to run.

Handrolling a Container Image

We will reproduce the equivalent of a docker "scratch" image with the following steps. It will effectively function like the hello-world:latest container image in Dockerhub.

First Time Ever: Creating New OCI Storage

umoci init the storage area for the container images

Create/Find a go-Based Executable For the Container

Coding and building an actual go executable that can be used are outside the scope of what I want to cover. However, don't forget GOOS=linux GOARCH=amd64 if you're not compiling the code on an Intel x86-based Linux machine.

In my simple case, I created a hello.go file that just printed "Hello, world!" to stdout and compiled it to a binary called hello.

Create/Update a New OCI Image Layout on Disk

New Image Layout Initialization

You only have to do this when you start a new image. Once this is done, everything is just making changes to your image layout.

  • umoci new the container image we're trying to create, which puts pretty much just a scratch container into the storage area. Might be good to have it be a different name referencing the scratch concept

Make Your Changes to the Image Layout

  • skopeo unpack the container image into a disk directory for the actual usable container image we want
  • make whatever structure adds/changes/deletions you want/copy in, create, modify, delete whatever files you need in the rootfs directory by whatever means necessary
  • skopeo pack the directory back into the umoci OCI container structure area with the name you actually want for your container (convention over configuration same name as directory you skopeo unpack'ed into) and set a useful tag. If you're updating an existing container image this probably means version tag bumping using whatever version tag scheme is in place
  • umoci config the image to at a minimum --config.cmd but maybe also config.entrypoint and any env variables you need/want
  • runc the newly-added container if you want to confirm it works
  • skopeo copy the container into whatever container registry you'd like and make sure that works
  • validate the container image is accessible and runs by pulling it down and running it via docker/kubernetes/etc.

Done!