Skip to content

fix(php8): prevent OPcache double-load without disabling it on older parents - #91

Merged
lussoluca merged 4 commits into
masterfrom
fix/duplicate-opcache-load
Jun 16, 2026
Merged

fix(php8): prevent OPcache double-load without disabling it on older parents#91
lussoluca merged 4 commits into
masterfrom
fix/duplicate-opcache-load

Conversation

@lussoluca

@lussoluca lussoluca commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🤖 This was written by an AI agent on behalf of @lussoluca.

Problem

Since e9b6e49 ("feat(php8): add PHP 8.5.7 and refresh image dependencies"), the 8.x image emits at every PHP invocation on some builds:

Cannot load Zend OPcache - it was already loaded

Reproduced on the 8.4.22-fpm-alpine3.24-rootless tag. The 8.4.2 tag (built before that commit) is clean.

Root cause

Commit e9b6e49 added a loader block:

RUN if [ -f "$(php -r 'echo ini_get("extension_dir");')/opcache.so" ]; then \ echo "zend_extension=opcache.so" > /usr/local/etc/php/conf.d/00-opcache-load.ini; \ fi

This fires whenever opcache.so exists (all PHP <= 8.4). Whether that double-loads depends on whether the official php parent already enables OPcache.

The official image started enabling OPcache by default in docker-library/php#1587 "Enable opcache by default", merged 2025-06-25, which adds RUN docker-php-ext-enable opcache to every Dockerfile variant (motivated by the RFC make_opcache_required). The determining factor is whether a given php:<patch> tag was built after that merge — not the Alpine version. Pinned old patch releases keep their pre-merge frozen image; newer patches were rebuilt with the change.

So:

  • Parent built post-merge (e.g. php:8.4.22-fpm-alpine3.24) — already loads OPcache via docker-php-ext-opcache.ini; our block adds a second loader → double-load warning.
  • Parent built pre-merge (e.g. php:8.3.2-fpm-alpine3.18, php:8.3.15-fpm-alpine3.21, php:8.4.2-fpm-alpine3.21) — does NOT load OPcache; our block is the only loader.
  • PHP 8.5 — OPcache is statically compiled, opcache.so is absent, the block never fires; parent loads it statically.

Verified against the official parents:

official parent ships docker-php-ext-opcache.ini OPcache loaded by parent
php:8.3.2-fpm-alpine3.18 (pinned, pre-merge) no false
php:8.3.15-fpm-alpine3.21 (pinned, pre-merge) no false
php:8.4.2-fpm-alpine3.21 (pinned, pre-merge) no false
php:8.3-fpm-alpine3.21 (rolling, post-merge) yes true
php:8.4.22-fpm-alpine3.24 (post-merge) yes true
php:8.5.7-fpm-alpine3.24 (static) static true

The two alpine3.21 rows show the split is build-date, not Alpine version.

Why not just delete the block

An earlier revision of this PR removed the block entirely. That regressed OPcache on every build whose parent predates docker-library/php#1587 (currently 8.3.2, 8.3.15, 8.4.2) — verified by building 8.3.15 from that revision: no loader present, extension_loaded("Zend OPcache")false. The block was not "always redundant"; it was redundant only on post-merge parents.

Fix

Guard the block so it adds 00-opcache-load.ini only when the parent has not already enabled OPcache and opcache.so exists:

RUN if [ ! -f /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini ] \
        && [ -f "$(php -r 'echo ini_get("extension_dir");')/opcache.so" ]; then \
        echo "zend_extension=opcache.so" > /usr/local/etc/php/conf.d/00-opcache-load.ini; \
    fi

This is version- and date-agnostic: it keys off the actual presence of the parent's opcache ini.

Additional cleanup: drop dead opcache.fast_shutdown

8/conf/opcache.ini set opcache.fast_shutdown=1. That directive was removed in PHP 7.2 — fast shutdown became always-on and the option no longer exists. On PHP 8.x it is absent from phpinfo() and ini_get("opcache.fast_shutdown") returns false, so the line is silently ignored dead config. The 8/ image only targets PHP 8.x, so it can never apply. Removed.

Verification

Built from this branch (--build-arg PHPVER=..., --target dist) and checked OPcache state:

version loader source php -v warning extension_loaded engine active (enable_cli=1)
8.3.15 block fires (opcache.so) none true true
8.4.22 parent docker-php-ext-opcache.ini none true true
8.5.7 static (block skipped) none true true

OPcache stays enabled on every build, the double-load warning is gone, and opcache.ini still tunes it (it carries no zend_extension line).

Assisted-by: claude-code/claude-opus-4-8

