Skip to content

Instantly share code, notes, and snippets.

@bcardiff
Last active May 29, 2024 17:37
Show Gist options
  • Save bcardiff/85ae47e66ff0df35a78697508fcb49af to your computer and use it in GitHub Desktop.
Save bcardiff/85ae47e66ff0df35a78697508fcb49af to your computer and use it in GitHub Desktop.
List binary dependencies to build a minimal docker image from scratch
unless ARGV.size > 0
puts " Missing executable file argument"
puts " Usage (in a Dockerfile)"
puts " RUN crystal run ./path/to/list-deps.cr -- ./bin/executable"
exit 1
end
executable = File.expand_path(ARGV[0])
unless File.exists?(executable)
puts " Unable to find #{executable}"
exit 1
end
puts " Extracting libraries for #{executable} ..."
deps = [] of String
output = `ldd #{executable}`.scan(/(\/.*)\s\(/) do |m|
library = m[1]
deps << library
real_lib = File.real_path(library)
deps << real_lib if real_lib != library
end
puts " Generating Dockerfile"
puts
puts "=" * 30
puts "FROM scratch"
deps.each do |dep|
puts "COPY --from=0 #{dep} #{dep}"
end
puts "COPY --from=0 #{executable} /#{File.basename(executable)}"
puts "ENTRYPOINT [\"/#{File.basename(executable)}\"]"
puts "=" * 30
@timkendall
Copy link

timkendall commented Nov 18, 2017

I ran into a weird issue when trying either of the above approaches - image built and booted fine but could no longer connect to my Postgres instance. Haven't resolved it yet.

@timkendall
Copy link

Looks like the lower level error from crystal-pg is No address found for localhost:5432

@dishcandanty
Copy link

@timkendall did you ever end up figuring out the resolution issues?

@plainas
Copy link

plainas commented Feb 22, 2018

@timkendall and @discandanty Those are off topic, but I would guess somewhere you are mapping ports for the container from the first stage. From the the "FROM scratch" line on, it's another dockerfile, so to speak. If this is confusing check the documentation for docker multi stage build https://docs.docker.com/v17.09/engine/userguide/eng-image/multistage-build/

More on the topic... @martinandert , I took that approach, fetched all the dependencies with ldd and copy them to the container and works like a charm. A question though: are there any pitfalls of just building statically?

Say:

FROM crystallang/crystal:latest
ADD . /src
WORKDIR /src
RUN crystal build src/miniserver.cr --static

FROM scratch
COPY --from=0 /src/bin/miniserver /miniserver

ENV WWW=/www
EXPOSE 80

ENTRYPOINT ["/miniserver"]

@charlie-hadden
Copy link

@timkendall @dishcandanty I'm late to the party on this but I ran into the same issue. Ultimately I ended up using the ldd approach, and adding the following lines to my Dockerfile:

COPY --from=0 /lib/x86_64-linux-gnu/libnss_dns.so.2 /lib/x86_64-linux-gnu/libnss_dns.so.2
COPY --from=0 /lib/x86_64-linux-gnu/libresolv.so.2 /lib/x86_64-linux-gnu/libresolv.so.2

This was enough to fix the issue for me.

@LeonLiuY
Copy link

LeonLiuY commented May 17, 2018

@charlie-hadden

I don't know how do you figure out those 2 files. I tried another approach and it works:

RUN shards build --production --static
FROM busybox:glibc

That's it, static build + busybox:glibc

See also: crystal-lang/crystal#6099

@LeonLiuY
Copy link

LeonLiuY commented May 17, 2018

@plainas FYI, the issue I met in the above link is exactly a problem of static build.

@ababich
Copy link

ababich commented Jun 14, 2018

busybox:glibc makes no sense as we try to make scratch images

@fusillicode
Copy link

fusillicode commented Sep 29, 2018

I'm trying @martinandert suggestion but after successfully build the image I end up with the following error when I try to run it:

docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown.

Is there anyone encountering the same problem? 🤔
Btw I'm using the Docker Community Edition Version 2.0.0.0-beta1-mac75 (27117), Channel: edge

@kalinon
Copy link

kalinon commented Sep 27, 2019

Had issues with scratch using 0.31.0. I think its because it cant find uname:

Unhandled exception: execvp (/bin/sh "-c" "uname \"${@}\"" "--"): No such file or directory: No such file or directory (Errno)
  from usr/share/crystal/src/process.cr:296:52 in 'initialize:shell:input:output:error'
  from usr/share/crystal/src/process.cr:251:3 in 'new:shell:input:output:error'
  from usr/share/crystal/src/process.cr:583:3 in '`'
  from usr/share/crystal/src/openssl/bio.cr:81:7 in '~Pinger::OS:init'
  from usr/share/crystal/src/crystal/once.cr:255:3 in 'once'
  from usr/share/crystal/src/crystal/once.cr:48:3 in '__crystal_once'
  from src/lib/pinger/src/pinger.cr:15:3 in '__crystal_main'
  from usr/share/crystal/src/crystal/main.cr:97:5 in 'main_user_code'
  from usr/share/crystal/src/crystal/main.cr:86:7 in 'main'
  from usr/share/crystal/src/crystal/main.cr:106:3 in 'main'
  from __libc_start_main
  from _start
  from ???

However, changing it alpine worked.

FROM crystallang/crystal:latest

ADD . /src
WORKDIR /src
RUN shards build --production
RUN ldd bin/miniserver | tr -s '[:blank:]' '\n' | grep '^/' | \
    xargs -I % sh -c 'mkdir -p $(dirname deps%); cp % deps%;'

FROM alpine
COPY --from=0 /src/deps /
COPY --from=0 /src/bin/miniserver /miniserver

ENTRYPOINT ["/miniserver"]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment