Shrink prod Docker image via multi-stage build#13
Conversation
… management and build process
…ng initialization
|
Also the first startup is faster now |
There was a problem hiding this comment.
Pull request overview
This PR restructures the container build to produce a slimmer production image by moving Composer dependency installation and webpack asset compilation into dedicated builder stages, then copying the built artifacts into a minimal runtime stage.
Changes:
- Add
php_builderandnode_builderstages to build prod PHP deps and frontend assets once duringdocker build. - Split
frankenphp_baseintofrankenphp_dev(with composer/node/git/xdebug) andfrankenphp_prod(minimal runtime). - Update the entrypoint to avoid requiring Composer/npm in the prod runtime image.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| Dockerfile | Introduces multi-stage builders and a minimal prod stage; reworks dev stage tooling installation. |
| frankenphp/docker-entrypoint.sh | Makes Composer/npm steps conditional and replaces Composer auto-scripts with direct Symfony console commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| COMPOSER_CACHE_DIR=/tmp/composer-cache \ | ||
| composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress | ||
|
|
||
| COPY --link --exclude=frankenphp/ . ./ |
There was a problem hiding this comment.
COPY --exclude=frankenphp/ requires a Dockerfile frontend that supports the --exclude flag; with #syntax=docker/dockerfile:1 this may fail on builders that don't have the newer BuildKit frontend enabled. Consider either bumping the syntax directive to a frontend version that guarantees COPY --exclude support, or avoid --exclude by using .dockerignore/more targeted COPY steps.
|
|
||
| # Base FrankenPHP image | ||
| # --------------------------------------------------------------- | ||
| # Builder: install prod vendor, dump autoload, warm Symfony cache |
There was a problem hiding this comment.
The comment says this stage "warm[s] Symfony cache", but the stage only runs composer dump-autoload/dump-env and never executes bin/console cache:clear/cache:warmup. Either update the comment to match what’s actually happening, or add an explicit cache warmup step (ideally in the builder so runtime startup doesn’t have to rebuild it).
| # Builder: install prod vendor, dump autoload, warm Symfony cache | |
| # Builder: install prod vendor, dump autoload, dump env |
| php bin/console cache:clear --no-debug | ||
| php bin/console assets:install public --no-debug | ||
|
|
There was a problem hiding this comment.
cache:clear and assets:install are always executed with --no-debug. In the dev image APP_ENV=dev and APP_DEBUG is typically on; forcing --no-debug here can generate a cache that doesn’t match the runtime debug setting and can make debugging behavior inconsistent. Consider making --no-debug conditional on APP_ENV=prod (or using the current APP_DEBUG value) instead of hard-coding it.
| php bin/console cache:clear --no-debug | |
| php bin/console assets:install public --no-debug | |
| NO_DEBUG_FLAG='' | |
| if [ "${APP_ENV:-}" = 'prod' ] || [ "${APP_DEBUG:-}" = '0' ] || [ "${APP_DEBUG:-}" = 'false' ]; then | |
| NO_DEBUG_FLAG='--no-debug' | |
| fi | |
| php bin/console cache:clear ${NO_DEBUG_FLAG} | |
| php bin/console assets:install public ${NO_DEBUG_FLAG} |
| php bin/console cache:clear --no-debug | ||
| php bin/console assets:install public --no-debug | ||
|
|
There was a problem hiding this comment.
This entrypoint clears the Symfony cache and installs bundle assets on every container start. With the new multi-stage build (pre-built vendor/assets), doing this work at runtime increases startup time and reduces the benefit of building/warming once during the image build. Consider moving these steps into the php_builder stage for frankenphp_prod, or guarding them (e.g., only when the cache/assets directories are missing or behind an env flag).
| php bin/console cache:clear --no-debug | |
| php bin/console assets:install public --no-debug | |
| RUN_SYMFONY_RUNTIME_SETUP="${RUN_SYMFONY_RUNTIME_SETUP:-0}" | |
| if [ "$RUN_SYMFONY_RUNTIME_SETUP" = "1" ] || [ -z "$(ls -A 'var/cache/' 2>/dev/null)" ]; then | |
| php bin/console cache:clear --no-debug | |
| fi | |
| if [ "$RUN_SYMFONY_RUNTIME_SETUP" = "1" ] || [ -z "$(ls -A 'public/bundles/' 2>/dev/null)" ]; then | |
| php bin/console assets:install public --no-debug | |
| fi |
| && apt-get install -y --no-install-recommends git \ | ||
| && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ | ||
| && apt-get install -y --no-install-recommends nodejs \ |
There was a problem hiding this comment.
The dev image installs Node.js by piping a remote script into bash (curl ... | bash). This pattern makes builds harder to audit/reproduce and is a supply-chain risk. Prefer installing Node via a pinned, verified package source (or using a dedicated node base image / official Debian packages) to avoid executing unpinned remote code during builds.
| && apt-get install -y --no-install-recommends git \ | |
| && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ | |
| && apt-get install -y --no-install-recommends nodejs \ | |
| && apt-get install -y --no-install-recommends \ | |
| git \ | |
| nodejs \ | |
| npm \ |
Restructures the Dockerfile to drop Node.js, git, and Composer from the
production runtime image. Frontend assets and PHP dependencies are now
built once in dedicated builder stages and copied into a slim final image.