DEV Community

Cover image for Designing containers for GitHub actions
Juan Julián Merelo Guervós
Juan Julián Merelo Guervós

Posted on • Updated on

Designing containers for GitHub actions

GitHub actions are workflows triggered by GitHub events. They are quite flexible, they run by default in "bare" operating system runners, but, additionally, they can be hosted in user-provided containers. The problem is, GitHub expect these containers to have certain services and structure, which is a problem if you use a standard or plain container.

Through the creation of this container, and multiple questions in the GitHub community, I've kinda discovered what are the things needed for containers to be used seamlessly in workflows. Here're the tips:

GHA user is UID=1001

While the first user you will create in any Linux system will have UID equal to 1000, GHAs are a bit special and they use UID 1001. So you'd better use that UID for your user, as is done in this Raku container

FROM alpine:latest as base


ENV PKGS="git make gcc musl-dev perl linux-headers bash"

RUN apk update && apk upgrade \
    && apk add --no-cache $PKGS \
    && git clone --depth 1 --branch ${RAKU_RELEASE} \
    && cd MoarVM \
    && perl --prefix /usr \
    && make --print-data-base \
    && make install\
    && cd .. \
    && git clone --depth 1 --branch ${RAKU_RELEASE} git:// \
    && cd nqp \
    && perl --backends=moar --prefix /usr \
    && make install \
    && cd .. \
    && git clone --depth 1 --branch ${RAKU_RELEASE} \
    && cd rakudo \
    && perl --backends=moar --prefix /usr \
    && make install \
    && ls /usr/share/nqp/

FROM alpine:latest
ARG UID=1000

LABEL version="0.5.0" maintainer="" raku_release=${RAKU_RELEASE} raku_user_uid=${UID}

COPY --from=base /usr/lib/ /usr/lib
COPY --from=base /usr/share/nqp/ /usr/share/nqp
COPY --from=base /usr/share/perl6/ /usr/share/perl6
COPY --from=base /usr/bin/moar /usr/bin/nqp /usr/bin/raku /usr/bin/perl6 /usr/bin/rakudo /usr/bin/

RUN mkdir /github \
    && addgroup -S raku  && adduser -S raku -G raku --uid ${UID}

USER raku
WORKDIR /home/raku

Enter fullscreen mode Exit fullscreen mode

It's a bit longish, but the real deal is just above these lines. You can use that UID by default, but in my case I wanted two versions, just in case I needed more modifications later for GHS

Spoiler: I did

So I use an ARG that carries the UID, with 1000 by default, and there's a specific build-arg for GHAs. And that's that.

Is that all? Not really.

GHAs reset HOME

The container mounts a directory into /github/home and then calls it $HOME. Don't really know why, but it does. This can wreak a bit of havoc in languages for containers that expect home to be where it was originally used to install whatever. That can be easily fixed by just setting HOME manually at a job level, but it can also be fixed adjusting the search path for installed modules, which in the case of Raku means setting RAKULIB, as done here:


ENV PKGS="git tar" PKGS_TMP="make gcc linux-headers musl-dev" WORKDIR="/home/raku"
LABEL version="1.0.3" maintainer="" rakuversion=$VER

USER root
RUN apk update && apk upgrade && apk add --no-cache $PKGS $PKGS_TMP
USER raku

# Environment
ENV PATH="${WORKDIR}/.raku/bin:${WORKDIR}/.raku/share/perl6/site/bin:${PATH}" \

# Basic setup, programs and init
RUN git clone --depth 1 \
    && cd zef && raku -I. bin/zef install . \
    && zef install Linenoise \
    && cd .. && rm -rf zef

USER root
RUN apk del $PGKS_TMP
USER raku

Enter fullscreen mode Exit fullscreen mode

This is already GHA-specific, as revealed by the base image. $RAKULIB is defined midway through the file, with the inst# prefix indicating it's already precompiled and ready to go. Raku will search there first, and then proceed to the supposed place, inst#/github/home/.raku. Where there will be noting in the raw container. Will it always be empty? Not by a long shot, because it will be used to install distributions by Raku, and thus it will be the place to cache. But something else is needed first.

Check also that we're including git in this image. Also useful for the checkout action (although this one has all kinds of defaults, and it's not actually essential).

You need to install GNU tar first

Alpine is an amazing little distro for containers, but part of its appeal comes from the fact that it puts a lot of external utilities into BusyBox, and that includes tar. However, in an apparently undocumented move, the caching GHA uses BSD or GNU tar for storing and restoring artifacts. That means it's needed in your container. Check above:

ENV PKGS="git tar" PKGS_TMP="make gcc linux-headers musl-dev" 
// and later
RUN apk update && apk upgrade && apk add --no-cache $PKGS 
Enter fullscreen mode Exit fullscreen mode

By installing tar, the image will be a bit heavier (in this case, taking 10 seconds to download and start), but not really a big deal.

And you're ready to go

See it in action here. Once the container is GHA-ready, you can run (at least these) Github actions in your workflow as easily as if you were running the default base runner.

Top comments (2)

kirklewis profile image
Kirk Lewis

This is useful Juan. 👍

jj profile image
Juan Julián Merelo Guervós

Glad you liked it. Thanks for reaching out.