Migrate CI from GitHub Actions to Hugging Face Jobs | Keryc
Doing CI with GPUs and reliable runners without maintaining servers sounds expensive and complex, right? Well, it doesn't have to be. In this technical guide I show you step by step how to connect your GitHub Actions workflows so they run on Hugging Face Jobs, keeping GitHub as the orchestrator but using HF infrastructure to run the actual tasks.
Why you'd want this
GitHub Actions gives you a machine by default with runs-on: ubuntu-latest. Convenient, yes; perfect for everything, not really. Common limits:
Latency or interruptions during GitHub maintenance.
Generic images without the specific tooling you need.
Almost no GPU access for many open source projects.
Can you imagine running the same test suite on a real GPU without maintaining your own runner? Trackio did it: they cut CPU job times by about 30% and added a GPU test suite that was previously impossible.
Basic architecture and core idea
The idea is simple and elegant: keep using GitHub Actions to define flows, but execute the steps in Hugging Face Jobs. To do that you build a bridge: a dispatcher that receives the webhook from the GitHub App and starts an HF Job that acts as an ephemeral runner.
workflow_job.queued
Key components:
A Space called jobs-actions-dispatcher that receives webhooks and launches Jobs.
A GitHub App that listens for queued jobs and creates temporary tokens to register runners.
HF Jobs that launch containers with the required hardware (CPU or GPU).
From GitHub's point of view it looks like a self-hosted runner. From Hugging Face it's a Job that runs the workflow steps.
Step-by-step setup
Duplicate the dispatcher Space
Go to huggingface/jobs-actions-dispatcher and duplicate the Space.
Owner: your HF user or organization.
Name: jobs-actions-dispatcher.
Hardware: use cpu-upgrade for production; cpu-basic works for tests but may go to sleep.
The change is minimal. Replace runs-on: ubuntu-latest with a label managed by the dispatcher, for example:
CPU: runs-on: hf-jobs-cpu-upgrade
GPU: runs-on: hf-jobs-t4-small
Minimal workflow example:
name: HF Jobs Test
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: hf-jobs-cpu-upgrade
steps:
- uses: actions/checkout@v4
- run: echo 'Hello from Hugging Face Jobs'
Then commit and push. The dispatcher will launch an HF Job that registers an ephemeral runner and executes the workflow.
Verification and useful commands
From the CLI you can check status with:
gh run list --repo YOUR-GITHUB-ORG/YOUR-REPO --limit 5
hf jobs ps --namespace "$HF_NAMESPACE"
hf spaces logs "$SPACE_ID"
Logs appear like in a normal Action, and you can also fetch them directly with hf jobs logs <job_id> > logs.txt to analyze locally.
Docker images and performance: practical lessons
Don't use an empty ubuntu:22.04 if you need tooling: you'll end up installing Playwright, Node, ffmpeg, sqlite, git and dependencies on every run.
Trackio switched to the Playwright image and a CUDA image for GPU:
CPU image: mcr.microsoft.com/playwright:v1.60.0-jammy
GPU image: nvidia/cuda:12.4.0-runtime-ubuntu22.04
Results reported by Trackio:
Runner setup
Runtime
Compared to GitHub average
GitHub ubuntu-latest baseline
1m40s
baseline
HF Jobs CPU with Playwright image
1m10s
-30s, ~30% faster
HF Jobs GPU, t4-small label
45s
no GPU baseline on GitHub
The key win was being able to run tests on real GPUs: 45s and costing less than a cent at the t4-small rate for that duration.
Best practices and tips
Use a Docker image that already includes the tooling you need to avoid installing dependencies on every run.
If you need datasets or models quickly, HF Jobs allows mounting volumes.
Watch out for the dispatcher on cpu-basic in production: it may sleep and leave workflows queued.
Keep the HF_TOKEN scoped minimally: permissions to launch Jobs only in the necessary namespace.
Small tip: in our bridge we also mirror GitHub logs into the HF Job and vice versa, so you don't lose context on either side.
Practical conclusion
If your project needs finer control over hardware, images, or access to accelerators, using Hugging Face Jobs as the backend for GitHub Actions is a practical, scalable solution. It requires an initial component (the dispatcher Space and the GitHub App), but daily integration is just changing runs-on to a label.
Want time savings, GPU access and easy-to-download logs? This flow can give you all that without the hassle of permanent runners.