GitHub Actions runner and images based on Podman.
The Bitdeps Actions Runner uses a statically built version of Podman, which differs from the official Microsoft runner and offers unique features. Unlike the official runner, this version provides:
- Rootless execution on Kubernetes with minimal capabilities (only SETUID and SETGID are used).
- No need for Buildx — efficient multi-architecture builds are supported out of the box.
- Container-only job execution — this runner is designed exclusively for container jobs.
- Pre-configured tools:
- A Docker compatibility wrapper for remote Podman execution inside any job container.
- GitHub CLI tool.
- Pre-configured Google Artifacts Registry credential helper.
Rootless execution in cloud environments like GKE has some limitations. For instance, port mapping and DNS for service containers do not work as expected. Podman relies on the CNI plugin, and Netavark does not function well in cloud environments. Despite this, the overall user experience remains smooth.
Since this runner does not use Buildx or Docker, Podman-native actions are used instead. Here are some examples:
jobs:
build:
runs-on: myrunner
permissions:
contents: read
packages: write
## Container jobs are only allowed!
container:
image: images/alpine:latest
steps:
- name: Build Image
id: build-image
uses: redhat-actions/buildah-build@v2
with:
image: myorg/repo
context: .
tags: latest
build-args: ""
containerfiles: Containerfile
- name: Push to Artifact Registry
uses: redhat-actions/push-to-registry@v2
with:
image: myorg/repo
tags: latest
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
For more information:
The standard method for deploying self-hosted runners in Kubernetes is using actions-runner-controller. For detailed setup, refer to the official documentation.
Further configuration details are provided below.
With the Fuse device plugin, pods no longer need to run in privileged mode to access /dev/fuse
. The DaemonSet below injects the fuse device into pods running on matched nodes. Learn more.
apiVersion: apps/v1
kind: DaemonSet
spec:
template:
spec:
hostNetwork: true
nodeSelector:
myorg/gh-runner: "true"
tolerations:
- key: myorg/gh-runner
operator: Exists
effect: NoSchedule
containers:
- image: soolaugust/fuse-device-plugin:v1.0
name: fuse-device-plugin-ctr
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
Refer to the values.yaml for configuration reference. The configuration below highlights important settings, such as the Fuse device plugin, virtual network interface, and AppArmor security profile.
template:
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/runner: unconfined
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
spec:
serviceAccountName: scale-set-wi
nodeSelector:
myorg/gh-runner: "true"
tolerations:
- key: myorg/gh-runner
operator: Exists
effect: NoSchedule
containers:
- name: runner
command:
- entrypoint.sh
- /home/runner/run.sh
imagePullPolicy: Always
image: ghcr.io/bitdeps/actions-runner:5.3.1
securityContext:
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
capabilities:
allowPrivilegeEscalation: false
drop: ["ALL"]
add:
- SETUID
- SETGID
resources:
requests:
cpu: 500m
memory: 3350Mi
limits:
github.com/fuse: 1
cpu: 2
memory: 3350Mi
volumeMounts:
- mountPath: /home/runner/.local/share/containers
name: podman-local
# Virtual network interfaces mount
- mountPath: /dev/net/tun
name: dev-tun
# Additional registry shortnames for Podman
- name: shortnames
mountPath: "/etc/containers/registries.conf.d/001-shortnames.conf"
subPath: 001-shortnames.conf
readOnly: true
volumes:
- name: podman-local
emptyDir: {}
- name: dev-tun
hostPath:
path: /dev/net/tun
type: CharDevice
- name: shortnames
configMap:
name: scale-set-configs-shortnames