Trim Laravel Cloud Build Time

I've been using Laravel Cloud a bunch lately. Build times take about 18 seconds on the app that I'm working on. My entire build script used to be:

1composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
2npm ci --audit false
3npm run build

Simple and clean.

The problem is, it's also doing a lot of waiting around. We have to wait 5–10 seconds for composer to finish installing before we can kick of npm. That drives me nuts. So I wrote a little shell script to fix that. It's called parallel.sh and works like this:

1chain "install dependencies" \
2 "composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader"
3 
4chain "build assets" \
5 "npm ci --audit false" \
6 "npm run build"
7 
8run

You start by defining any "chains" of commands that need to happen in sequence (in this case, we need to run npm ci before we can build our assets). Once your chains are defined, you call run and everything runs in parallel.

Here are some benchmarks:

Scenario Before After Speedup
Typical Composer + NPM build 11.01s 7.09s 1.6x
Four CI checks 12.01s 5.03s 2.4x
A failing step late in the build 6.01s 1.03s 5.8x
One long process + several quick ones 10.01s 6.04s 1.7x

In my real-world testing, this has brought my Laravel Cloud build down from ~18s to ~11s.

Check the parallel-build repo out. It's a single script you can just copy-and-paste into your project.

The earlier implementation

Before I abstracted this away into a shell function, I had a much less elegant solution. Below you'll find the earlier version of this page:

The ugly solution

Well, if you want something less simple and clean, but a little bit faster, you can speed up your builds by parallelizing (almost) everything. Here's my current script. It:

  • Runs everying it can in parallel
  • Fails as fast as possible
  • Still gets you the command output in the end

Maybe it's not worth it, but while I'm iterating fast, the 11s build feels a lot better than the 18s one!

1composer_log=$(mktemp)
2npm_log=$(mktemp)
3build_log=$(mktemp)
4 
5cleanup() {
6 rm -f "$composer_log" "$npm_log" "$build_log"
7}
8trap cleanup EXIT
9 
10# Start composer install in bg
11composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader > "$composer_log" 2>&1 &
12composer_pid=$!
13 
14# Start npm install in fg because "run build" depends on it
15echo "--- npm install ---"
16npm ci --audit false
17npm_status=$?
18 
19if [ $npm_status -ne 0 ]; then
20 echo "[!] npm ci failed"
21 kill $composer_pid 2>/dev/null
22 wait $composer_pid 2>/dev/null
23 echo "--- composer install (maybe partial) ---"
24 cat "$composer_log"
25 exit 1
26fi
27 
28npm run build > "$build_log" 2>&1 &
29build_pid=$!
30 
31wait $composer_pid
32composer_status=$?
33 
34echo "--- composer install ---"
35cat "$composer_log"
36 
37if [ $composer_status -ne 0 ]; then
38 echo "[!] composer failed"
39 kill $build_pid 2>/dev/null
40 wait $build_pid 2>/dev/null
41 echo "--- npm build (maybe partial) ---"
42 cat "$build_log"
43 exit 1
44fi
45 
46wait $build_pid
47build_status=$?
48 
49echo "--- npm build ---"
50cat "$build_log"
51 
52if [ $build_status -ne 0 ]; then
53 echo "[!] npm build failed"
54 exit 1
55fi

Enjoy!