Skip to main content

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.

LayerWhat it carriesSize
System layerdebian 13 base + single-version system tools (Node 24 LTS, build essentials, scanners, IaC + cloud CLIs, browsers, db clients, native Docker daemon)~300 MB
hostedtoolcache layerMulti-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.

LanguageVersionNotes
Node.js24.x LTS+ npm, plus pnpm + yarn via corepack
Rustrustup stablerustc + cargo
PowerShell7.xpwsh
PHP8.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).

ActionVersions bakedWhat it sets up
actions/setup-node@v424.x, 22.xNode.js + npm; activate pnpm/yarn via corepack enable
actions/setup-go@v51.26.x, 1.25.xGo toolchain + module cache
actions/setup-python@v53.13, 3.12Python + pip
actions/setup-dotnet@v410.0, 8.0.NET SDK (side-by-side, DOTNET_ROOT set)
actions/setup-java@v4Temurin 21, 17OpenJDK from Adoptium
ruby/setup-ruby@v13.3.x, 3.2.xRuby (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 managerVersionNotes
npmbundled with Node 24
pnpmcorepack-managed (pinned)corepack enable && pnpm --version
yarncorepack-managed (pinned)Yarn Classic + Berry both work
pipxdebianInstalls Python CLI tools in isolated venvs
composerlatestPHP
cargorustup-managedRust
mvn (Maven)3.9.xJVM
gradle9.5.xJVM

IaC

ToolNotes
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.
bicepAzure
pulumiCross-cloud
ansible (ansible-core)Via pipx
sam (AWS SAM CLI)Serverless

Cloud CLIs

CloudCLINotes
AWSaws v2, sam
Google Cloudgcloud, gsutil, bq
Azureaz
T Cloud Public (OTC)tofu + opentelekomcloud providerOr pip install otc-cli per job

Containers + Kubernetes

ToolNotes
docker (CLI + native daemon)Docker runs natively on the VM (no sidecar, no DOCKER_HOST indirection) — see Container builds below
kanikoDaemonless rootless OCI image builder
skopeoImage copy / inspect between registries
kubectl
helm
kustomize
kindLocal 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)
teaForgejo / Gitea CLI

Security + supply-chain

Pre-baked so secret/CVE/SBOM/SAST scans add no setup time.

ScannerCatches
gitleaksSecrets in commits
trivyOS + language CVEs + IaC misconfig
osv-scannerOSV-database vulnerabilities
cosignContainer image signing + verification
syftSBOM generation (CycloneDX / SPDX)
opengrepSAST (Semgrep-compatible fork) — pre-baked with the GitLab MIT sast-rules at /opt/sast-rules
scorecardOSSF supply-chain scorecard
checkovIaC security (Terraform, CFN, k8s)
cfn-lintCloudFormation 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)

ToolWhat’s pre-installedAdd per-job
Android SDKcmdline-tools (latest) + platforms;android-34 + build-tools;34.0.0 + platform-tools at ANDROID_HOME=/usr/local/lib/android/sdkExtra 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/x64CRAN packages via Rscript -e 'install.packages(...)' or r-lib/actions/setup-r-dependencies@v2
Miniconda3conda + python 3.11 at CONDA=/opt/hostedtoolcache/Miniconda3/latest/x64Environments + 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: pytest

What’s still NOT baked in

Genuinely niche or heavy tools that most workflows don’t need. Install per-job:

ToolInstall pathNotes
iOS / XcodemacOS-only — not supported (fremforge runners are Linux)Separate runner-tier roadmap item
Juliajulia-actions/setup-julia@v2~150 MB per-job install
Swiftswift-actions/setup-swift@v2Linux Swift toolchain
Haskell / GHChaskell-actions/setup@v2
Monoapt-get install mono-completeLegacy .NET Framework only — .NET 8/10 are baked
Microsoft Edge browserNot needed — Chromium is the same engine
Lua / Crystal / Nim / ElixirPer-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 unchangeddocker 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:16

kaniko

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_SHA

Privileged 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:

  1. 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-*.
  2. 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).
  3. 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.
  4. Microsoft Edge browser — not baked (Chromium is the same engine for CI testing purposes).
  5. Mono / Julia / Swift / Haskell / Lua / Crystal / Nim / Elixir — not baked (niche, install per-job).
  6. 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.