The smallest Docker image - less than 1000 bytes

Note trans. : The author of this material is the architect at Barclays and the Open Source-enthusiast from Great Britain Ian Miell. It aims to make a convenient Docker image (with a “sleeping” binary), which does not need to be downloaded, but rather simply copied via copy & paste. By the method of trial, error and experiments with the Assembler-code, it achieves the goal by preparing an image smaller than a kilobyte in size.



Here it is (base64 encoded)
H4sICIa2A1sCA2IA7Vrrbts2FFYL7M9+7QUGGNyfDYhtkuJFFLAhWZOhBYJmaLMOWBAEFC+xVlkyJLpYEBjdY+0l+k6jfGvqtkEWp2qD8TMg8vAqnsNzDg9lQhhmEjHDhY4zgWJBBUQJ5ZnCGAubMUQMyhJqoRRMJxYbo7Q2CedYxlQO/myqMroeEEHICIngApspxohEKI4h5DHmGEUQQw7jqAejDjBtnKz9q2w7zubi7gkugazVKHdGuWltQArkWDMCdoCqSpufg/QSPK4aV8pxW+nL96uxzMu39G+NqRe5PeekGj13Oi9BamXRmCtl1dS9X2jqel147C7W+aOJKd8dZ04dlcqsSw7KVyA9Ab/uHT/+cTht6mFRKVkMmywv0yv0mnxbMc8sSP8Apzvg0ViDtJwWxQ54Mpbny5W9qIrp2DSrmt+r+mVenu/ny+UelK6+mFR56VYtjsqfp3mxHupQZqZYdp/NGeo850x99r9j7QloyWEz8kvpK//47vuymvzQ29vf79m8MKnIaIa8bUmwRdByw6TKREIoIzE3xBrjrY7MGDUilomQ3GrNrFaIKqSZ4lkvL3tD12sn/IQCrI10xtcC7C1kH9I+xseQpYilRAwoZ5AI9IcfWFfqpRfzK1M3eeUZDRAfQDGAfc/jHTDKG1fVXiInlzcfctnwLPP9Vszs9VXvUzFy5jlZV5WzTbtN3cWkZWkhL/yS2gXm1p7lumkl24wkpv51FbYcU0EZy7SV0ucEZowkiCjvLbAVikCaGUqhyjT0c0Lj/YrElmmSWANOZ7MooHPwRCiLRaJEzBXKFGTCy49lUHNKjEigVdD6H4uTzPj9wzDCSawU0TQT2ujhjVwjgZzSj/n/eX7D/xPm/T8N/v/Ll/+Lg2fPnxw93eL85xFvyB9Rn4TzXwdAAxiMYLD/t9f/7eM/xDja1P+YBf3vKP7L2+PnttsA/IfjcQiE7nkgdH18Ey4O7pjdH7ygmX0p9n8eFA5aG3pb+0/eP/9jzFmw/13AdTBHK3/OPx7/Ic4X8qecQ9K244QG/98JXh8c/vLwwYM1/TD6KWqpv6LdOb37gT67URKterTpVxu1V9PXq3lW1d8skn++9Y83f4cDeEBAQMBnwliWuTWNu8l33G38/3X3fzGk79wFQ4S4Lwr+vwOcXIJHy4ANkLv4L4APcJ6ZSXUsz+efh1xaSOf3VxstHS6+H/nSu4s6wOns9OugxrdG7WXV5K6qc9NEn0n/ESab+s9o0P+O7v9ce1WzVNI7uAiczYI6BgQEBNwD/AvqV/+XACoAAA==

How did I come to this?


Once a colleague showed a Docker image that he used to test Kubernetes clusters. He did nothing: just ran under and waited for you to kill him.

“Look, it takes only 700 kilobytes! Its really fast to download! ”

And then I was curious what kind of minimal Docker image I can create. I wanted to get one that could be encoded in base64 and sent literally anywhere by simple copy & paste. Since the Docker image is just a tar file, and a tar file is just a file, everything should work out.

