Building a Tiny Docker Image

28 April 2019 - Docker, Container, Assembler

Introduction

Tomorrow, I will do a session at a high school for computer science. They asked me to speak about Docker container and related technologies like Kubernetes. In the session, I want to highlight that containers are not just VMs. So I decided to build a tiny, tiny Docker image using assembler language.

Image with NASM

We will use the Netwide Assembler NASM to build the executable for our tiny container. Let’s build an Alpine-based image and install NASM in it. Here is a Dockerfile for that:

FROM i386/debian
LABEL author="Rainer Stropek @rstropek"

# Install packages necessary for installing NASM
RUN apt-get update \
  && apt-get install -y \
       asciidoc \
       build-essential \
       curl \
       xmlto

# Default version of NASM that we want to install
ARG NASMVERSION=2.14.02

# Download and install NASM
RUN curl --output nasm-${NASMVERSION}.tar.gz -L https://www.nasm.us/pub/nasm/releasebuilds/${NASMVERSION}/nasm-${NASMVERSION}.tar.gz \
  && tar -xzf nasm-${NASMVERSION}.tar.gz --directory /usr/local/src \
  && cd /usr/local/src/nasm-${NASMVERSION}/ \
  && ./configure \
  && make \
  && make install

# Run shell if container is started
CMD ["/bin/bash"]

Build the Docker image with docker build -t rstropek/nasm-gcc . (replace rstropek with your Docker Hub user).

Assembler Hello World

Next, let’s write an assembler program with some Linux Kernel calls. I call it hello.asm.

        BITS 32
        SECTION .data       ; DATA
msg:    db "Hello World",10 ; This is the text we want to print.
                            ; The 10 at the end means "next line" (see
                            ; http://www.asciitable.com/)
len:    equ $-msg           ; We calculate the length of the text by
                            ; subtracting the memory address of msg
                            ; from the current address ("$")

        SECTION .text       ; PROGRAM CODE
        global _start       ; Program starts at _start
_start:
                            ; We use the Linux syscall "write"
                            ; (see http://man7.org/linux/man-pages/man2/write.2.html)
        mov edx, len        ; edx receives the length of the string
                            ; edx is a so called "register" (Details see
                            ; https://en.wikipedia.org/wiki/Processor_register)
        mov ecx, msg        ; ecx receives the address of the text
        mov ebx, 1          ; 1 means "stdout" = screen
        mov eax, 4          ; 4 identifies the syscall "write"
        int 0x80            ; Call Linux kernel with interrupt 0x80

                            ; To exit, we use the syscall "exit"
                            ; (see http://man7.org/linux/man-pages/man2/exit.2.html)
        mov ebx, 0          ; 0 means "no error"
        mov eax, 1          ; 1 identifies the syscall "exit"
        int 0x80

Generate Tiny Image

Now we can use our previously created image rstropek/nasm-gcc with NASM to generate our tiny image in a multi-staged build process:

# Use a multi-stage build
FROM rstropek/nasm-gcc AS build

# Arguments for gcc:
# - Compile for 32bit
# - Link statically
# - Optimize for size
# - No standard system startup file (we have our own _start)
# - No unwind tables (to make our executable even smaller)
ARG GCCARGS="-m32 -static -Os -nostartfiles -fno-asynchronous-unwind-tables"

WORKDIR /src
COPY hello.asm /src

# Compile and link our assembler code
RUN nasm -f elf hello.asm \
  && gcc ${GCCARGS} -o hello hello.o \
  && strip -R .comment -s hello

FROM scratch
COPY --from=build /src/hello /
CMD ["/hello"]

Build the Docker image with docker build -t rstropek/hello-world . (replace rstropek with your Docker Hub user).

Let’s Check the Size

rainer@dockervm:~/containerdata/nasm$ docker images rstropek/hello-world
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
rstropek/hello-world   latest              53fc14858574        8 minutes ago       484B

484 Bytes, nice. Does it work?

rainer@dockervm:~/containerdata/nasm$ docker run rstropek/hello-world
Hello World

Yes it does :-)