Skip to content

Templated Dockerfile + slim runtime image#11

Merged
loks0n merged 1 commit into
mainfrom
simplify-versions
May 14, 2026
Merged

Templated Dockerfile + slim runtime image#11
loks0n merged 1 commit into
mainfrom
simplify-versions

Conversation

@loks0n
Copy link
Copy Markdown
Contributor

@loks0n loks0n commented May 14, 2026

Summary

  • Collapse php-8.{3,4,5}/Dockerfile into a single root Dockerfile parameterized by versions/<X.Y>.env (PHP_VERSION, PHP_API, and per-extension pins).
  • Strip extension .so files in each builder stage and swap dev packages (*-dev) for their runtime equivalents (*-libs) in the final stage.
  • Compile sockets.so once in the compile stage and copy it into final (instead of recompiling, which dragged linux-headers into the runtime image).
  • Unify extension pins across 8.3/8.4 where possible: redis 6.1.0 → 6.3.0, imagick 3.7.0 → 3.8.1 (8.3), yaml 2.2.4 → 2.3.0, xdebug 3.4.1 → 3.5.1.
  • Root tests.yaml replaces the per-version copies (uses php -m, no ABI-specific paths).

Image size impact

Version Before After Δ
8.3 237 MB 169 MB -29%
8.4 246 MB 178 MB -28%
8.5 262 MB 194 MB -26%

Test plan

  • CI builds all three versions × both arches green
  • Structure tests pass against the root tests.yaml for each version
  • Dive efficiency check passes
  • Trivy scan shows no new HIGH/CRITICAL CVEs vs. main

🤖 Generated with Claude Code

Replace php-8.{3,4,5}/Dockerfile with a single root Dockerfile driven by
versions/<X.Y>.env, and trim the runtime image.

Templating:
- Single Dockerfile parameterized via build args (PHP_VERSION, PHP_API,
  extension pins).
- Per-version pins live in versions/8.3.env, 8.4.env, 8.5.env.
- Root tests.yaml replaces the per-version copies (uses php -m, no
  ABI-specific paths).
- ci.yml builds by sourcing the env file and passing --build-arg per key.

Image-size cleanup (~28% smaller):
- Strip extension .so files in each builder stage (binutils added to the
  compile stage); halves swoole/mongodb among others.
- Replace dev packages with their runtime equivalents in the final stage
  (brotli-libs, lz4-libs, zstd-libs, yaml). Drop linux-headers (build-only).
- sockets.so is now compiled once in the compile stage and copied into
  final, instead of recompiled (which required linux-headers at runtime).

Extension version unification (across 8.3/8.4):
- redis 6.1.0 -> 6.3.0
- imagick 3.7.0 -> 3.8.1 (8.3 only; 8.4/8.5 already at 3.8.1)
- yaml 2.2.4 -> 2.3.0
- xdebug 3.4.1 -> 3.5.1

Image sizes:
- 8.3: 237MB -> 169MB
- 8.4: 246MB -> 178MB
- 8.5: 262MB -> 194MB

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 14, 2026

Greptile Summary