Tiny Binary


First of all, I needed a very small Linux binary that doesn't do anything. It will take a bit of magic - and here are two wonderful, informative and worthy articles about creating small executable files:


I didn’t need “Hello World”, but a program that just sleeps and works on x86_64. I started with an example from the first article:

  SECTION .data msg: db "Hi World",10 len: equ $-msg SECTION .text global _start _start: mov edx,len mov ecx,msg mov ebx,1 mov eax,4 int 0x80 mov ebx,0 mov eax,1 int 0x80 

Run:

 nasm -f elf64 hw.asm -o hw.o ld hw.o -o hw strip -s hw 

It turns out the binary is 504 bytes .

But still, not “Hello World” is needed ... First, I found out that the .data or .text sections are unnecessary and no data loading is required. In addition, the upper half of the _start section deals with text output. In the end, I tried the following code:

 global _start _start: mov ebx,0 mov eax,1 int 0x80 

And he compiled already in 352 bytes .

But this is not the desired result, because the program simply completes its work, but we need it to sleep as well . As a result of additional research, it turned out that the mov eax command fills the processor register with the corresponding Linux system call number, and int 0x80 makes the call itself. This is described in more detail here .

And here I found the desired list. Syscall 1 is exit , and the one we need is syscall 29:pause . The result was such a program:

 global _start _start: mov eax, 29 int 0x80 

We saved another 8 bytes: the compilation produced a result of 344 bytes , and now it is a suitable binary that does nothing and waits for a signal.

Delving into hexes


It is time to get a chainsaw and deal with the binary ... For this, I used a hexer , which is essentially vim for binary files with the ability to directly edit hexes. After lengthy experiments, I got from this:



… this:



This code does the same thing, but notice how many lines and spaces are gone. In the course of my work, I was guided by such a document , but by and large it was a way of trial and error.

So, the size has decreased to 136 bytes .

Less than 100 bytes?


I wanted to know if it was possible to go further. After reading this , I assumed that it would be possible to reach 45 bytes, but - alas! - not. The tricks described there are designed only for 32-bit binaries, but for 64-bit ones they did not work.

The best that I managed to do was to take this 64-bit version of the program and embed it in my system call:

 BITS 64 org 0x400000 ehdr: ; Elf64_Ehdr db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident times 8 db 0 dw 2 ; e_type dw 0x3e ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize dw 1 ; e_phnum dw 0 ; e_shentsize dw 0 ; e_shnum dw 0 ; e_shstrndx ehdrsize equ $ - ehdr phdr: ; Elf64_Phdr dd 1 ; p_type dd 5 ; p_flags dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_align phdrsize equ $ - phdr _start: mov eax, 29 int 0x80 filesize equ $ - $$ 

The resulting image is 127 bytes . At this point, I stopped trying to reduce the size, but accept the offers.

Tiny Docker Image


Now, when there is a binary that implements endless waiting, it remains to put it in the Docker image.

To save every possible byte, I created a binary with a file name of one byte - t - and placed it in the Dockerfile , creating an almost empty image:

 FROM scratch ADD t /t 

Note that there is no CMD in the Dockerfile , as this would increase the size of the image. To run, you will need to pass a command through the arguments to the docker run .

Then the docker save created a tar file, and then compressed it with maximum gzip compression. The result is a ported Docker image file of less than 1000 bytes in size:

 $ docker build -tt . $ docker save t | gzip -9 - | wc -c 976 

I also tried to reduce the size of the tar file by experimenting with the Docker manifest file, but in vain: because of the specifics of the tar format and the gzip compression algorithm, such changes only increased the final gzip. I tried other compression algorithms, but gzip turned out to be the best for this small file.

PS from translator


Read also in our blog:

Source: https://habr.com/ru/post/413959/


All Articles