Hosted runner image
Workflows that target runs-on: fremforge execute on a single-use VM provisioned from the hosted runner image. It’s designed for ubuntu-latest parity: the same actions/setup-* UX you use on GitHub Actions, the same baked toolchains, the same scanners, plus native Docker for container builds.
Two-layer architecture
The runner image is the VM’s boot disk — both layers ship on the same disk and are present from the moment the VM boots, with no per-job download.
| Layer | What it carries | Size |
|---|---|---|
| System layer | debian 13 base + single-version system tools (Node 24 LTS, build essentials, scanners, IaC + cloud CLIs, browsers, db clients, native Docker daemon) | ~300 MB |
| hostedtoolcache layer | Multi-version language runtimes (Node, Go, Python, .NET, Java, Ruby) + Android SDK + Miniconda3 + R | ~7 GB |
setup-* actions resolve from the hostedtoolcache with no per-job download. RUNNER_TOOL_CACHE and AGENT_TOOLSDIRECTORY are pre-pointed at /opt/hostedtoolcache.
The version numbers in the tables below indicate which major/minor lines are baked. Exact resolved versions are bumped continuously by the weekly rebuild and auto-published per rebuild — see the changelog for the current published toolstack.
Languages — system
Single recent version on PATH. Use these directly when your workflow is happy with the system default.
| Language | Version | Notes |
|---|---|---|
| Node.js | 24.x LTS | + npm, plus pnpm + yarn via corepack |
| Rust | rustup stable | rustc + cargo |
| PowerShell | 7.x | pwsh |
| PHP | 8.3 | + Composer |
Languages — multi-version via setup-* actions
Use these actions/setup-* actions exactly as on GitHub-hosted runners. The version you ask for resolves from the hostedtoolcache (no network fetch).
| Action | Versions baked | What it sets up |
|---|---|---|
actions/setup-node@v4 | 24.x, 22.x | Node.js + npm; activate pnpm/yarn via corepack enable |
actions/setup-go@v5 | 1.26.x, 1.25.x | Go toolchain + module cache |
actions/setup-python@v5 | 3.13, 3.12 | Python + pip |
actions/setup-dotnet@v4 | 10.0, 8.0 | .NET SDK (side-by-side, DOTNET_ROOT set) |
actions/setup-java@v4 | Temurin 21, 17 | OpenJDK from Adoptium |
ruby/setup-ruby@v1 | 3.3.x, 3.2.x | Ruby (ruby-builder) |
If you need a version we don’t bake (e.g. Node 20.x for a legacy project), the same setup-* actions can download + install it at job time — but slower (~30-60 s vs ~1 s from hostedtoolcache).
Build essentials
Native node modules, Python wheels with C extensions, Go cgo, and Rust crates with C deps build out of the box.
build-essential (gcc + make) · clang + lld · cmake · ninja · pkg-config · autoconf · automake · libtool · zstd · linux-headers (via base)
Package managers + project tooling
| Package manager | Version | Notes |
|---|---|---|
npm | bundled with Node 24 | |
pnpm | corepack-managed (pinned) | corepack enable && pnpm --version |
yarn | corepack-managed (pinned) | Yarn Classic + Berry both work |
pipx | debian | Installs Python CLI tools in isolated venvs |
composer | latest | PHP |
cargo | rustup-managed | Rust |
mvn (Maven) | 3.9.x | JVM |
gradle | 9.5.x | JVM |
IaC
| Tool | Notes |
|---|---|
tofu (OpenTofu) | MPL-2.0 Terraform drop-in. Real Terraform is BUSL-licensed and not baked; use hashicorp/setup-terraform if you specifically need Terraform. |
bicep | Azure |
pulumi | Cross-cloud |
ansible (ansible-core) | Via pipx |
sam (AWS SAM CLI) | Serverless |
Cloud CLIs
| Cloud | CLI | Notes |
|---|---|---|
| AWS | aws v2, sam | |
| Google Cloud | gcloud, gsutil, bq | |
| Azure | az | |
| T Cloud Public (OTC) | tofu + opentelekomcloud provider | Or pip install otc-cli per job |
Containers + Kubernetes
| Tool | Notes |
|---|---|
docker (CLI + native daemon) | Docker runs natively on the VM (no sidecar, no DOCKER_HOST indirection) — see Container builds below |
kaniko | Daemonless rootless OCI image builder |
skopeo | Image copy / inspect between registries |
kubectl | |
helm | |
kustomize | |
kind | Local k8s clusters — works with the native Docker daemon for most cases; add dind-privileged label if you hit capability-restricted operations (KVM nesting, raw /dev for nested guests) |
tea | Forgejo / Gitea CLI |
Security + supply-chain
Pre-baked so secret/CVE/SBOM/SAST scans add no setup time.
| Scanner | Catches |
|---|---|
gitleaks | Secrets in commits |
trivy | OS + language CVEs + IaC misconfig |
osv-scanner | OSV-database vulnerabilities |
cosign | Container image signing + verification |
syft | SBOM generation (CycloneDX / SPDX) |
opengrep | SAST (Semgrep-compatible fork) — pre-baked with the GitLab MIT sast-rules at /opt/sast-rules |
scorecard | OSSF supply-chain scorecard |
checkov | IaC security (Terraform, CFN, k8s) |
cfn-lint | CloudFormation lint |
Linters
shellcheck · shfmt · yamllint · actionlint (lints the Forgejo Actions workflows you’re writing on frem.sh) · hadolint (Dockerfile)
Browsers + e2e
For headless browser tests:
- Chromium +
chromedriver(debian-packaged, version-matched) - Firefox ESR +
geckodriver - Selenium Server (jar at
/opt/selenium-server.jar)
Microsoft Edge is not baked — Chromium uses the same Blink engine; for CI testing, Chromium is equivalent.
Database clients
postgresql-client · default-mysql-client · sqlite3. The full database servers come via services: blocks (Docker pull from the in-region Docker Hub cache).
Mobile + data-science (baked since 2026-06-06)
| Tool | What’s pre-installed | Add per-job |
|---|---|---|
| Android SDK | cmdline-tools (latest) + platforms;android-34 + build-tools;34.0.0 + platform-tools at ANDROID_HOME=/usr/local/lib/android/sdk | Extra API levels / NDK / emulators via android-actions/setup-android@v3 (reuses what’s pre-installed, no re-download) |
| R (CRAN) | r-base + r-base-dev (debian CRAN repo, currently 4.3.x) at /opt/hostedtoolcache/R/4.3/x64 | CRAN packages via Rscript -e 'install.packages(...)' or r-lib/actions/setup-r-dependencies@v2 |
| Miniconda3 | conda + python 3.11 at CONDA=/opt/hostedtoolcache/Miniconda3/latest/x64 | Environments + scientific stack via conda-incubator/setup-miniconda@v3 with environment-file: |
# Android example
- uses: android-actions/setup-android@v3
- run: ./gradlew assembleDebug
# R example
- uses: r-lib/actions/setup-r@v2
- uses: r-lib/actions/setup-r-dependencies@v2
- run: Rscript -e 'testthat::test_local()'
# Miniconda example
- uses: conda-incubator/setup-miniconda@v3
with:
environment-file: env.yml
- shell: bash -el {0}
run: pytestWhat’s still NOT baked in
Genuinely niche or heavy tools that most workflows don’t need. Install per-job:
| Tool | Install path | Notes |
|---|---|---|
| iOS / Xcode | macOS-only — not supported (fremforge runners are Linux) | Separate runner-tier roadmap item |
| Julia | julia-actions/setup-julia@v2 | ~150 MB per-job install |
| Swift | swift-actions/setup-swift@v2 | Linux Swift toolchain |
| Haskell / GHC | haskell-actions/setup@v2 | |
| Mono | apt-get install mono-complete | Legacy .NET Framework only — .NET 8/10 are baked |
| Microsoft Edge browser | Not needed — Chromium is the same engine | |
| Lua / Crystal / Nim / Elixir | Per-job setup-* action or apt |
Container builds
Native Docker (default)
The VM ships with the Docker daemon installed and running on the host. Your docker CLI talks to it over the standard local socket — no Docker-in-Docker, no sidecar, no DOCKER_HOST indirection. Most GitHub-hosted-runner workflows work unchanged — docker build, docker compose, docker pull, multi-stage builds, BuildKit features.
- run: docker build -t myapp:${{ github.sha }} .
- run: docker compose up -d
- run: docker pull postgres:16kaniko
For pure image builds, kaniko is daemonless (faster cold-start when you don’t need the whole Docker daemon) and has better layer-cache behaviour against remote registries.
- run: |
/usr/local/bin/kaniko \
--context=$GITHUB_WORKSPACE \
--dockerfile=Dockerfile \
--destination=registry.example.com/$GITHUB_REPOSITORY:$GITHUB_SHAPrivileged Docker (KVM nesting)
If your workload needs extra capabilities beyond what the default Docker user gets (e.g. kind with KVM nesting, testcontainers features that need namespace-creation caps, nested-guest /dev access), add the label to runs-on:
jobs:
integration:
runs-on: [fremforge, dind-privileged]
steps:
- run: docker run --privileged kindest/node:v1.31.0 ...fremforge routes these to a VM flavor with nested-virtualisation enabled, with the same single-use-VM-per-job isolation as the default runners.
How we deliver coverage in ~7 GB when GitHub-hosted is ~50 GB
GitHub-hosted ubuntu-latest images are large because they bake:
- 4-6 versions per language (Node 16/18/20/22, Python 3.7-3.12, Go 1.20-1.22, Java 8/11/17/21, Ruby 3.1-3.3) — we bake the 2 most recent LTS-class versions of each (~3 GB total for all 6 language families). Anything older installs per-job via
setup-*. - Android SDK + NDK (~10 GB on GitHub-hosted) — we bake the core SDK (cmdline-tools + platform 34 + build-tools 34.0.0 + platform-tools, ~3 GB). Extra API levels, NDK, emulator system images install per-job via
setup-android(reuses what’s pre-installed). - Pre-pulled Docker service-container images (postgres, mysql, mongo, redis, mssql ~5 GB) — not baked. The in-region Docker Hub mirror makes
docker pull~2 s cold, ~50 ms warm. - Microsoft Edge browser — not baked (Chromium is the same engine for CI testing purposes).
- Mono / Julia / Swift / Haskell / Lua / Crystal / Nim / Elixir — not baked (niche, install per-job).
- Legacy GCC versions, deprecated toolchains — we ship one current set.
What we don’t lose vs GitHub-hosted for typical workflows:
- Every common language (Node, Python, Go, Java, .NET, Ruby, Rust, PHP, PowerShell)
- Mobile (Android SDK) and data-science (Miniconda + R) baked since 2026-06-06
- Every major cloud CLI (AWS, Azure, GCP)
- Every common scanner (Trivy, Gitleaks, OSV, Cosign, Syft, OpenGrep, Scorecard, Checkov)
- Both major browsers + Selenium
- Docker (native, privileged via label)
- All standard GitHub Actions (
actions/checkout,actions/setup-*,actions/upload-artifact, etc.)
If your team needs something we don’t bake, contact support — Renovate-tracked tools roll into the next weekly rebuild.
Image size, cold-start, rebuild cadence
- Weekly rebuild: every Monday 06:00 UTC. Renovate proposes version bumps continuously; the Monday rebuild folds in everything merged that week.
- Trivy gate at build time: HIGH + CRITICAL CVEs hard-fail the build. We never ship an image with known unpatched HIGH/CRITICAL findings.
- sha256-pinned downloads: every build-time binary fetch verifies against a pinned sha256. Renovate bumps the version + the hash in the same PR. The build fails loudly on hash drift, which is the supply-chain safety net.
- VM cold-start: ~25 s (ECS provision + boot from baked image). Warm-pool VMs (pre-provisioned, sitting idle) drop the wait to ~5 s when the pool has capacity.
- Manifest publication: each rebuild publishes the resolved tool versions, image digest, Trivy summary, and CycloneDX SBOM to the changelog.
Sample workflow
A minimal “uses everything” workflow:
name: ci
on:
push:
branches: [main]
workflow_dispatch: {}
jobs:
build-test-scan:
runs-on: fremforge
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24' # resolves from hostedtoolcache, ~1 s
- run: npm ci && npm test
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- run: pip install -r requirements.txt && pytest
- name: SAST + secrets
run: |
gitleaks detect --no-banner --redact
opengrep scan --config=/opt/sast-rules .
- name: SBOM
run: syft scan dir:. -o cyclonedx-json > sbom.json
- name: Container build
run: docker build -t myapp:${{ github.sha }} .Reporting issues with the runner image
If a tool you expected isn’t available, or you hit a VM-runtime issue (resource limits, network, capability), contact support@frem.sh. We respond same-business-day for hosted-runner regressions.