<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title> - Writing</title>
    <link rel="self" type="application/atom+xml" href="https://www.kurtismullins.com/blog/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.kurtismullins.com/blog/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-03-04T00:00:00+00:00</updated>
    <id>https://www.kurtismullins.com/blog/atom.xml</id>
    <entry xml:lang="en">
        <title>Exploring Container Runtimes with Scratch Images</title>
        <published>2023-05-29T00:00:00+00:00</published>
        <updated>2026-03-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Kurtis Mullins
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.kurtismullins.com/blog/single-file-containers/"/>
        <id>https://www.kurtismullins.com/blog/single-file-containers/</id>
        
        <content type="html" xml:base="https://www.kurtismullins.com/blog/single-file-containers/">&lt;p&gt;It has been a little while since I&#x27;ve done anything interesting with Kubernetes.
Curious about what information a Pod has available to it, I decided to take a
stab at inspecting a Pod&#x27;s contents; especially what a Pod knows about itself
and the cluster.&lt;&#x2F;p&gt;
&lt;p&gt;Most container images have a lot of files and may declare, or set, their own
environment variables. This would be too much to sift through just to find out
the difference between what a container image&#x27;s author created and what was
added by Kubernetes at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;While there is documentation on &lt;a rel=&quot;external&quot; title=&quot;Kubernetes Container Environment&quot; href=&quot;https:&#x2F;&#x2F;kubernetes.io&#x2F;docs&#x2F;concepts&#x2F;containers&#x2F;container-environment&#x2F;&quot;&gt;what information is available&lt;&#x2F;a&gt;
from within a Kubernetes pod, there is also a lot of &lt;a rel=&quot;external&quot; title=&quot;Expose Pod Information to Containers Through Files&quot; href=&quot;https:&#x2F;&#x2F;kubernetes.io&#x2F;docs&#x2F;tasks&#x2F;inject-data-application&#x2F;downward-api-volume-expose-pod-information&#x2F;&quot;&gt;potential
complexity&lt;&#x2F;a&gt;
and the answer may not always be straight-foward.&lt;&#x2F;p&gt;
&lt;p&gt;Essentially, I wanted an easy, minimal container that can simply run an
executable file or script. My goal was to log the filesystem tree and
environment variables. To make things a bit more complicated, I wanted to do
this from my MacOS laptop.&lt;&#x2F;p&gt;
&lt;p&gt;At first, I decided to look at &lt;a rel=&quot;external&quot; title=&quot;A tool to build OCI images&quot; href=&quot;https:&#x2F;&#x2F;buildah.io&quot;&gt;Buildah&lt;&#x2F;a&gt; and the &lt;a rel=&quot;external&quot; title=&quot;Docker Scratch (empty) Image&quot; href=&quot;https:&#x2F;&#x2F;docs.docker.com&#x2F;build&#x2F;building&#x2F;base-images&#x2F;&quot;&gt;scratch image&lt;&#x2F;a&gt;. It was
an interesting tool I experimented with while working at &lt;a rel=&quot;external&quot; title=&quot;Red Hat&quot; href=&quot;https:&#x2F;&#x2F;www.redhat.com&quot;&gt;Red Hat&lt;&#x2F;a&gt; on
&lt;a rel=&quot;external&quot; title=&quot;Red Hat Quay.io Container Registry&quot; href=&quot;https:&#x2F;&#x2F;www.quay.io&quot;&gt;Quay&lt;&#x2F;a&gt;. Unfortunately, it (understandably) does not have support to run on
anything besides Linux without some &lt;a rel=&quot;external&quot; title=&quot;Is buildah meant to be Linux only tool?&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;buildah&#x2F;issues&#x2F;156&quot;&gt;work-arounds&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;My next approach was using the infamous &lt;a rel=&quot;external&quot; title=&quot;Open-source cross-platform software to create machine images&quot; href=&quot;https:&#x2F;&#x2F;www.packer.io&quot;&gt;Packer&lt;&#x2F;a&gt; application. While I
have never used Packer, I&#x27;ve heard many great things and its one of those tools
that seemed to follow me through my career. While Packer appears to run on MacOS,
it &lt;a rel=&quot;external&quot; title=&quot;Unable to use packer with distroless base images&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hashicorp&#x2F;packer&#x2F;issues&#x2F;8120&quot;&gt;does &lt;em&gt;not&lt;&#x2F;em&gt; support the scratch image&lt;&#x2F;a&gt; I need to
produce a single-file container. That&#x27;s okay -- the
docs looked great and I&#x27;ll find some other purpose for it down the road.&lt;&#x2F;p&gt;
&lt;p&gt;After realizing that I just needed the scratch &quot;image&quot;, I was curious if I can
simply define a Dockerfile that uses it. Not only is it possible, it is
a &lt;a rel=&quot;external&quot; title=&quot;Docker Scratch (empty) Image&quot; href=&quot;https:&#x2F;&#x2F;docs.docker.com&#x2F;build&#x2F;building&#x2F;base-images&#x2F;&quot;&gt;documented Docker feature&lt;&#x2F;a&gt;! The answer was right in front of my face. So off I
went to see if this would even work.&lt;&#x2F;p&gt;
&lt;p&gt;All containers, which are the meat of a Pod, keep one or more processes contained; hence the name. When you
define a container image, you need to specify some sort of entry point or
otherwise tell your runtime (e.g. Docker) what to execute. Typically, I could just throw
together a quick shell script and set that as my entrypoint; it would make the
subsequent calls to list the environment variables. I would also be able to copy
or install the tool &lt;a rel=&quot;external&quot; title=&quot;list contents of directories in a tree-like format&quot; href=&quot;https:&#x2F;&#x2F;manpages.ubuntu.com&#x2F;manpages&#x2F;noble&#x2F;man1&#x2F;tree.1.html&quot;&gt;tree&lt;&#x2F;a&gt; to print the contents of the filesystem as seen
from the container&#x27;s perspective.&lt;&#x2F;p&gt;
&lt;p&gt;One important thing to note about using Linux containers is that you do not have
access to anything outside of the container unless they&#x27;re explicitly provided (e.g. mounted volumes and environment variables); this includes dynamically linked
libraries. In summary, if you want to &lt;code&gt;echo&lt;&#x2F;code&gt; or &lt;code&gt;printf()&lt;&#x2F;code&gt; within a container
then you&#x27;re typically going to bundle a lot of &lt;a rel=&quot;external&quot; title=&quot;userland definition&quot; href=&quot;https:&#x2F;&#x2F;en.wiktionary.org&#x2F;wiki&#x2F;userland&quot;&gt;userland&lt;&#x2F;a&gt;. This is why most
container images are large and have a lot of files.&lt;&#x2F;p&gt;
&lt;p&gt;There is a fairly popular language called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;go.dev&quot;&gt;Go&lt;&#x2F;a&gt; which has a great feature: it can
easily &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Static_build&quot;&gt;statically compile
code&lt;&#x2F;a&gt; -- no additional
files are required to run its executables. It also provides its own standard
library which is packed full of useful features including logging, reading
environment variables and working with filesystem. Go can also compile cross-platform
executables, which would be pretty useful here since I am compiling Linux software
on my Mac. &lt;em&gt;Quick side note:&lt;&#x2F;em&gt; Docker on Mac is really just using a Virtual
Machine running Linux in the background.&lt;&#x2F;p&gt;
&lt;p&gt;Out of habit, I decided to skip Go&#x27;s cross-platform compilation and instead take
advantage of a Docker feature I really appreciate called &lt;a rel=&quot;external&quot; title=&quot;Docker multi-stage builds&quot; href=&quot;https:&#x2F;&#x2F;docs.docker.com&#x2F;build&#x2F;building&#x2F;multi-stage&#x2F;&quot;&gt;multi-stage builds&lt;&#x2F;a&gt;.
For the uninitiated, its a great way to use one image for building and another
image to run or distribute an application. In this case, I am going to use the
community &lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hub.docker.com&#x2F;_&#x2F;golang&quot;&gt;golang&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; (the combination of &lt;em&gt;go&lt;&#x2F;em&gt; and &lt;em&gt;language&lt;&#x2F;em&gt;) image to compile my
program, throw away everything besides the executable, and then run it within an otherwise empty container.&lt;&#x2F;p&gt;
&lt;p&gt;If this is confusing, don&#x27;t worry -- I am running through this information
quickly and touching on topics that I&#x27;ve spent years learning. Perhaps one day it could make for a good presentation or
talk. Regardless, I believe the following two snippets will make a lot of sense
if you play around with it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Dockerfile&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;dockerfile&quot;&gt;# This is a multi-stage build.
#
# Useful Docs:
# - https:&#x2F;&#x2F;docs.docker.com&#x2F;build&#x2F;building&#x2F;multi-stage&#x2F;
# - https:&#x2F;&#x2F;docs.docker.com&#x2F;build&#x2F;building&#x2F;base-images&#x2F;#create-a-simple-parent-image-using-scratch
#
# The first stage, which uses the &amp;quot;golang&amp;quot; image,
# simply compiles the application and stores it in &#x2F;src&#x2F;kurtismullins&#x2F;.
#
FROM golang
WORKDIR &#x2F;src&#x2F;kurtismullins&#x2F;
COPY myapp.go .
RUN go build -o myapp myapp.go

