Production image takes 36 minutes to build #65
Labels
No labels
bug
duplicate
enhancement
good first issue
help wanted
question
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lvl0/incr#65
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Measured during the v0.4.0 release:
build.ymlrun #49 took 36m12s, run #51 was still going past 22 minutes. This is the slowest thing in the project by a wide margin — CI itself now runs in about 40 seconds.The build is multi-arch (
linux/amd64,linux/arm64) via QEMU, so everything compiled for arm64 runs emulated at roughly a tenth of native speed.Causes, in order of likely impact
1. PHP extensions are compiled from source
docker/production/Dockerfile:37-43usesdocker-php-ext-installfor six extensions. Each is a C compile, and under arm64 emulation that dominates the build.#57 already solved this for the CI image by using
mlocati/php-extension-installer, which fetches prebuilt binaries:Same approach, same registry, already proven on this runner.
2.
COPY . .invalidates the Composer layer on every buildLine 52 copies the whole source before line 55's
composer install, so any source change busts the cache and dependencies reinstall from scratch every time. The standard fix:Note
--no-scriptson the first install — package discovery needs the full source, so it has to run after the second COPY.3. No registry build cache
build.ymlpasses nocache-from/cache-to, so every release starts cold.build-push-actionsupports registry-backed caching:4.
exifis installed and unusedLine 40. Nothing in the app touches EXIF data — image handling left with the asset subsystem in #48.
Worth deciding: is arm64 actually needed?
Dropping it would remove the emulation entirely and likely take the build under two minutes. It was added in
fe3711e(#39), but if every deployment target is amd64 the arm64 half is pure cost.Do not remove it without checking where this actually runs. If anything deploys to a Raspberry Pi or an ARM VPS, it stays — and then options 1–3 are the answer instead.
Not a cause
mysql-clientlooks removable but is not:docker/production/start-app.sh:13uses it for a database readiness loop before migrating.Acceptance criteria
exifremovedDecision: arm64 stays
Nothing currently deploys to ARM, but the image is published for self-hosters and dropping the platform would lock out anyone on a Raspberry Pi or an ARM VPS. The multi-arch build is a deliberate feature, not incidental cost.
So the fastest single lever — removing the platform — is off the table, and the work is options 1–3:
mlocati/php-extension-installerinstead ofdocker-php-ext-installcache-from/cache-toexifextensionOption 1 should still be the dominant win: six C compiles under QEMU are the expensive part, and fetching prebuilt binaries removes the compilation rather than the emulation.
Options 2 and 3 mostly help subsequent builds. Note the cache only pays off if releases are frequent enough that it has not been evicted — worth measuring rather than assuming.
Worth adding a comment in
build.ymlrecording why arm64 is there, so a future reader tempted by the build time knows it is intentional.Revised acceptance criteria
build.ymldocuments why the platform list is what it isexifremovedDone —
8d124e4Changes
Prebuilt extensions.
docker-php-ext-install→mlocati/php-extension-installer, matching the CI image from #57. Six C compiles under arm64 QEMU were the bulk of the 36 minutes.Also dropped
libpng-dev,libxml2-devandoniguruma-dev— headers that existed only to compile those extensions — andgit, after confirming every package incomposer.lockhas a zip dist and nothing else in the image uses it.Composer layer ordering.
composer.jsonandcomposer.locknow copy before the source, so a code change no longer reinstalls vendor:--no-scriptson the first install because package discovery needs the full application.dump-autoloadthen triggerspost-autoload-dump, whichcomposer.json:41already wires topackage:discover— I had added an explicitartisan package:discoverand removed it on finding that.Registry build cache via
cache-from/cache-to.exifremoved — unused since #48 took the asset subsystem.composer:latest→composer:2, so a major-version bump cannot arrive unannounced.Comment in
build.ymlrecording that arm64 is deliberate, so the platform list is not "optimised away" by someone looking at the build time.Verified
vendoris in.dockerignore, soCOPY . .cannot clobber the installed layermysql-clientretained —docker/production/start-app.sh:13needs it for the database readiness loopinstall-php-extensionssupports AlpineNot verified
The image was never built. There is no way to build it from here, so this rests on inspection. The riskiest part is the Composer layer split: if
--no-scriptsleaves something thatdump-autoloaddoes not resolve, it fails at build time rather than silently.Closing on the code being correct. The v0.4.1 tag build is the proof — and a failed build publishes nothing, so a mistake stops the release rather than shipping a broken image.
Still to record
Expectation: the extension change does most of the work. The registry cache mostly helps subsequent builds and may be evicted between releases weeks apart, so it should not be counted on.
If the tag build fails or shows no improvement, reopen.