BuildKit is the default in 19.03, and it is faster for a reason

The old builder executes every instruction in order on one goroutine. BuildKit builds a graph, runs independent branches concurrently, and skips stages nothing depends on.

$ DOCKER_BUILDKIT=1 docker build -t app .
[+] Building 18.4s (14/14) FINISHED
 => [internal] load build definition                          0.0s
 => [builder 3/5] RUN composer install --no-dev              11.2s
 => [assets 3/4] RUN npm ci                                   9.8s   ← concurrent
 => [stage-2 4/6] COPY --from=assets /app/public/build ...     0.3s

# same Dockerfile, old builder: 31.6s — the two stages ran in series

The concurrency is free on any multi-stage build whose stages do not depend on each other, which is most of them — a PHP stage and an asset stage typically share only the final COPY. Setting the environment variable per invocation is the 18.09 way; in 19.03 it can go in /etc/docker/daemon.json as {"features": {"buildkit": true}} so nobody has to remember. The output format is different enough that a CI log parser may need adjusting, which is the only migration cost.