How to Build a Docker Image for a Ruby Project Without Build Tools

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

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...)

How to install new gems in a rails docker image without rebuilding it

A simple solution is to cache the gems in a docker volume. You can create a volume in docker and attach it to the path to bundle gems. This will maintain a shared state and you will not require to install the gems in every container you spun.

  container_name: repository_api
build:
context: ../..
dockerfile: repository_docker/development/repository_api/Dockerfile
user: $UID
env_file: .env
stdin_open: true
environment:
DB_NAME: ${POSTGRES_DB}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_USER: ${POSTGRES_USER}
DB_HOST: ${POSTGRES_DB}
volumes:
- ../../repository_api:/var/www/repository/repository_api
- bundle_cache:/usr/local/bundle
networks:
- proxy
- internal
.
.
volumes:
bundle_cache:

Also, a/c to bundler.io, the official Docker images for Ruby assume that you will use only one application, with one Gemfile, and no other gems or Ruby applications will be installed or run in your container. So once you have added all the gems required in your application development, you can remove this bundle_cache volume and rebuild your image with your final Gemfile.

Should I Compile My Application Inside of a Docker Image

You can, but not in your final image, as that would mean a much larger image than necessary: it would include all the compilation tool, instead of limiting to only what you need to execute the resulting binary.

You can see an alternative in "How do I build a Docker image for a Ruby project without build tools?"

  • I use an image to build,
  • I commit the resulting stopped container as a new image (with a volume including the resulting binary)
  • I use an execution image (one which only contain what you need to run), and copy the binary from the other image. I commit again the resulting container.

The final image includes the compiled binary and the execution environment.

Installing gems with native extension on docker image

Your image is missing the gcc/g++ compiler and therefore it can't build any native code, as you can clearly see from the error message:

You have to install development tools first.

You can install the build-essential metapackage to get a working build environment inside your image.

RUN apt-get install -y build-essential

Of course you need the above line before the gem install RUN command you already have

Configure Dockerfile to Run Cron Tasks using Whenver and Rake

This is the solution to the problem I listed above. I had some issues at the Dockerfile and schedule.rb. This is what I had to change to make it work correctly.

Dockerfile

  • wrong echo call
  • wrong bundle command
  • change ENTRYPOINT instead of CMD
FROM ruby:2.5.3-alpine3.8

RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/main && \
apk update && apk upgrade && \
apk add build-base bash dcron && \
apk upgrade --available && \
rm -rf /var/cache/apk/* && \
mkdir /usr/app

WORKDIR /usr/app

COPY Gemfile* /usr/app/

RUN bundle install

COPY . /usr/app

RUN bundle exec whenever -c && bundle exec whenever --update-crontab && touch ./log/cron.log

ENTRYPOINT crond && tail -f ./log/cron.log

config/schedule.rb

  • no need to ENV.each
every 1.minutes do
rake 'hello:start'
end

UPDATE

I've created a GitHub repository and a Docker Hub repository to share with the community this progress.



Related Topics



Leave a reply



Submit