This PR collapses three per-version Dockerfiles into a single parameterized Dockerfile driven by versions/<X.Y>.env files, switches runtime packages from -dev to -libs variants, and strips debug symbols from compiled .so files to shrink the final image by ~26–29%.

  • Templating: ARG PHP_VERSION / ARG PHP_API replace hardcoded 8.3/8.4/8.5 values; version pins move to versions/*.env files sourced by CI.
  • Slim runtime: brotli-dev/lz4-dev/zstd-dev/yaml-dev replaced with *-libs/yaml; linux-headers and apk update removed from the final stage.
  • Symbol stripping: binutils strip --strip-unneeded added after each extension make install; sockets.so is now compiled once in the compile stage and copied rather than recompiled in final.

Confidence Score: 3/5

The build will fail as-is for all three PHP versions due to missing ARG re-declarations in builder stages.

The strip commands in every builder stage reference ${PHP_API}, but Docker ARG values do not carry across FROM instructions unless explicitly re-declared. Only the compile and final stages declare ARG PHP_API; the 12 intermediate builder stages do not. The variable expands to an empty string in those stages, producing an invalid path that causes strip (and therefore the RUN layer) to fail, blocking every build variant.

The root Dockerfile needs ARG PHP_API added after each of the 12 FROM compile AS lines.

Important Files Changed

Filename Overview
Dockerfile New templated Dockerfile using ARG PHP_API/PHP_VERSION; ARG PHP_API is correctly re-declared in final but missing in all 12 builder stages, causing ${PHP_API} to expand to empty and every strip command to fail.
.github/workflows/ci.yml Build step now sources the .env file and passes each key as --build-arg KEY (value picked up from environment); structure-test now uses root tests.yaml.
versions/8.3.env New per-version env file with PHP_VERSION, PHP_API and bumped extension pins for 8.3.
versions/8.4.env New per-version env file with PHP_VERSION, PHP_API and bumped extension pins for 8.4.
versions/8.5.env New per-version env file with PHP_VERSION, PHP_API and commit-hash pins for extensions not yet tagged for 8.5.
tests.yaml Replaces three per-version tests.yaml files; uses php -m and ini-file checks instead of ABI-specific .so path assertions — correctly version-agnostic.
README.md Updated local-dev instructions to document the env-sourcing + --build-arg loop pattern.

Reviews (1): Last reviewed commit: "Collapse per-version Dockerfiles into on..." | Re-trigger Greptile

Comment thread Dockerfile
Comment on lines 48 to +156
FROM compile AS redis
RUN \
# Redis Extension
git clone --depth 1 --branch $PHP_REDIS_VERSION https://github.com/phpredis/phpredis.git && \
cd phpredis && \
phpize && \
./configure && \
make && make install
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## Swoole Extension
FROM compile AS swoole
RUN \
git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git && \
cd swoole-src && \
phpize && \
./configure --enable-sockets --enable-http2 --enable-openssl --enable-swoole-curl && \
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so && \
cd ..

## Imagick Extension
FROM compile AS imagick
RUN \
git clone --depth 1 --branch $PHP_IMAGICK_VERSION https://github.com/imagick/imagick && \
cd imagick && \
phpize && \
./configure && \
make && make install
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## YAML Extension
FROM compile AS yaml
RUN \
git clone --depth 1 --branch $PHP_YAML_VERSION https://github.com/php/pecl-file_formats-yaml && \
cd pecl-file_formats-yaml && \
phpize && \
./configure && \
make && make install
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## Maxminddb extension
FROM compile AS maxmind
RUN \
git clone --depth 1 --branch $PHP_MAXMINDDB_VERSION https://github.com/maxmind/MaxMind-DB-Reader-php.git && \
cd MaxMind-DB-Reader-php && \
cd ext && \
phpize && \
./configure && \
make && make install
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

# Zstd Compression
FROM compile AS zstd
RUN git clone --recursive -n https://github.com/kjdev/php-ext-zstd.git \
&& cd php-ext-zstd \
&& git checkout $PHP_ZSTD_VERSION \
&& phpize \
&& ./configure --with-libzstd \
&& make && make install
&& make && make install \
&& strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## Brotli Extension
FROM compile AS brotli
RUN git clone https://github.com/kjdev/php-ext-brotli.git \
&& cd php-ext-brotli \
&& git reset --hard $PHP_BROTLI_VERSION \
&& phpize \
&& ./configure --with-libbrotli \
&& make && make install
&& make && make install \
&& strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## LZ4 Extension
FROM compile AS lz4
RUN git clone --recursive https://github.com/kjdev/php-ext-lz4.git \
&& cd php-ext-lz4 \
&& git reset --hard $PHP_LZ4_VERSION \
&& phpize \
&& ./configure --with-lz4-includedir=/usr \
&& make && make install
&& make && make install \
&& strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## Snappy Extension
FROM compile AS snappy
RUN git clone --recursive https://github.com/kjdev/php-ext-snappy.git \
&& cd php-ext-snappy \
&& git reset --hard $PHP_SNAPPY_VERSION \
&& phpize \
&& ./configure \
&& make && make install
&& make && make install \
&& strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## Scrypt Extension
FROM compile AS scrypt
RUN git clone --depth 1 https://github.com/DomBlack/php-scrypt.git \
&& cd php-scrypt \
&& git reset --hard $PHP_SCRYPT_VERSION \
&& phpize \
&& ./configure --enable-scrypt \
&& make && make install
&& make && make install \
&& strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## XDebug Extension
FROM compile AS xdebug
RUN \
git clone --depth 1 --branch $PHP_XDEBUG_VERSION https://github.com/xdebug/xdebug && \
cd xdebug && \
phpize && \
./configure && \
make && make install
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so

## MongoDB Extension
FROM compile AS mongodb
RUN \
git clone --depth 1 --recursive --branch $PHP_MONGO_VERSION https://github.com/mongodb/mongo-php-driver.git && \
cd mongo-php-driver && \
phpize && \
./configure && \
make && make install
make && make install && \
strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-${PHP_API}/*.so
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 ARG PHP_API missing in all builder stages

Docker ARG values do not propagate across FROM instructions — each stage must re-declare every ARG it intends to use. The compile stage declares ARG PHP_API (line 5), but none of the 12 downstream stages (redis, swoole, imagick, yaml, maxmind, zstd, brotli, lz4, snappy, scrypt, xdebug, mongodb) do. As a result ${PHP_API} expands to an empty string in every one of them. The strip command becomes strip --strip-unneeded /usr/local/lib/php/extensions/no-debug-non-zts-/*.so, which references a non-existent directory; strip (binutils) exits non-zero, and the entire build fails for every PHP version.

The fix is to add ARG PHP_API immediately after each FROM compile AS <stage> line — the same pattern already correctly applied to the final stage at line 159.

@loks0n
Copy link
Copy Markdown
Contributor Author

loks0n commented May 14, 2026

@greptile-apps the ARG-doesn't-cross-FROM claim is a false positive here.

That rule applies to fresh base images (e.g. FROM alpine). When a stage uses FROM <named-stage> (e.g. FROM compile AS swoole), it inherits the parent stage's build context, including ARGs already in scope. No re-declaration needed.

Empirical confirmation:

ARG MYVAR
FROM alpine AS base
ARG MYVAR
RUN echo "base: [$MYVAR]" > /tmp/out

FROM base AS child
RUN echo "child: [$MYVAR]" >> /tmp/out
RUN cat /tmp/out

Built with --build-arg MYVAR=hello:

base: [hello]
child: [hello]

And on the actual PR: locally-built images show swoole.so at 3 MB (vs. 47 MB unstripped). If ${PHP_API} had expanded to empty, strip /usr/local/lib/php/extensions/no-debug-non-zts-/*.so would have errored and no image would exist.

@loks0n loks0n merged commit 16e8c6a into main May 14, 2026
20 checks passed
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 14, 2026

You're right, and I was wrong to flag this. Looking at the Dockerfile structure:

  • ARG PHP_API is declared and activated in the compile stage
  • All extension stages (FROM compile AS redis, FROM compile AS swoole, etc.) inherit from compile — not from a fresh base image
  • Your empirical test confirms the behavior: named-stage children do carry the parent's ARG scope into RUN commands

The "ARG doesn't cross FROM" rule strictly applies to FROM <external-image> (a new root stage), not to FROM <named-stage>. The 3 MB stripped .so files are proof enough that ${PHP_API} resolved correctly throughout.

Withdrawing the comment — the code is correct as written.

Tip: You can give me more context with [.greptile/rules.md](https://www.greptile.com/docs/code-review/custom-standards) so I get this right next time.

@loks0n loks0n mentioned this pull request May 14, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant