At Pantographe, we use GitLab CE to manage our projects. And we use GitLab CI to run different tests.
For a while we used the official ruby Docker image. But Rails applications need nodejs
to be installed for the asset pipeline and since Rails 5.1, for webpacker
.
At the beginning we simply installed nodejs
package with apt-get install
on every job we ran. But it unnecessarily spend time, that's why we create our own ruby Docker image.
This image is just for our own internal usage, so we don't really need to store it into the official Docker repository. And since GitLab comes with its own Docker repository we decided to use it.
So we create a new repository to build and manage our own image.
Structure
We would like to have the ability to manage multiple versions in one projet. So we took a look at the repository of the official ruby Docker image to understand how they did.
So the structure simply looks like this:
├── .gitlab-ci.yml
├── 2.4
│ └── Dockerfile
├── 2.5
│ └── Dockerfile
├── ...
│
└── README.md
For now we don't build variant as slim
or alpine
but we plan to. And this structure permits us to simply do it in the future by adding subdirectories per variant like following.
├── ...
├── 2.4
│ ├── alpine
│ │ └── Dockerfile
│ ├── slim
│ │ └── Dockerfile
│ └── Dockerfile
└── ...
Docker image
Now it's time to write our Docker image. We don't need to build everything from scratch, we only need to install some more packages. So we simply use the official ruby Docker image and we run our extra commands.
# FROM ruby:2.5 # for our 2.5 image version
FROM ruby:2.5
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
wget \
nodejs \
zlib1g-dev \
build-essential \
libssl-dev \
libreadline-dev \
libyaml-dev \
libsqlite3-dev \
sqlite3 \
libxml2-dev \
libxslt1-dev \
libcurl4-openssl-dev \
python-software-properties \
libffi-dev \
postgresql-client \
libpq-dev \
git-core \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
RUN gem install pg
Don't forget to install your packages in less lines possible and to purge cache at the end to have image with small size (see: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run).
GitLab CI configuration
To make our image usable we need to build and store it. For that work we use GitLab CI to automatise the process.
For this we need to edit the .gitlab-ci.yml
file like following:
image: docker:latest
services:
- docker:dind
variables:
VARIANT: ""
DOCKER_DRIVER: overlay2
stages:
- test
- release
- tag
before_script:
- docker info
- docker login -u "gitlab-ci-token" -p "$CI_JOB_TOKEN" "$CI_REGISTRY_IMAGE"
- cd "$VERSION/$VARIANT"
- image="ci-ruby:${VERSION}${VARIANT:+-${VARIANT}}"
- registry_image="$CI_REGISTRY_IMAGE:${VERSION}${VARIANT:+-${VARIANT}}"
after_script:
- docker images
.test_template: &test_template
stage: test
script:
- apk add --no-cache git bash
- git clone --depth 1 https://github.com/docker-library/official-images.git ~/official-images
- docker build --pull -t "$image" .
- ~/official-images/test/run.sh "$image"
.release_template: &release_template
stage: release
script:
- docker build --pull -t "$registry_image" .
- docker push "$registry_image"
- full_ruby_version=$(docker run --rm "$registry_image" ruby -e "puts RUBY_VERSION")
- docker tag "$registry_image" "$CI_REGISTRY_IMAGE:${full_ruby_version}"
- docker push "$CI_REGISTRY_IMAGE:${full_ruby_version}"
only:
- main
except:
- schedules
# Ruby 2.4
test:2.4:
<<: *test_template
variables:
VERSION: "2.4"
release:2.4:
<<: *release_template
variables:
VERSION: "2.4"
# Ruby 2.5
test:2.5:
<<: *test_template
variables:
VERSION: "2.5"
release:2.5:
<<: *release_template
variables:
VERSION: "2.5"
# Tags
tag:latest:
<<: *release_template
stage: tag
variables:
VERSION: "2.5"
script:
- docker pull "$registry_image"
- docker tag "$registry_image" "$CI_REGISTRY_IMAGE:latest"
- docker push "$CI_REGISTRY_IMAGE:latest"
dependencies:
- release:2.5
In this example we test and build 2.4
and 2.5
versions of ruby. We also create a docker tag named latest
which targets the 2.5
image version.
We also read the exact version of ruby build to tag it too. For example for 2.5
image we add a tag as 2.5.3
.
Why not simply install packages on CI job
Like we explained at the beginning of the article, installing packages on every job waste time unnecessarily.
By installing packages on a Docker image, we optimise our process and save time for these jobs execution. It also allows us to centralise the main dependencies of our projet.
Why don't use existing image?
Using an existant image would do the job but we prefer to have the hand on the image and could easily make evolutions for all our projects without having to edit the .gitlab-ci.yml
file of each project we have.
Conclusion
This example we tell you is specific of our usage. But it was a good example to show you how to build a Docker image with GitLab CI.