The official php:8.x-fpm image already enables OPcache via
docker-php-ext-opcache.ini (zend_extension=opcache). Adding a second
00-opcache-load.ini (zend_extension=opcache.so) made PHP load OPcache
twice on PHP <= 8.4, emitting 'Cannot load Zend OPcache - it was already
loaded' at every invocation.

On PHP 8.5 OPcache is statically compiled, so the guard never created the
file there anyway. The block only ever fired on <= 8.4, exactly where the
parent image already loads OPcache, so it was always redundant.

opcache.ini still tunes OPcache in both cases; it carries no zend_extension
line.

Assisted-by: claude-code/claude-opus-4-8
@sparkfabrik-ai-bot

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

OPcache on PHP 8.5

After removing the block, verify that OPcache is properly enabled and functional on PHP 8.5 builds. The PR description states OPcache is statically compiled on 8.5, but it should be confirmed that the opcache.ini tuning file still applies correctly and OPcache is active without any additional loader configuration.

COPY conf/*.ini /usr/local/etc/php/conf.d/
COPY conf.disabled /usr/local/etc/php/conf.disabled
COPY fpm-conf-templates/ /templates/

# Make folders writable for the root group
RUN chmod 775 /usr/local/etc/php && \
    chmod 775 /usr/local/etc/php/conf.d && \
    chmod 775 /usr/local/etc/php-fpm.d && \

@sparkfabrik-ai-bot

Copy link
Copy Markdown

PR Code Suggestions ✨

No code suggestions found for the PR.

@lussoluca
lussoluca requested a review from Monska85 June 16, 2026 07:02
opcache.fast_shutdown was removed in PHP 7.2 — fast shutdown became
always-on and the directive no longer exists. PHP 8.4 does not list it in
phpinfo() and ini_get() returns false, so the line in 8/conf/opcache.ini is
silently ignored dead config. The 8/ image only targets PHP 8.x, so it can
never apply. Remove it.

Assisted-by: claude-code/claude-opus-4-8
Removing the loader block entirely regressed OPcache on the 8.3.x and
8.4.2 builds: their official php:8.x-fpm-alpine parents (alpine 3.18/3.21)
do not ship docker-php-ext-opcache.ini, so the removed block was the only
thing enabling OPcache there. Verified by building 8.3.15 from this branch:
no loader, extension_loaded('Zend OPcache') === false.

Only the newer alpine3.24 parents (PHP 8.4.22+) and the static 8.5 build
enable OPcache on their own, which is why the double-load warning only
appeared there.

Restore the block but guard it: add 00-opcache-load.ini only when the parent
has not already enabled OPcache (docker-php-ext-opcache.ini absent) and
opcache.so exists. Result, verified across builds:
- 8.3.15: block fires, OPcache on, no warning
- 8.4.22: parent ini present, block skipped, no double-load
- 8.5.7:  static, no opcache.so, block skipped

Assisted-by: claude-code/claude-opus-4-8
@lussoluca lussoluca changed the title fix(php8): remove duplicate OPcache loader on PHP <= 8.4 fix(php8): prevent OPcache double-load without disabling it on older parents Jun 16, 2026
@Monska85
Monska85 requested a review from Copilot June 16, 2026 08:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a PHP 8.x image runtime warning caused by OPcache being loaded twice when the upstream php:<tag> parent already enables OPcache by default, while preserving OPcache enablement for older (pre-change) parents.

Changes:

  • Guard OPcache loader creation (00-opcache-load.ini) so it’s only added when the parent image hasn’t already enabled OPcache.
  • Remove the obsolete opcache.fast_shutdown directive from the PHP 8 OPcache config.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
8/Dockerfile Adds a presence-check guard to prevent OPcache double-loading while keeping OPcache enabled on older parents.
8/conf/opcache.ini Removes a dead OPcache setting that no longer applies to PHP 8.x.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread 8/Dockerfile Outdated
Comment on lines +91 to +95
# Enable OPcache as a Zend extension only when the parent image hasn't already
# (older php:8.x parents don't ship docker-php-ext-opcache.ini) and it ships as
# a shared object. Newer parents (PHP 8.4.22+) and static builds (PHP 8.5)
# already load it, so the guard avoids the "Cannot load Zend OPcache - it was
# already loaded" double-load while keeping OPcache enabled everywhere.

@Monska85 Monska85 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

Key the explanation off the docker-php-ext-opcache.ini file and
docker-library/php#1587 rather than a specific patch version (8.4.22+),
since rolling 8.3+ tags rebuilt after that PR also ship the ini.

Assisted-by: claude-code/claude-opus-4-8
@lussoluca
lussoluca merged commit 2632f56 into master Jun 16, 2026
14 checks passed
@lussoluca
lussoluca deleted the fix/duplicate-opcache-load branch June 16, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants