../
2025-05-16
Docker images built using Ubuntu as a base image often start at hundreds of megabytes, or even over a gigabyte. However, in reality, a running binary program doesn’t depend on many files. If we only keep the files absolutely necessary to run the program, the image size will undoubtedly be greatly reduced.
Here, we’ll take a Rust application as an example and try to package an image that can run ripgrep. Compiled languages like C/C++, Go, and others can follow the same method.
Use the ldd command to view ripgrep’s dynamic link library dependencies:
ldd /usr/bin/rg
Result:
linux-vdso.so.1 (0x00007bd80ef30000)
libpcre2-8.so.0 => /usr/lib/libpcre2-8.so.0 (0x00007bd80e912000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007bd80eefd000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007bd80e722000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2
(0x00007bd80ef32000)
The first entry is inserted into memory by the kernel and can be ignored. Therefore, rg requires a minimum of four .so files to run.
Create a new directory:
mkdir rootfs
Write a script to copy all these .so files to their corresponding locations in rootfs:
#!/bin/bash
copy_file () {
local dir=$(dirname $1)
mkdir -p rootfs/$dir
cp $1 rootfs/$dir
}
for f in $(ldd /usr/bin/rg | grep '=>' | awk '{print $3}'); do
copy_file $f
done
Run the script. Then note:
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2
(0x00007bd80ef32000)
Copy ld-linux-x86-64.so.2 to rootfs/lib64.
mkdir -p rootfs/lib64
cp rootfs/usr/lib64/ld-linux-x86-64.so.2 rootfs/lib64/
Then copy the rg executable itself to rootfs:
cp /usr/bin/rg rootfs/
At this point, the file system for the Docker image is ready.
Create the Dockerfile:
FROM scratch
COPY rootfs /
ENTRYPOINT ["/rg"]
Create the Docker image:
sudo docker build -t myrg .
Attempt to run it:
sudo docker run --rm myrg --version
Result: $ sudo docker run –rm myrg –version ripgrep 14.1.1
features:+pcre2
simd(compile):+SSE2,-SSSE3,-AVX2
simd(runtime):+SSE2,+SSSE3,+AVX2
PCRE2 10.43 is available (JIT is available)
The Docker image build is complete.
Check the size:
sudo docker images
Result:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/myrg latest 89ce8f41feaa 4 minutes ago 9.23 MB
As you can see, it is only 9.23 MB.
In fact, the principle behind this method is the same as creating a chroot jail; you can also run it using chroot:
sudo chroot rootfs /rg --version
In a real-world scenario, if you were actually deploying an application this way, it would be best to run it with a non-root user for security reasons.
sudo chroot --userspec 1000:1000 rootfs /rg --version
Some applications, especially those in dynamic languages like Python, heavily rely on using dlopen to dynamically load .so files. These files cannot be found using ldd and thus cannot be packaged using this method. Although one could also use strace to analyze system calls and see exactly which files the program opened, it is still difficult to ensure that all dynamically linked libraries are copied and loaded.
Therefore, this method is generally only suitable for compiled languages, such as C/C++, Go, Rust, Haskell, and so on.
Mistivia - https://mistivia.com