-
-
Notifications
You must be signed in to change notification settings - Fork 4
Walkthrough of Dockerfile for ghpages docker
This will be a quick walkthrough of the Dockerfile for ghpages-docker
and is intended to give a leg up to anyone who may need to modify this file in the future, but who doesn't have a lot of prior experience with Docker. It is not a thorough explanation of Docker or a How-To on creating a Dockerfile. (For that, see Docker's Getting Started guide and guide to writing a Dockerfile.)
Please note that the Dockerfile itself is already fairly thoroughly commented, but this guide should offer a more detailed supplement to those comments.
Disclaimer: I know very little about Ruby and Linux, and I knew nothing about Dockerfiles when I first began researching this issue, so some of this info may be wrong or incomplete. Future Hack for LA devs should feel free to edit as they see fit.
FROM ruby:2.7.3-alpine3.13 AS build
Most Dockerfiles begin by specifying a previously-created base image to build on, using the FROM
command.
This particular Dockerfile also uses a multistage build, in which the first stage is used to install packages that are required only for the image build process, and the second stage is used to build the final image by copying over only the required files from the first stage. This results in a much smaller image, which speeds up download times. Note that both build stages begin with a FROM
command, but only this one includes the information AS build
. Naming the build in this way will allow us to reference it later when we want to copy files from it into a new build stage.
ENV GEM_BIN=/usr/gem/bin
ENV GEM_HOME=/usr/gem
These lines assign values to environment variables that can then be accessed by Ruby when gems are installed, and also referenced later in the Dockerfile.
RUN apk --no-cache add \
zlib-dev \
libffi-dev \
build-base \
libxml2-dev \
imagemagick-dev \
readline-dev \
libxslt-dev \
libffi-dev \
yaml-dev \
zlib-dev \
vips-dev \
vips-tools \
sqlite-dev \
cmake
RUN apk --no-cache add \
linux-headers \
openjdk8-jre \
less \
zlib \
libxml2 \
readline \
libxslt \
libffi \
git \
nodejs \
tzdata \
shadow \
npm \
libressl \
yarn
The base image we're using is built on Alpine Linux, a super lightweight Linux distro. Because it's so streamlined, there are a lot of utilities it doesn't automatically come with. The lines above install various utilities we'll need later via the Alpine Package Keeper (apk
). It's likely that not all are truly required, but Docker won't be able to install the github-pages
gem later without some of them.
RUN echo "gem: --no-ri --no-rdoc" > ~/.gemrc
RUN unset GEM_HOME && unset GEM_BIN && \
yes | gem update --system
When you need to execute a Linux command within a Dockerfile, you can do so using by prefacing it with the RUN
command. There may not be any gems to update in our image at this point, but these lines are nevertheless held over from the original Jekyll Docker
because, frankly, they don't seem to break anything.
RUN gem install github-pages -- \
--use-system-libraries
This is the line where the github-pages
gem, which bundles all the dependencies required to run a GitHub Pages site locally, is installed. Note that no version number is specified, so every time an image is built from this Dockerfile the latest version will automatically be installed.
FROM ruby:2.7.3-alpine3.13
LABEL maintainer "Jordon Bedwell <[email protected]>"
Same as Build Stage 1, but putting a FROM
command here tells Docker that we're starting over with a new build stage. This time we also add a LABEL
command to set metadata for the author of the Dockerfile. In this case, because ghpages-docker
is based on the Jekyll Docker
image by Jordon Bedwell, his name has been left as the maintainer.
COPY copy/all /
This line tells Docker to copy everything in the copy/all
directory of the build context, which is the directory in which the Dockerfile is located, into the root of the new image. These shell scripts were written for Jekyll Docker
and have been held over because the Docker container breaks without them.
ENV GEM_BIN=/usr/gem/bin
ENV GEM_HOME=/usr/gem
Because it's a new build stage, we have to treat it as if it's an entirely separate image from the one created during the first build stage. Nothing persists from the first stage unless we manually copy it over, and things like variables need to be reinitialized in order to apply in the new build stage. (There are fancier ways of carrying a variable over from a previous build stage, but for the sake of easy comprehension the code has simply been duplicated here.)
ENV JEKYLL_BIN=/usr/jekyll/bin
# Set system ENV variables
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV TZ=America/Chicago
ENV PATH="$JEKYLL_BIN:$PATH"
ENV LC_ALL=en_US.UTF-8
ENV LANGUAGE=en_US
The first line specifies a path in the new image where Jekyll can store its binaries. The rest are basic Linux system values.
RUN apk --no-cache add \
bash \
su-exec
The shell scripts we copied over at the beginning of this build stage won't run without bash
and su-exec
.
COPY --from=build /usr/gem/ /usr/gem/
This is an example of how to copy over a package that was installed in a previous build stage, using the --from
flag and specifying the name of the previous build stage we want to copy from. In the beginning of our first stage, in the line FROM ruby:2.7.3-alpine3.13 AS build
, we called that stage build
.
So, what we're telling Docker to do here is to copy the github-pages
gem, which was installed into a directory called /usr/gem/ in the previous build stage, into an identical directory within the new build stage.
RUN addgroup -Sg 1000 jekyll
RUN adduser -Su 1000 -G \
jekyll jekyll
RUN mkdir -p /var/jekyll
RUN mkdir -p /srv/jekyll
RUN chown -R jekyll:jekyll /srv/jekyll
RUN chown -R jekyll:jekyll /var/jekyll
RUN rm -rf /home/jekyll/.gem
RUN rm -rf /usr/gem/cache
RUN rm -rf /root/.gem
CMD ["jekyll", "--help"]
ENTRYPOINT ["/usr/jekyll/bin/entrypoint"]
WORKDIR /srv/jekyll
VOLUME /srv/jekyll
EXPOSE 35729
EXPOSE 4000
The CMD
command specifies what command(s) Docker should run by default when it starts up a container based on this image. ENTRYPOINT
is similar.
WORKDIR
sets the working directory that other commands within the Dockerfile will refer to, and VOLUME
tells Docker to create a mount point at the specified directory.
Finally, EXPOSE
tells Docker which ports to open from within the container so that it can be accessed from the host OS. In this case, 4000 is the port at which the server will display the locally-hosted website, and 35729 is required to enable live reload.
We'll ultimately use the information specified in these last lines in our docker-compose.yml
file to make sure our local Jekyll server starts correctly with the docker compose up
command.