How to Run a Shell Script inside a Docker Container

My aim is to automate the compatibility test to check if a given version of Nokogiri gem will install in a given version of Ruby. Instead of using the alpine Docker image, we will use the official Ruby docker image as a starting point. The reason is that we need flexibility in what version of Ruby is installed inside the container. Here is the nok.sh:

#!/bin/bash

echo 'installing nokogiri gem'
RUBY_VERSION="$(ruby -v)"

gem install nokogiri -v 1.10.1

success=$?
if [ $success -eq 0 ]; then
  echo "Shell Output: Installation of nokogiri 1.10.1 on Ruby ${RUBY_VERSION} successful"
else
  echo "Shell Output: Failed to install nokogiri  1.10.1 on Ruby ${RUBY_VERSION}"
fi

exit $success

This script tries to install a specific version or Nokogiri gem. I modified the official Ruby docker image Dockerfile to install the bash.

RUN apk add --no-cache bash

To execute the shell script that determines whether nokogiri gem installed successfully, added ENTRYPOINT command to point to the shell script. Before the ENTRYPOINT we need to use COPY command to copy the shell script from the laptop into the container first.

COPY nok.sh /usr/local/bin/
ENTRYPOINT ["nok.sh"]

We can now run another script called doc.sh on the laptop:

#!/bin/bash

echo 'Building docker image'
docker image build -t alpine-ruby .
echo 'Running alpine container'
docker container run alpine-ruby 

This builds the Docker image on the Mac and runs a container based on the customized Dockerfile of official Ruby Docker image. We now see the glorious failure output we have been waiting for:

Shell Output: Failed to install nokogiri  1.10.1 on Ruby ruby 2.3.8p459 (2018-10-18 revision 65136) [x86_64-linux-musl]

The complete Dockerfile:

FROM alpine:3.7

# skip installing gem documentation
RUN mkdir -p /usr/local/etc \
    && { \
        echo 'install: --no-document'; \
        echo 'update: --no-document'; \
    } >> /usr/local/etc/gemrc

ENV RUBY_MAJOR 2.3
ENV RUBY_VERSION 2.3.8
ENV RUBY_DOWNLOAD_SHA256 910f635d84fd0d81ac9bdee0731279e6026cb4cd1315bbbb5dfb22e09c5c1dfe
ENV RUBYGEMS_VERSION 3.0.1

# some of ruby's build scripts are written in ruby
#   we purge system ruby later to make sure our final image uses what we just built
# readline-dev vs libedit-dev: https://bugs.ruby-lang.org/issues/11869 and https://github.com/docker-library/ruby/issues/75
RUN set -ex \
    \
    && apk add --no-cache --virtual .ruby-builddeps \
        autoconf \
        bison \
        bzip2 \
        bzip2-dev \
        ca-certificates \
        coreutils \
        dpkg-dev dpkg \
        gcc \
        gdbm-dev \
        glib-dev \
        libc-dev \
        libffi-dev \
        libxml2-dev \
        libxslt-dev \
        linux-headers \
        make \
        ncurses-dev \
        libressl \
        libressl-dev \
        procps \
        readline-dev \
        ruby \
        tar \
        xz \
        yaml-dev \
        zlib-dev \
    \
    && wget -O ruby.tar.xz "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz" \
    && echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz" | sha256sum -c - \
    \
    && mkdir -p /usr/src/ruby \
    && tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1 \
    && rm ruby.tar.xz \
    \
    && cd /usr/src/ruby \
    \
# https://github.com/docker-library/ruby/issues/196
# https://bugs.ruby-lang.org/issues/14387#note-13 (patch source)
# https://bugs.ruby-lang.org/issues/14387#note-16 ("Therefore ncopa's patch looks good for me in general." -- only breaks glibc which doesn't matter here)
    && wget -O 'thread-stack-fix.patch' 'https://bugs.ruby-lang.org/attachments/download/7081/0001-thread_pthread.c-make-get_main_stack-portable-on-lin.patch' \
    && echo '3ab628a51d92fdf0d2b5835e93564857aea73e0c1de00313864a94a6255cb645 *thread-stack-fix.patch' | sha256sum -c - \
    && patch -p1 -i thread-stack-fix.patch \
    && rm thread-stack-fix.patch \
    \
# hack in "ENABLE_PATH_CHECK" disabling to suppress:
#   warning: Insecure world writable dir
    && { \
        echo '#define ENABLE_PATH_CHECK 0'; \
        echo; \
        cat file.c; \
    } > file.c.new \
    && mv file.c.new file.c \
    \
    && autoconf \
    && gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \
# the configure script does not detect isnan/isinf as macros
    && export ac_cv_func_isnan=yes ac_cv_func_isinf=yes \
    && ./configure \
        --build="$gnuArch" \
        --disable-install-doc \
        --enable-shared \
    && make -j "$(nproc)" \
    && make install \
    \
    && runDeps="$( \
        scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \
            | tr ',' '\n' \
            | sort -u \
            | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
    )" \
    && apk add --no-network --virtual .ruby-rundeps $runDeps \
        bzip2 \
        ca-certificates \
        libffi-dev \
        procps \
        yaml-dev \
        zlib-dev \
    && apk del --no-network .ruby-builddeps \
    && cd / \
    && rm -r /usr/src/ruby \
# make sure bundled "rubygems" is older than RUBYGEMS_VERSION (https://github.com/docker-library/ruby/issues/246)
    && ruby -e 'exit(Gem::Version.create(ENV["RUBYGEMS_VERSION"]) > Gem::Version.create(Gem::VERSION))' \
    && gem update --system "$RUBYGEMS_VERSION" && rm -r /root/.gem/ \
# rough smoke test
    && ruby --version && gem --version && bundle --version

RUN apk add --no-cache bash

# install things globally, for great justice
# and don't create ".bundle" in all our apps
ENV GEM_HOME /usr/local/bundle
ENV BUNDLE_PATH="$GEM_HOME" \
    BUNDLE_SILENCE_ROOT_WARNING=1 \
    BUNDLE_APP_CONFIG="$GEM_HOME"
# path recommendation: https://github.com/bundler/bundler/pull/6469#issuecomment-383235438
ENV PATH $GEM_HOME/bin:$BUNDLE_PATH/gems/bin:$PATH
# adjust permissions of a few directories for running "gem install" as an arbitrary user
RUN mkdir -p "$GEM_HOME" && chmod 777 "$GEM_HOME"
# (BUNDLE_PATH = GEM_HOME, no need to mkdir/chown both)

COPY nok.sh /usr/local/bin/
ENTRYPOINT ["nok.sh"]

Instead of watching videos and working through tutorials, this time I went from a problem and with the clear idea of what I needed to happen and found the solution for this on Docker blog.


Related Articles


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.