jvergeldedios jvergeldedios - 3 months ago 9
Ruby Question

How do I build a Docker image for a Ruby project without build tools?

I'm trying to build a Docker image for a Ruby project. The problem is the project has some gem dependencies that need to build native extensions. My understanding is that I have a couple of choices:


  1. Start with a base image that already has build tools installed.

  2. Use a base image with no build tools, install build tools as a step in the Dockerfile before running
    bundle install
    .

  3. Precompile the native extensions on the host, vendorize the gem, and simply copy the resulting bundle into the image.



1 & 2 seem to require that the resulting image contains the build tools needed to build the native extensions. I'm trying to avoid that scenario for security reasons. 3 is cumbersome, but doable, and would accomplish what I want.

Are there any options I'm missing or am I misunderstanding something?

Answer

I use option 3 all the time, the goal being to end up with an image which has only what I need to run (not to compile)

For example, here I build and install Apache first, before using the resulting image as a base image for my (patched and recompiled) Apache setup.

Build:

if [ "$(docker images -q apache.deb 2> /dev/null)" = "" ]; then
  docker build -t apache.deb -f Dockerfile.build . || exit 1
fi

The Dockerfile.build declares a volume which contains the resulting Apache recompiled (in a deb file)

RUN checkinstall --pkgname=apache2-4 --pkgversion="2.4.10" --backup=no --deldoc=yes --fstrans=no --default
RUN mkdir $HOME/deb && mv *.deb $HOME/deb
VOLUME /root/deb

Installation:

if [ "$(docker images -q apache.inst 2> /dev/null)" = "" ]; then
    docker inspect apache.deb.cont > /dev/null 2>&1 || docker run -d -t --name=apache.deb.cont apache.deb
    docker inspect apache.inst.cont > /dev/null 2>&1 || docker run -u root -it --name=apache.inst.cont --volumes-from apache.deb.cont --entrypoint "/bin/sh" openldap -c "dpkg -i /root/deb/apache2-4_2.4.10-1_amd64.deb"
    docker commit apache.inst.cont apache.inst
    docker rm apache.deb.cont apache.inst.cont
fi

Here I install the deb using another image (in my case 'openldap') as a base image:

docker run -u root -it --name=apache.inst.cont --volumes-from apache.deb.cont --entrypoint "/bin/sh" openldap -c "dpkg -i /root/deb/apache2-4_2.4.10-1_amd64.deb"
docker commit apache.inst.cont apache.inst

Finally I have a regular Dockerfile starting from the image I just committed.

FROM apache.inst:latest

psmith points out in the comments to Building Minimal Docker Image for Rails App from Jari Kolehmainen.
For a ruby application, you can remove the part needed for the build easily with:

bundle install --without development test && \
apk del build-dependencies

Since ruby is needed to run the application anyway, that works great in this case.

I my case, I still need a separate image for building, as gcc is not needed to run Apache (and it is quite large, comes with multiple dependencies, some of them needed by Apache at runtime, some not...)