#
# The second stage, based on &amp;quot;scratch&amp;quot;,
# copies the executable from the previous container
# and declares that it should be run by default.
#
FROM scratch
COPY --from=0 &#x2F;src&#x2F;kurtismullins&#x2F;myapp &#x2F;usr&#x2F;local&#x2F;bin&#x2F;myapp
CMD [&amp;quot;&#x2F;usr&#x2F;local&#x2F;bin&#x2F;myapp&amp;quot;]
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;myapp.go&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;go&quot;&gt;package main

import (
	&amp;quot;fmt&amp;quot;
	&amp;quot;os&amp;quot;
)

func main() {
	&#x2F;&#x2F; Print all Environment Variables
	fmt.Println(os.Environ())
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With those two files in the same directory, I used Docker to build and run the
container. The test was a success!&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;$ docker build . -t myapp
$ docker run myapp
[PATH=&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;sbin:&#x2F;bin HOSTNAME=af31fb5e860c HOME=&#x2F;]
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My next steps would be to add logging of the filesystem and run this within
Kubernetes. I&#x27;ll save that for another day.&lt;&#x2F;p&gt;
&lt;p&gt;Food for thought: This general approach may be useful when debugging or learning more about other environments where containers are
first-class components such as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;lambda&#x2F;&quot;&gt;AWS Lambda&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cloud.google.com&#x2F;run&#x2F;&quot;&gt;Google&#x27;s Cloud Run&lt;&#x2F;a&gt;, or even the latest generation
of hosting services such as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fly.io&quot;&gt;fly.io&lt;&#x2F;a&gt;. Those platforms have
their own requirements which may, or may not, be compatible with a minimal &lt;code&gt;FROM scratch&lt;&#x2F;code&gt; image.&lt;&#x2F;p&gt;
&lt;p&gt;Happy Hacking!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-03-04):&lt;&#x2F;strong&gt; Renamed the title to better reflect the content and direction of its follow-up series.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
