Process::run('gzip -k dump.sql'). Out of the whole Laravel 10 release this week, that is the line I will actually use.

The rest is quiet. Skeleton and framework code got native type declarations instead of docblocks, PHP 8.1 is the floor, and there is the Process facade. That is roughly the whole story. Some people are disappointed. I am not. A major release that is mostly maintenance means the framework is an adult.

use Illuminate\Support\Facades\Process;

$result = Process::run('gzip -k dump.sql');

if ($result->failed()) {
    Log::error($result->errorOutput());
}

Before this it was raw Symfony Process or exec() hidden in some helper. Now it is a facade, which means it is fakeable in tests. Process::fake() and your test does not really call gzip. Shelling out was always the untested corner of every Laravel app I saw. Less excuse now.

The typing change is bigger than it looks. Generated classes come with real parameter and return types. Your IDE stops guessing. But your own classes that extend framework ones may need signature updates. This is the main source of upgrade friction, and it is mechanical work. Rector handles most of it.

Yearly majors used to scare people. In practice 8 to 9 took me an afternoon, and 9 to 10 looks the same. The trick is never being more than one major behind. Upgrade cost is not linear in versions skipped. It is worse.

Read the upgrade guide, it is two screens. Run the tests. Ship it while it is boring.

The untested shell-out corner in my own project is a backup script. It has called exec() for years. It still does.