Hyper Open Edge Cloud

Glibc incompatibiliy with Docker

Showing how building software with Docker on Debian can lead to runtime incompatibilities between Glibc and the kernel. Summary: Docker cannot be used reliably to create portable binaries.
  • Last Update:2025-09-08
  • Version:001
  • Language:en

Glibc incompatibility with Docker 

Setup

Build machine runs Ubuntu 24 with kernel 6.8.0-71-generic and glibc 2.39.
Target machine runs Debian 11 with kernel 5.10.0-35-amd64 and glibc 2.31.

We install Docker from official instructions on both machines.

Debian:

sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Ubuntu:

sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

Test Program

We will use the memfd_secret syscall which was introduced in Linux kernel 5.14 and supported in glibc 2.35.

The test program acts differently whether the __NR_memfd_secret symbol is defined or not. This is a realistic scenario where one might want to enable specific optimizations based on features from more recent Kernel versions and skip these if the kernel functions are not available. 

Copy the following to a main.c file:

#include <unistd.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    #ifndef __NR_memfd_secret
        printf("Doing stuff without memfd\n");
    #else
        printf("Doing stuff with memfd\n");
        long ret = syscall(__NR_memfd_secret, 0);
        if (ret == -1) {
            perror("syscall memfd_secret failed");
            return 1;
        } else {
        printf("syscall memfd_secret succeeded\n");
        }
    #endif

    return 0;
}

The execution should follow the first branch of the #ifndef condition since the system call is not available with our current kernel (not defined with our glibc).

Dockerfile

The Dockerfile is very straightforward, we pull the latest GCC and use it to compile our test program.

Copy the following to a Dockerfile file in the same directory:

FROM gcc:latest
COPY . /Test
WORKDIR /Test/
RUN gcc -o test-program main.c
CMD ["./test-program"]

Building the image

We build the image on our host machine (kernel 6.8) and confirm it runs as expected:

ubuntu:~$ sudo docker image build -t test-program ./
ubuntu:~$ sudo docker container run -it test-program
Doing stuff with memfd
syscall memfd_secret succeeded

Getting the image on the target machine

We can generate a tar archive and copy it to our target machine using scp. Adapt the following command to match your target username and address:

ubuntu:~$ sudo docker save -o image.tar test-program
ubuntu:~$ sudo scp ./image.tar  user@address:/home/user/image.tar

Running it on the target machine

If we compile and run the test program on our target machine (kernel 5.10), the binary should print out "Doing stuff without memfd".
But if we decompress and run the docker image, we get:

debian:~$ sudo docker load -i ~/image.tar 
Loaded image: test-program:latest
debian:~$ sudo docker container run -it test-program:latest
Doing stuff with memfd
syscall memfd_secret failed: Function not implemented

The execution isn't as expected, the binary attempted to run the system call and failed because it's not supported by the kernel.

Conclusion

Docker cannot be used reliably to create portable binaries.

In the docker image, the program was compiled against a newer and independant glibc that defines the __NR_memfd_secret symbol. This results in unexpected behavior on older systems. On the other hand, using an older glibc in the docker image would only result in unexpected behavior on systems that support the memfd system call. We cannot create a generic docker image.