Building a Docker-Image GitHub Action That Writes Values to outputs

Watch out for permissions


Posted on Tue, Sep 24, 2024
Tags githubactions, docker
githubactions, docker
📝 This article is a translation of the original Japanese post. View original

Writing a Custom Action for GitHub Actions

First, as background, defining a custom action for GitHub Actions is not that hard. If you can define:

  1. Metadata such as the action’s name, description, and branding
  2. The input values in inputs
  3. The output values in outputs
  4. The commands that actually run in runs

then you can build one easily.

Among these, the specification for outputs is somewhat involved, but as described in Metadata syntax for GitHub Actions - GitHub Docs, if you write a string in the prescribed format to the file named by the GITHUB_OUTPUT environment variable, you can hand the contents of outputs to the next step.

1
2
3
4
5
6
7
8
# image details and such are omitted below
runs:
  steps:
    - id: rand-gen
      run: echo "random-id=$(echo $RANDOM)" >> $GITHUB_OUTPUT
      shell: bash
    # ${{ steps.<step id>.outputs.<output id> }} gives access to the value emitted by the previous step
    - run: echo "${{ steps.rand-gen.outputs.random-id }}"

For a simple example the short snippet above is all it takes, but when you want a custom action to write to outputs, the custom action has to append to the file path in $GITHUB_OUTPUT in the format output_id=....

As an aside, when the contents of an output span multiple lines, you can emit them using a here-document style like the following.

1
2
3
output_id<<ANY_UNIQUE_PLACE_HOLDER
...
ANY_UNIQUE_PLACE_HOLDER

Here, $GITHUB_OUTPUT holds a value like /github/file_commands/set_output_<guid>.

Writing to the $GITHUB_OUTPUT File from a Custom Action Fails with permission denied

While building a custom action, I found that writing to /github/file_commands/set_output_<guid> failed with permission denied.

1
2
3
4
time=2024-09-24T08:36:07.762Z level=DEBUG msg="Checking permissions"
time=2024-09-24T08:36:07.762Z level=DEBUG msg="Current process user name and id" user=nonroot uid=65532
time=2024-09-24T08:36:07.762Z level=DEBUG msg="file info" fi="&{Dev:2065 Ino:550836 Nlink:1 Mode:33188 Uid:1001 Gid:127 X__pad0:0 Rdev:0 Size:0 Blksize:4096 Blocks:0 Atim:{Sec:1727166955 Nsec:581692099} Mtim:{Sec:1727166955 Nsec:581692099} Ctim:{Sec:1727166955 Nsec:581692099} X__unused:[0 0 0]}"
Error: print result: open file of GITHUB_OUTPUT: open /github/file_commands/set_output_81a41ae4-699c-4a1e-ba50-eb26527a4d69: permission denied

To give the conclusion first: when you define and run a custom action via a Dockerfile, access to the host’s directory (the host is usually something like ubuntu-latest) may not be permitted depending on the uid inside Docker.

The custom action I had built used ko to create the docker image, which is based on chainguard, a rootless container.

The uid of a rootless container is set to something like 65534 (nobody). But as the log above shows, the file /github/file_commands/set_output_81a41ae4-699c-4a1e-ba50-eb26527a4d69 is owned by uid 1001. This uid 1001 appears to be the user defined on the host for GitHub Actions.

So the solutions are as follows.

  1. Use a Dockerfile that runs as root. The Dockerfile that fixed it ended up dropping ko and using wolfi-base, a base image that has a root user. Of course basing it on an image such as Ubuntu or Rocky is fine too
  2. Define the GitHub Actions user (uid 1001) with useradd inside the Dockerfile
    • Note: I haven’t actually tried this, so whether it really resolves the permission denied is a guess

There isn’t much information about this behavior on the internet and it took some effort to resolve, so I wrote it up.

https://github.com/go-zen-chu/aictl/blob/9c0d780ebb4c3594a8e0aed1f5248f042fd25a57/cmd/aictl/cmd/query.go#L103-L123 has a working example of implementing outputs for a custom action written in Go, so have a look if it helps.

Share


See also