While I was investigating Kyverno, I wanted to check my Kubernetes deployments for compliance with Kyverno policies. The Kyverno CLI can be used to do that with the following command:
kyverno apply ./policies --resource=./deploy/deployment.yaml
To do this easily from a GitHub workflow, I created an action called gbaeke/kyverno-cli. The action uses a Docker container. It can be used in a workflow as follows:
# run kyverno cli and use v1 instead of v1.0.0
- name: Validate policies
uses: gbaeke/kyverno-action@v1
with:
command: |
kyverno apply ./policies --resource=./deploy/deployment.yaml
You can find the full workflow here. In the next section, we will take a look at how you build such an action.
If you want a video instead, here it is:
GitHub Actions
A GitHub Action is used inside a GitHub workflow. An action can be built with Javascript or with Docker. To use an action in a workflow, you use uses: followed by a reference to the action, which is just a GitHub repository. In the above action, we used uses: gbaeke/kyverno-action@v1. The repository is gbaeke/kyverno-action and the version is v1. The version can refer to a release but also a branch. In this case v1 refers to a branch. In a later section, we will take a look at versioning with releases and branches.
Create a repository
An action consists of several files that live in a git repository. Go ahead and create such a repository on GitHub. I presume you know how to do that. We will add several files to it:
- Dockerfile and all the files that are needed to build the Docker image
- action.yml: to set the name of our action, its description, inputs and outputs and how it should run
Docker image
Remember that we want a Docker image that can run the Kyverno CLI. That means we have to include the CLI in the image that we build. In this case, we will build the CLI with Go as instructed on https://kyverno.io. Here is the Dockerfile (should be in the root of your git repo):
FROM golang:1.15
COPY src/ /
RUN git clone https://github.com/kyverno/kyverno.git
WORKDIR kyverno
RUN make cli
RUN mv ./cmd/cli/kubectl-kyverno/kyverno /usr/bin/kyverno
ENTRYPOINT ["/entrypoint.sh"]
We start from a golang image because we need the go tools to build the executable. The result of the build is the kyverno executable in /usr/bin. The Docker image uses a shell script as its entrypoint, entrypoint.sh. We copy that shell script from the src folder in our repository.
So go ahead and create the src folder and add a file called entrypoint.sh. Here is the script:
#!/usr/bin/env bash
set -e
set -o pipefail
echo ">>> Running command"
echo ""
bash -c "set -e; set -o pipefail; $1"
This is just a bash script. We use the set commands in the main script to ensure that, when an error occurs, the script exits with the exit code from the command or pipeline that failed. Because we want to run a command like kyverno apply, we need a way to execute that. That’s why we run bash again at the end with the same options and use $1 to represent the argument we will pass to our container. Our GitHub Action will need a way to require an input and pass that input as the argument to the Docker container.
Note: make sure the script is executable; use chmod +x entrypoint.sh
The action.yml
Action.yml defines our action and should be in the root of the git repo. Here is the action.yml for our Docker action:
name: 'kyverno-action'
description: 'Runs kyverno cli'
branding:
icon: 'command'
color: 'red'
inputs:
command:
description: 'kyverno command to run'
required: true
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.command }}
Above, we give the action a name and description. We also set an icon and color. The icon and color is used on the GitHub Marketplace:

As stated earlier, we need to pass arguments to the container when it starts. To achieve that, we define a required input to the action. The input is called command but you can use any name.
In the run: section, we specify that this action uses Docker. When you use image: Dockerfile, the workflow will build the Docker image for you with a random name and then run it for you. When it runs the container, it passes the command input as an argument with args: Multiple arguments can be passed, but we only pass one.
Note: the use of a Dockerfile makes running the action quite slow because the image needs to be built every time the action runs. In a moment, we will see how to fix that.
Verify that the image works
On your machine that has Docker installed, build and run the container to verify that you can run the CLI. Run the commands below from the folder containing the Dockerfile:
docker build -t DOCKER_HUB_USER/kyverno-action:v1.0.0 . docker run DOCKER_HUB_USER/kyverno-action:v1.0.0 "kyverno version"
Above, I presume you have an account on Docker Hub so that you can later push the image to it. Substitute DOCKER_HUB_USER with your Docker Hub username. You can of course use any registry you want.
The result of docker run should be similar to the result below:
>>> Running command Version: v1.3.5-rc2-1-g3ab75095 Time: 2021-04-04_01:16:49AM Git commit ID: main/3ab75095b70496bde674a71df08423beb7ba5fff
Note: if you want to build a specific version of the Kyverno CLI, you will need to modify the Dockerfile; the instructions I used build the latest version and includes release candidates
If docker run was successful, push the image to Docker Hub (or your registry):
docker push DOCKER_HUB_USER/kyverno-action:v1.0.0
Note: later, it will become clear why we push this container to a public registry
Publish to the marketplace
You are now ready to publish your action to the marketplace. One thing to be sure of is that the name of your action should be unique. Above, we used kyverno-action. When you run through the publishing steps, GitHub will check if the name is unique.
To see how to publish the action, check the following video:
Note that publishing to the marketplace is optional. Our action can still be used without it being published. Publishing just makes our action easier to discover.
Using the action
At this point, you can already use the action when you specify the exact release version. In the video, we created a release called v1.0.0 and optionally published it. The snippet below illustrates its use:
- name: Validate policies
uses: gbaeke/kyverno-action@v1.0.0
with:
command: |
kyverno apply ./policies --resource=./deploy/deployment.yaml
Running this action results in a docker build, followed by a docker run in the workflow:

The build step takes quite some time, which is somewhat annoying. Let’s fix that! In addition, we will let users use v1 instead of having to specify v1.0.0 or v1.0.1 etc…
Creating a v1 branch
By creating a branch called v1 and modifying action.yml to use a Docker image from a registry, we can make the action quicker and easier to use. Just create a branch in GitHub and call it v1. We’ll use the UI:

Make the v1 branch active and modify action.yml:

In action.yml, instead of image: ‘Dockerfile’, use the following:
image: 'docker://DOCKER_HUB_USER/kyverno-action:v1.0.0'
When you use the above statement, the image will be pulled instead of built from scratch. You can now use the action with @v1 at the end:
# run kyverno cli and use v1 instead of v1.0.0
- name: Validate policies
uses: gbaeke/kyverno-action@v1
with:
command: |
kyverno apply ./policies --resource=./deploy/deployment.yaml
In the worflow logs, you will see:

Conclusion
We can conclude that building GitHub Actions with Docker is quick and fun. You can build your action any way you want, using the tools you like. Want to create a tool with Go, or Python or just Bash… just do it! If you do want to build a GitHub Action with JavaScript, then be sure to check out this article on devblogs.microsoft.com.