Skip to content

Configuration

Settings live in /etc/firerunner/config.yaml. Change them with the CLI; values are checked and most take effect for the next job, without a restart:

sudo firerunner config show
sudo firerunner config set vm.memory_mb 3072

Job VMs and pool

Key Default
vm.vcpu host CPUs / runner concurrent (2 to 16), set by the installer per job VM
vm.memory_mb 2048 per job VM
vm.job_max_vcpu, vm.job_max_memory_mb 0 the largest VM a job may ask for (see below); 0 = vm.vcpu and vm.memory_mb, so jobs cannot ask for more
pool.size runner concurrent, set by the installer pre-booted VMs; a job that finds one does not wait for a boot (about 10 s)
pool.max_idle 30m idle pool VMs are replaced after this
pool.preload_images none images pulled into pool VMs in advance, e.g. a large SDK
pool.preload_top 0 (off) also pull the N images the recent jobs used most (at least 2 of the last 200 jobs), see below
vm.host_reserve_mb 1024 host memory never given to VMs

Set pool.size to the number of jobs that usually start at once: a job that finds the pool empty waits for a boot. Each pool VM holds vm.memory_mb of host memory while it waits.

A shell executor's job gets every CPU of the host. vCPUs are threads on the host and an idle one costs nothing, so the installer gives each VM the host's CPUs divided by the jobs that run at once.

A job whose image: is already in its pool VM skips the pull (10 to 20 s for a large image). pool.preload_top picks those images from what the recent jobs ran in, so they need no list:

sudo firerunner config set pool.preload_top 3

Pool VMs pull without the jobs' credentials, so an image that needs a login is never preloaded (it is skipped for a day after its first failed pull), and a job gets no image it could not pull itself. A job can see the names of the public images other projects use, though: leave it off where that matters. Preloading uses the pool VM's disk and makes its boot longer; a job that arrives meanwhile takes the VM and the preload stops.

Keep vm.vcpu and vm.memory_mb at what most jobs need and let the few heavy jobs (static analysis, large test suites) ask for more with job variables, up to vm.job_max_vcpu and vm.job_max_memory_mb:

sudo firerunner config set vm.job_max_memory_mb 4096
sudo firerunner config set vm.job_max_vcpu 4

Such a job boots its own VM (pool VMs have the default size), so it waits for memory like any cold boot and counts its own size.

Builders (Docker layer cache)

Key Default
builder.enabled true one builder VM per project that runs docker build
builder.vcpu, builder.memory_mb 4, 8192 Node.js frontend builds need 4 GB or more
builder.max 4 the least recently used builder idle for 5 minutes makes room for a new one; while none is, a build goes without a cache
builder.idle_ttl 24h an unused builder is deleted; its cache is saved
builder.saved_cache_gb 100 host disk for the caches of deleted builders; 0 saves none

When a builder is deleted because it was idle, its slot was needed, it was a week old, or its settings changed (not its size), its cache is copied to /var/lib/firerunner/builder-cache/<project>.tar and loaded into the project's next builder. So every project keeps a warm cache while only builder.max builders use memory. When the saved caches pass builder.saved_cache_gb, or the disk has less than 10% free, the least recently used are deleted. firerunner builder rm deletes a project's saved cache too.

A saved cache names the builder image it came from and is only loaded into a builder of the same image. A builder deleted because builder.image changed is not copied out, and a cache of another image is dropped when the next builder starts. Caches saved by versions that did not record the image were all written by moby/buildkit:v0.33.0: they are still loaded while builder.image is that image, and dropped otherwise.

A new builder size applies to builders started later; existing caches are kept.

Sizing a host

A VM starts only when its memory fits. Jobs that do not fit wait, so the host never runs out of memory.

((jobs × vm.memory_mb) + (pool.size × vm.memory_mb) + (builder.max × builder.memory_mb)) × 1.05 ≤ RAM − 1 GB

1.05 adds Firecracker's own memory, 5% per VM; 1 GB is vm.host_reserve_mb.

For example, 64 GB fits 10 jobs and 4 pool VMs at 1.5 GB plus 4 builders at 8 GB. Set the parallel jobs with sudo firerunner runner concurrent 10.

CPU is shared, not reserved: give VMs more vCPUs than the host has cores.

Other keys

firerunner config keys lists all of them. Worth knowing:

Key Default
daemon.metrics_listen 127.0.0.1:9477 Prometheus endpoint
daemon.job_max_age 3h VMs of jobs older than this are deleted
vm.docker_bip, vm.docker_address_pool 10.201.0.1/24, 10.202.0.0/16 Docker networks in the VM; change them if they clash with your LAN
vm.registry_mirror http://<bridge>:5000 Docker Hub's pull-through mirror on the host
vm.registry_mirrors one per registry in FR_REGISTRY_MIRRORS pull-through mirrors of other registries, e.g. {"ghcr.io": "http://10.200.0.1:5001"}; the installer writes it on every run

Registry mirrors

Every job starts with an empty image store, so each image:, services: and docker pull downloads its image again. The host keeps a pull-through mirror of Docker Hub and, by default, of ghcr.io, quay.io, registry.k8s.io and mcr.microsoft.com (FR_REGISTRY_MIRRORS at install time), so a layer crosses the internet once per host instead of once per job. Job VMs and builders use them without any change to .gitlab-ci.yml.

A pull the mirror cannot serve goes to the registry itself: private images (the mirror has no credentials, so it never holds another project's private image), a registry the mirror cannot reach, or a mirror that is down. Other registries' mirrors need the guest's Docker to use the containerd image store, the default of Docker 29 in the FireRunner image.

Corporate networks

Set these with the installer options described in Corporate networks; the keys are:

Key Default
proxy.enabled false microVMs and host services use the corporate proxy through the forwarder
proxy.listen 10.200.0.1:3128 the forwarder on the bridge address (and 127.0.0.1)
proxy.upstream_file /etc/firerunner/proxy-upstream root-only file with http://[user:password@]host:port
proxy.no_proxy none hosts, .domains and CIDRs reached directly; local addresses are always added
proxy.connect_ports [443] ports HTTPS tunnels through the proxy may go to
network.egress_deny none networks jobs must not reach, directly or through the proxy (installer: FR_EGRESS_DENY)
vm.ca_file none PEM root CA trusted by microVMs, their Docker, builders and job containers
vm.insecure_registries none host:port (TLS without a check) or http://host:port (plain HTTP)