berlin
berlin

Reputation: 526

Manually build image with packages and dependencies

i am trying to build up a base image with certain components only - in my case, just some packages to run a java application. The idea is for this to be as minimal as possible.

My question is how can I get the bare minimum required to get the most optimal image and get all the dependencies for my packages(like openssl) to run without an issue?

I am aware of this answer. However, it shows that we still need to manually copy all the dependencies. is there a better way to do it? such that i can automatically copy all the dependencies and the executable itself to the new image.

Upvotes: 0

Views: 1426

Answers (1)

larsks
larsks

Reputation: 311238

I once wrote a tool called dockerize that does most of what you want. Given a command, it reads the name of the dynamic linker embedded in that command, and then uses that to get a list of dependencies, which it uses to build a Docker image.

So for example, run as dockerize -o openssl /usr/bin/openssl, this produces a directory with a Dockerfile and all the necessary files:

openssl
├── Dockerfile
├── etc
│   ├── group
│   ├── nsswitch.conf
│   └── passwd
├── lib64
│   ├── ld-linux-x86-64.so.2
│   ├── libcrypto.so.1.1
│   ├── libc.so.6
│   ├── libdl.so.2
│   ├── libnss3.so
│   ├── libnssckbi.so
│   ├── libnss_compat-2.33.so
│   ├── libnss_compat.so.2
│   ├── libnss_dns-2.33.so
│   ├── libnss_dns.so.2
│   ├── libnss_files-2.33.so
│   ├── libnss_files.so.2
│   ├── libnss_libvirt_guest.so.2
│   ├── libnss_libvirt.so.2
│   ├── libnss_mdns4_minimal.so.2
│   ├── libnss_mdns4.so.2
│   ├── libnss_mdns6_minimal.so.2
│   ├── libnss_mdns6.so.2
│   ├── libnss_mdns_minimal.so.2
│   ├── libnss_mdns.so.2
│   ├── libnss_myhostname.so.2
│   ├── libnss_mymachines.so.2
│   ├── libnss_resolve.so.2
│   ├── libnss_sss.so.2
│   ├── libnsssysinit.so
│   ├── libnss_systemd.so.2
│   ├── libnssutil3.so
│   ├── libpthread.so.0
│   ├── libresolv-2.33.so
│   ├── libresolv.so
│   ├── libresolv.so.2
│   ├── libssl.so.1.1
│   └── libz.so.1
└── usr
    └── bin
        └── openssl

Building a Docker image from this directory results in a fully functional openssl binary.


Now, you're asking to this within the context of a multi-stage Dockerfile. That might look like this:

FROM ubuntu:bionic as base

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
        git \
        openssl \
        python3 \
        python3-pip \
        rsync \
        && \
    apt-get clean

RUN pip3 install git+https://github.com/larsks/dockerize

RUN dockerize -o /tmp/openssl -n /usr/bin/openssl

FROM scratch
COPY --from=base /tmp/openssl/ /
ENTRYPOINT ["/usr/bin/openssl"]

This installs dockerize in the build stage, and uses that to generate a directory with the target command and its dependencies, then copies this into the scratch image in the final stage.


There's nothing special about the dockerize script; it's mostly a convenience wrapper and we could achieve a similar result like this:

FROM ubuntu:bionic as base

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
        openssl \
        && \
    apt-get clean

RUN mkdir /tmp/openssl

# The following command (a) produces a list of files to copy into
# the target directory, and then (b) uses `xargs` and `cp` to copy
# them. 
#
# We list the binary itself, the appropriate dynamic interpreter, and
# then we use `ldd` to get a list of dependencies from the binary.
RUN (echo /usr/bin/openssl; \
  echo /lib64/ld-linux*; \
    ldd /usr/bin/openssl | awk '/=>/ {print $3}') | \
    xargs -iFILE sh -c 'mkdir -p /tmp/openssl/$(dirname FILE); cp FILE /tmp/openssl/FILE'

FROM scratch
COPY --from=base /tmp/openssl/ /
ENTRYPOINT ["/usr/bin/openssl"]

For your specific example, this also produces a runnable openssl image.

Upvotes: 2

Related Questions