Designing containers for GitHub actions
Tags: devops, githubactions, ci, cd
Published: 2021-12-30 12:11:01

---
title: Designing containers for GitHub actions
published: true
description: GitHub actions jobs can be run in container. But you need to tailor them specifically for that, or they will fail. Here's how to do it.
tags: devops, GithubActions, CI, CD
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edzx2thewxajcjsmrebj.jpg
---

[GitHub actions](https://github.com/features/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](https://docs.github.com/es/actions/using-github-hosted-runners/about-github-hosted-runners#docker-container-filesystem). 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](https://github.com/JJ/alpine-raku/), and multiple questions in the [GitHub community](https://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](https://github.com/JJ/docker-raku)

```Dockerfile
FROM alpine:latest as base

ARG RAKU_RELEASE=2021.12

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} https://github.com/MoarVM/MoarVM.git \
    && cd MoarVM \
    && perl Configure.pl --prefix /usr \
    && make --print-data-base \
    && make install\
    && cd .. \
    && git clone --depth 1 --branch ${RAKU_RELEASE} git://github.com/Raku/nqp.git \
    && cd nqp \
    && perl Configure.pl --backends=moar --prefix /usr \
    && make install \
    && cd .. \
    && git clone --depth 1 --branch ${RAKU_RELEASE} https://github.com/rakudo/rakudo.git \
    && cd rakudo \
    && perl Configure.pl --backends=moar --prefix /usr \
    && make install \
    && ls /usr/share/nqp/

FROM alpine:latest
ARG UID=1000

LABEL version="0.5.0" maintainer="JJMerelo@GMail.com" raku_release=${RAKU_RELEASE} raku_user_uid=${UID}

COPY --from=base /usr/lib/libmoar.so /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

ENTRYPOINT ["raku"]
```

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](https://github.community/t/dont-quite-see-the-point-of-container-runners-setting-home/218476), 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`](https://docs.raku.org/language/modules#Finding_installed_modules), as done [here](https://github.com/JJ/alpine-raku/blob/master/github-actions.Dockerfile):

```Dockerfile
FROM ghcr.io/jj/raku-gha

ENV PKGS="git tar" PKGS_TMP="make gcc linux-headers musl-dev" WORKDIR="/home/raku"
LABEL version="1.0.3" maintainer="JJMerelo@GMail.com" 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}" \
    ENV="${WORKDIR}/.profile"\
    RAKULIB="inst#/home/raku/.raku"

# Basic setup, programs and init
WORKDIR $WORKDIR
RUN git clone --depth 1 https://github.com/ugexe/zef.git \
    && cd zef && raku -I. bin/zef install . \
    && zef install Linenoise \
    && cd .. && rm -rf zef

USER root
RUN apk del $PGKS_TMP
USER raku

ENTRYPOINT ["raku"]
```

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](https://busybox.net/), and that includes `tar`. However, in an apparently undocumented move, [the caching GHA](https://github.com/actions/cache) uses BSD or [GNU tar](https://www.gnu.org/software/tar/manual/tar.html) for storing and restoring artifacts. That means it's needed in your container. Check above:

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

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](https://github.com/JJ/raku-dist-template/blob/master/.github/workflows/test.yaml). Once the container is GHA-ready, you can run (at least these) [Github actions](https://github.com/actions) in your workflow as easily as if you were running the default base runner.