GitHub Actions runner for Laravel & PHP / early access

Laravel CI. 90% shorter.

A hosted runner rebuilt from the metal up for PHP test suites. Strong cores, databases tuned for tests, and coverage collectors we wrote ourselves. Billed per run, because we'd rather be fast than paid by the minute.

SUITE C · REPLAY ×12 running passed skipped
otterwise-raft
00:00.0
ubuntu-latest
00:00.0
0 / 3,392 passed

Same tests. Same commit. Raft finishes first.

Production Laravel suites, same commit on both sides. The top rows are whole jobs on our metal hosts, from checkout to coverage upload. The bottom rows are test runtime only. Both bars move at the same speed; the hatched part is time you get back.

Whole jobs on Raft metal · checkout to coverage upload
Fresh microVM per job, ready in
250 ms
MySQL 8 + Postgres 16 services healthy
5.3 s
docker pull, image already on disk
1.2 s
Test runtime only · measured before our latest round of work
All four suites, back to back
32:50 → 8:53
Peak memory, Suite A
2.71 GB → 184 MB
Memory fatals, Suite B
1 → 0
Unedited output from both runners
── Suite A ─────────────────────────────────────────────
ubuntu-latest   Time: 18:42.959, Memory: 2.71 GB
otterwise-raft  Time: 01:47.215, Memory: 184.50 MB

── Suite B ─────────────────────────────────────────────
ubuntu-latest   Tests: 1 skipped, 2282 passed (8756 assertions)  Duration: 400.66s
                PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted (tried to allocate 107758448 bytes)
otterwise-raft  Tests: 1 skipped, 2282 passed (8756 assertions)  Duration: 279.61s

── Suite C ─────────────────────────────────────────────
ubuntu-latest   Tests: 1 deprecated, 1 incomplete, 7 skipped, 3392 passed (32076 assertions)  Duration: 287.25s
otterwise-raft  Tests: 1 deprecated, 1 incomplete, 7 skipped, 3392 passed (32083 assertions)  Duration: 87.50s

── Suite D ─────────────────────────────────────────────
ubuntu-latest   159s
otterwise-raft  59s

We didn't rent a bigger VM. We rebuilt the runner.

Five layers, each one tuned for how PHP test suites actually spend their time: single-threaded, database-heavy, booting the framework thousands of times, and collecting coverage on top.

Metal bare metal · microVM per job

Strong cores. Nobody else on the box. A PHP test process runs on one thread, so clock speed beats core count. Raft runs on dedicated Zen 5 metal. Each job gets its own microVM, restored from a clean snapshot in about 250 ms. No noisy neighbours, no surprise throttling.

Database migrate · refresh · rollback

Databases that forget on purpose. A test database doesn't need to survive a power cut. Engines tuned for migrations, factories and RefreshDatabase, warm before your first step.

Coverage faster than PCOV, plus branches

Faster than PCOV, and sees more. Native PCOV can double a suite's runtime and only tracks lines. Our in-house collector costs usually less than 5%, and adds branch coverage on top, so you see every if your tests only ever took one way.

Type coverage ~1 min → 3s

Pest type coverage in three seconds. Our own collector takes a medium repo from about a minute to about three seconds. Cheap enough to run on every push.

Booster Laravel + Symfony

Boot the framework less. Suites that bootstrap Laravel or Symfony test after test pay for it thousands of times. Our PHPUnit booster removes the repeated work. Your tests don't change.

Coverage on every push. Not just nightly.

Most teams move --coverage to a nightly job because it's slow. On Raft it costs a few percent, adds branch coverage PCOV can't collect, and lands in OtterWise next to your type coverage on every pull request.

runtime
1.00×

Baseline: the suite without coverage.

Your agent writes the fix in a minute. Then waits 18 for CI.

Coding agents work in laps: edit, push, wait for the checks, read what failed, push again. On a slow runner the waiting is most of every lap, and it's the one part a better model can't fix.

Time to a green pull request · one agent, suite A 0:00 / 1:20:00
agent workingwaiting on CIfailed runpassed run

Suite A, our slowest suite, test runtime only. Assumes a minute of agent work before each push, and four pushes to get a pull request green.

Runs per hour one agent · Suite A

21 tries an hour, not 3. An agent rarely lands it on the first push. At 1:47 a run, it gets seven times as many attempts before you check back.

Parallel agents one microVM per job

Four agents, four clean machines. Every job boots its own microVM from a clean snapshot in about 250 ms. Nothing one agent leaves behind can fail another agent's run.

Readable results PR comment · annotations

Proof it tested what it wrote. OtterWise comments coverage on the pull request and flags uncovered lines in the diff. Your agent reads them like any other review.

Per-minute billing pays runners to be slow.

Raft charges a flat price per run. 1:47 costs exactly what 18:42 would. The only way that works for us is making your suite faster, so that's the whole job.

runs-onsizeper run
otterwise-raft2 vCPU · 4 GB$0.10
otterwise-raft-medium4 vCPU · 8 GB$0.20

Up to 30 minutes per job. Need longer? Tell us, though after the move you probably won't.

18:42
ubuntu-latest19 billed min
$0.114 19 × $0.006
otterwise-raft
$0.10
per run
Flat, whatever the duration.

GitHub list prices for private repositories, per minute with each job rounded up. Before any minutes included in your GitHub plan. marks the first minute where Raft is cheaper.

One line. Nothing else moves.

Your workflow, secrets, caches and matrix stay exactly where they are. Change the runner label and push.

  • Pest and PHPUnit, Laravel and Symfony
  • Database engines tuned for Laravel's test patterns
  • Line, branch and type coverage land in OtterWise, no extra step
  • Each job runs in its own microVM. Your source never leaves the runner
.github/workflows/tests.yml+1 −1
1jobs:
2 tests:
3- runs-on: ubuntu-latest
3+ runs-on: otterwise-raft
4 steps:
5 - uses: actions/checkout@v4
6 - run: composer install
7 - run: vendor/bin/pest --coverage --type-coverage
All checks have passed2 successful checks
tests / pest otterwise-raft 18m 42s 1m 47s
OtterWise / coverage line · branch · type reported

Send us your slowest suite.

Early access for Laravel and Symfony teams. We run your workflow on Raft and send back the numbers.

raft /ræft/ noun — a group of otters floating together, holding paws so nobody drifts off.