Bundler GemspecError when building inside Docker container - ruby

People were reporting this error and solved it by deleting a folder holding cached gems. Since I'm working inside a Docker container, this didn't seem like helpful advice for me. Indeed, the cache folder was not empty but did contain two items (I guess they are there from gem install bundle).
This is my (faulty) dockerfile
FROM nginx
RUN apt-get update -qqy && \
apt-get install -qqy \
build-essential \
ruby-full \
ruby-dev
RUN gem install bundle
RUN useradd -ms /bin/bash udo
USER udo
WORKDIR /home/udo
COPY . .
RUN bundle install
RUN bundle exec -- jekyll build
USER root
RUN cp -r _site/* /usr/share/nginx/html
I could fix the error by replacing RUN bundle install with
RUN bundle install --path=tmp
at the expense of having this local tmp directory that I then had to add to the exclude list in the _config.yml for Jekyll.
I'd prefer to know what was actually going on. I suspect there's a problem with the way I use the non-root user.
Note: bundle install --no-cache did not help.

Related

gem command not found when install rbenv in debian

I'm creating a Dockerfile to run truffleruby. I'm getting an error when trying to install bundler and foreman. The error is /bin/sh: 1: gem: not found
Dockerfile
FROM debian:buster-slim
# Install packages for building ruby
RUN apt update -y && apt install -y git curl libssl-dev libpq-dev libreadline-dev zlib1g-dev \
autoconf bison build-essential libyaml-dev \
libreadline-dev libncurses5-dev libffi-dev libgdbm-dev
RUN apt clean
# Install rbenv and ruby-build
RUN git clone https://github.com/sstephenson/rbenv.git /root/.rbenv
RUN git clone https://github.com/sstephenson/ruby-build.git /root/.rbenv/plugins/ruby-build
RUN /root/.rbenv/plugins/ruby-build/install.sh
ENV PATH /root/.rbenv/bin:$PATH
RUN echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh # or /etc/profile
RUN echo 'eval "$(rbenv init -)"' >> .bashrc
RUN . ~/.bashrc
RUN rbenv install truffleruby-20.3.0
RUN rbenv global truffleruby-20.3.0
RUN rbenv rehash
ENV BUNDLER_VERSION=2.2.4 NODE_ENV=production RAILS_ENV=production RAILS_SERVE_STATIC_FILES=true RAILS_LOG_TO_STDOUT=true PORT=3000
ENV CONFIGURE_OPTS --disable-install-doc
RUN apt-get install -y curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
apt-get update && apt-get install -y nodejs && \
apt-get clean
RUN rbenv versions
RUN gem install bundler:2.2.4 foreman
RUN mkdir /app
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment 'true'
RUN bundle config set --local without 'development test'
RUN bundle install
COPY . .
EXPOSE 3000
CMD ["foreman", "start"]
tail of build
Removing intermediate container 1a445fde7fc0
---> 43c3d72b7eb6
Step 17/27 : RUN rbenv versions
---> Running in feb5bb9361cc
* truffleruby-20.3.0 (set by /root/.rbenv/version)
Removing intermediate container feb5bb9361cc
---> c7d1a5826af5
Step 18/27 : RUN gem install bundler:2.2.4 foreman
---> Running in 998461afc89c
/bin/sh: 1: gem: not found
The command '/bin/sh -c gem install bundler:2.2.4 foreman' returned a non-zero code: 127
You don't generally use version managers like rbenv in Docker. There are a couple of reasons for this. One is that an image usually only contains a single application and its single runtime, so you'd never have more than one Ruby in an image and therefore there's no need to switch. A second is that most common paths of running containers (including docker run and the Dockerfile RUN directive) don't look at shell dotfiles like .bashrc or /etc/profile, so the version manager setup will never get run.
TruffleRuby is distributed (among other ways) as a standalone tar file so you can just install that in your Dockerfile. I'd make the Dockerfile look roughly like:
FROM debian:buster-slim
# Install the specific dependency packages TruffleRuby recommends
# (build-essential is much larger but might actually be necessary)
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive \
apt-get install --no-install-recommends --assume-yes \
curl \
gcc \
libssl-dev \
libz-dev \
make
# Download and unpack TruffleRuby
ARG TRUFFLERUBY_VERSION=20.3.0
ENV PATH /opt/truffleruby-$TRUFFLERUBY_VERSION-linux-amd64/bin:$PATH
RUN cd /opt \
&& curl -L https://github.com/oracle/truffleruby/releases/download/vm-$TRUFFLERUBY_VERSION/truffleruby-$TRUFFLERUBY_VERSION-linux-amd64.tar.gz | tar xz \
&& /opt/truffleruby-$TRUFFLERUBY_VERSION-linux-amd64/lib/truffle/post_install_hook.sh
# Now build and install your application
RUN gem install bundler:2.2.4
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment 'true'
RUN bundle config set --local without 'development test'
RUN bundle install
COPY . .
ENTRYPOINT ["bundle", "exec"]
EXPOSE 3000
CMD ["rails", "start"]
You can reasonably split this into two separate Dockerfiles. End the first one before the "build and install your application" comment, and build it with docker build -t myname/truffleruby:20.3.0 -f Dockerfile.truffleruby .. Then the second one can begin with FROM myname/truffleruby:20.3.0 in the same way as the standard Docker Hub ruby image.
Does the same work with CRuby?
I'd suspect RUN doesn't read shell files, so PATH needs to be modified.
RUN git clone https://github.com/sstephenson/rbenv.git /root/.rbenv
...
RUN /root/.rbenv/plugins/ruby-build/install.sh
ENV PATH /root/.rbenv/bin:$PATH
seems a bit weird, is rbenv cloned and installed in the same place?
I would skip rbenv in Docker, and instead just:
RUN ruby-build truffleruby ~/.rubies/truffleruby
ENV PATH $HOME/.rubies/truffleruby/bin:$PATH

Bundle install error in ruby docker image

I am trying to create a docker image using a ruby app using the docker file:
FROM jruby:latest
# Install apt based dependencies required to run Rails as
# well as RubyGems. As the Ruby image itself is based on a
# Debian image, we use apt-get to install those.
RUN apt-get update && apt-get install -y \
build-essential \
nodejs \
git \
ruby-dev \
gcc \
libffi-dev \
make \
zlib1g-dev \
libssl-dev \
libreadline6-dev \
libyaml-dev
# Configure the main working directory. This is the base
# directory used in any further RUN, COPY, and ENTRYPOINT
# commands.
RUN mkdir -p /app
WORKDIR /app
# Copy the Gemfile as well as the Gemfile.lock and install
# the RubyGems. This is a separate step so the dependencies
# will be cached unless changes to one of those two files
# are made.
COPY Gemfile Gemfile.lock ./
RUN gem install bundler && bundle install --jobs 20 --retry 5
# Copy the main application.
COPY . ./
# Expose port 3000 to the Docker host, so we can access it
# from the outside.
EXPOSE 3000
but i get an error when i'm trying to build this docker image
the gem install RedCloth is giving an error saying that I need to install development tools first.
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
current directory: /usr/local/bundle/gems/RedCloth-4.3.2/ext/redcloth_scan
/opt/jruby/bin/jruby -r ./siteconf20181127-31-1zsapr.rb extconf.rb
checking for main() in -lc... RuntimeError: The compiler failed to generate an
executable file.
You have to install development tools first.
How do I fix this?
jruby has often has trouble with native extensions. In this case, the redcloth gem used to provide precompiled binaries for jruby but as of version 4.3 (the version you're attempting to install) they are no longer available:
Precompiled binary gems are provided for JRuby and Win32 platforms prior to version 4.3.
According to the README, you should be able to run rake compile to build the binaries. This may be a lot of extra work in your Docker image to make work, so I would recommend trying an older version of redcloth that has the binaries you need first. (keep in mind, the 4.2 series was released in 2011 and may not have the functionality you need)

How to install ruby and bundler with dockerfile?

New to ruby and bundler here.
I am installing them in a docker image with this docker file:
FROM alpine:3.5
# Install Ruby, Ruby Bundler and other ruby dependencies
RUN apk add --update \
ruby ruby-bigdecimal ruby-bundler \
ca-certificates libressl \
libressl-dev build-base ruby-dev \
ruby-rdoc ruby-io-console ruby-irb; \
\
&& bundle config build.nokogiri --use-system-libraries; \
&& bundle config git.allow_insecure true; \
\
&& gem install json foreman --no-rdoc --no-ri; \
&& gem cleanup; \
&& rm -rf /usr/lib/ruby/gems/*/cache/*; \
&& apk del libressl-dev build-base ruby-dev; \
&& rm -rf /var/cache/apk/* /tmp;
CMD ["bundle"]
When I run do a docker run I get:
Don't run Bundler as root. Bundler can ask for sudo if it is needed,
and installing your bundle as root will break this application for all
non-root users on this machine.
Could not locate Gemfile or .bundle/ directory
How do I resolve this? I just want to install ruby and ruby-bundle and be done with this ...
There are pre built ruby images (e.g Alpine 3.11 Ruby 2.7) that include bundler. It's easier to start with them as they generally use the current "best practices" to build.
Notice that they set the BUNDLE_SILENCE_ROOT_WARNING environment variable with ENV directive in the image build to remove that root warning.
You normally wouldn't run bundler as the CMD for a container either, you might run bundler during a RUN image build step though.
Running containers as non-root users is not a bad idea in any case. Use the USER directive to change that.
FROM ruby:2.7
WORKDIR /app
ADD . /app/
RUN set -uex; \
bundle install; \
adduser -D rubyapp; \
mkdir -p /app/data; \
chown rubyapp /app/data
USER rubyapp
CMD [ "ruby", "whatever.rb" ]

prevent rebuilding whole docker container everytime? improving speed

Dockerizing a Rails application taking ages to rebuild the container.
I tried to ADD as far at the end but not possible I think more.
Any suggestions on how to improve the rebuild speed of my docker container?
Or general suggestions on how to improve the docker file, it takes very long to rebuild every time.
Also are there smart ways to check if for example a directory already exist without throwing an error and not be able to complete the build?
FROM ruby:2.2.0
EXPOSE 80
EXPOSE 22
ENV RAILS_ENV production
RUN apt-get update -qq && apt-get install -y build-essential
# --------------------------------------
# GEM PRE-REQ
# --------------------------------------
#RUN apt-get install -y libpq-dev
#RUN apt-get install -y libxml2-dev libxslt1-dev #nokigiri
#RUN apt-get install -y libqt4-webkit libqt4-dev xvfb
RUN cd /tmp && git clone https://github.com/maxmind/geoipupdate && cd geoipupdate && ./bootstrap
# --------------------------------------
# HOME FOLDER
# --------------------------------------
WORKDIR /srv/my
ADD . /srv/my
ADD ./Gemfile /srv/my/Gemfile
ADD ./Gemfile.lock /srv/my/Gemfile.lock
#RUN mkdir /srv/my
RUN bundle install --without development test
#RUN bundle install foreman
RUN bundle exec rake assets:precompile --trace
# --------------------------------------
# UNICORN AND NGINX
# --------------------------------------
ADD ./config/_server/unicorn_my /etc/init.d/unicorn_my
RUN chmod 755 /etc/init.d/unicorn_my
RUN update-rc.d unicorn_my defaults
ADD ./config/_server/nginx.conf /etc/nginx/sites-available/default
RUN apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
#RUN chown -R www-data:www-data /var/lib/nginx ??
ADD ./config/_server/nginx.conf /etc/nginx/my.conf
ADD ./config/_server/my.conf /etc/nginx/sites-enabled/my.conf
ADD ./config/_server/unicorn.rb /srv/my/config/unicorn.rb
ADD ./config/_server/Procfile /srv/my/Procfile
#RUN service unicorn_my start
#RUN foreman start -f ./Procfile
You can improve your build speed by:
Install all of your requirement as early as possible.
Combine all apt-get/yum into a single command, after that clean up the apt/yum cache. It can decrease your image size.
Sample:
RUN \
apt-get -y update && \
apt-get -y install curl build-essential nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Put ADD/COPY as late as possible, because it will invalidate the Docker image cache.
Avoid put long-running task (e.g: apt-get, download large file, etc.) after ADD/COPY file or directory that is often changed.
Docker take a "snapshot" for each your command. So, when you build a new image from same state (no Dockerfile/file/directory change), it should be fast.
Comment/uncomment Dockerfile in order to reduce apt-get install time might not help you, because it will invalidate your Docker cache.

Docker: Do I need to rebuild the container from scratch whenever I need to add software?

So I'm experimenting with Docker, and I set up a Rails App talking to postgres. Everything works, I'm able to access the app, migrate the database, etc.
Dockerfile
FROM ruby:2.2.1
RUN apt-get update -qq && apt-get install -y build-essential
RUN apt-get install -y libxml2-dev libxslt1-dev
RUN apt-get install -y libqt4-webkit libqt4-dev xvfb
RUN apt-get install -y nodejs
ENV APP_HOME /code
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME
RUN bundle install
docker-compose.yml
web:
build: .
command: bin/rails server --port 3000 --binding 0.0.0.0
ports:
- "3000:3000"
volumes:
- .:/code
links:
- db
db:
image: postgres
ports:
- "5432:5432"
After making a couple models and a controller, I decided to use Slim templates for the project and added it to my Gemfile
gem 'slim'
I would have expected to be able to update this dependency on the web container by running bundle install on it:
docker-compose run web bundle install
Which appears to work at first, it installs the gem and it's dependencies. However these are just on a throw-away copy of the built container. IT doesn't stick around when I run docker-compose up the next time.
The only way to get the gem in permanently is to build web from scratch again, which can take 3-4 minutes to install all the gems from scratch again, mostly due to nokigiri.
Is this the only way to "update" a base image? I would expect I would only need to rebuild if I'm modifying the Dockerfile itself, when it would make sense to rebuild from scratch.
If you want, you can also update an existing image by "committing" the changes you've made to a container. The syntax is docker commit <container ID> <image>.
Alternatively you can take advantage of the build cache, by separating your bundle install command into individual gem installs. Then you can add your new gem to the end and it'll reuse all the previous layers when you rebuild the image. This may not be ideal considering that there may be overlapping dependencies.

Resources