Writing jobs¶
A FireRunner job behaves like a job on any Linux runner, except that it always starts on a new machine that is deleted afterwards.
Pick the runner¶
default:
tags: [firecracker]
With or without image:¶
test:
image: node:22 # the script runs in this container, inside the job's VM
script:
- npm ci && npm test
build:
script: # no image: the script runs on the VM as root
- docker build -t app:$CI_COMMIT_SHORT_SHA .
Without image: you get Ubuntu 24.04 with Docker (buildx, compose), git, curl, jq, make and python3.
Docker builds¶
Every VM has its own Docker daemon. No docker:dind, no privileged mode.
In jobs without image:, docker build runs on your project's builder, a microVM that keeps your
project's layer cache between jobs. You change nothing; the job log says:
Docker layer cache: using this project's builder (warm cache)
The first build of a project starts its builder: 30 to 45 s, plus loading its saved cache (about
100 MB/s). docker build waits for it up to 5 minutes, then builds without the cache. A builder
unused for a while is deleted, but its cache is kept on the host and comes back with the
project's next builder.
Cache¶
cache: works as on any runner. The runner host keeps it per project. Only paths inside the
project can be cached, so point tool caches there:
test:
image: node:22
cache:
key: npm-$CI_COMMIT_REF_SLUG
paths: [.npm/]
script:
- npm ci --cache .npm --prefer-offline
pip --cache-dir .pip, NUGET_PACKAGES: $CI_PROJECT_DIR/.nuget, GOMODCACHE: $CI_PROJECT_DIR/.go.
Services¶
integration:
image: python:3.12
services:
- name: postgres:16
variables: { POSTGRES_PASSWORD: test }
script:
- pg_isready -h postgres
Each service runs as a container in your VM, reachable by its alias (the image name without the tag). FireRunner waits up to 30 s for its ports before your script starts.
Private registries¶
Set the DOCKER_AUTH_CONFIG CI/CD variable. It is written into the VM before your script runs,
for image:, services: and docker pull alike. Internal registries with a company CA or
without TLS are set up on the host: see Corporate networks.
The VM¶
| Size | 2 vCPU, 2 GB RAM, 40 GB disk (the operator may change it) |
| Bigger size | set FIRERUNNER_VM_MEMORY_MB and FIRERUNNER_VM_VCPU in the job's variables: |
| User | root |
| Network | internet and your LAN through NAT; no access to other jobs' VMs |
| Hostname | job-<CI_JOB_ID> |
A job that needs more memory or CPUs, such as a SonarQube scan, asks for it:
sonar:
variables:
FIRERUNNER_VM_MEMORY_MB: "3072"
FIRERUNNER_VM_VCPU: "4"
The job log shows what it asked for and what it got. It gets at most what the operator allows
(vm.job_max_memory_mb, vm.job_max_vcpu); without such a limit it gets the default size. A job
with its own size does not use the pre-booted VMs, so it waits for a boot (about 10 s). A value
that is not a number fails the job.
When a job fails¶
The Preparing section shows where the job ran:
microVM pool-551bf7 ready at 10.200.0.236 in 300ms (pool, 2 vCPU, 2048 MB).
| Log | Meaning |
|---|---|
Job failed: exit status 1 |
your script failed |
Job failed (system failure) |
no VM could be started; retry, then tell the operator |
waiting for host memory |
the host is full; the job continues when a VM frees up |
pull access denied |
set DOCKER_AUTH_CONFIG |
service … did not open port … within 30s |
the service crashed or starts slowly; its log follows |
'build_script' stage will be replaced with 'step_script' |
printed by gitlab-runner for every custom executor; ignore it |