Skip to content

fix(docker): raise PHP post_max_size for default 20MB upload chunks#798

Draft
akirayamamoto wants to merge 2 commits intolibrespeed:masterfrom
akirayamamoto:fix/php-post-max-size-upload-chunks
Draft

fix(docker): raise PHP post_max_size for default 20MB upload chunks#798
akirayamamoto wants to merge 2 commits intolibrespeed:masterfrom
akirayamamoto:fix/php-post-max-size-upload-chunks

Conversation

@akirayamamoto
Copy link
Copy Markdown

@akirayamamoto akirayamamoto commented Apr 26, 2026

Important

Blocked by #800. That PR drops the unused docker-library PHP install from Dockerfile.alpine. Once it lands, this PR will be rebased onto master and the comment about "two PHP installs" can be simplified or removed. Both PRs touch different parts of Dockerfile.alpine and don't conflict mechanically; the order is just to avoid carrying a stale comment.

Fixes #797.

speedtest_worker.js uploads in 20 MB chunks (xhr_ul_blob_megabytes: 20, speedtest_worker.js:59). Both Docker images inherit PHP's stock post_max_size = 8M, so every chunk triggers a startup warning and breaks empty.php's header() calls. Full repro and effects in #797.

Fix

New docker/librespeed-php.ini:

post_max_size = 32M

Copied into the right conf.d for each variant:

  • Dockerfile: ${PHP_INI_DIR}/conf.d. The env var is set by php:8-apache to /usr/local/etc/php and is stable across PHP majors.
  • Dockerfile.alpine: globs /etc/php*/conf.d to find the apk-installed conf.d (currently /etc/php84). The FROM php:8-alpine image ships its own PHP at /usr/local/etc/php, but mod_php (loaded from php-apache2 via apk) doesn't read from there. The glob tracks the apk PHP major and errors if nothing matches.

Filename 99-librespeed.ini follows the NN-name.ini convention so it loads after packaged defaults but before any operator override at a higher prefix. 32M is the next round number above the worker's 20M default.

Verified

Built both variants and tested via real Apache:

$ dd if=/dev/zero bs=1M count=20 | curl -X POST --data-binary @- http://.../backend/empty.php
HTTP 200, 0 bytes

No warnings in body, Apache error log clean, empty.php's response headers make it through.

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Fix PHP upload limits for 20MB default upload chunks

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Raise PHP post_max_size and upload_max_filesize to 32M
• Fixes malformed responses from /backend/empty.php on uploads
• Prevents PHP warnings and header modification failures
• Adds docker/librespeed-php.ini with recommended PHP limits
Diagram
flowchart LR
  A["20MB upload chunks"] -->|exceeds| B["PHP 8M post_max_size"]
  B -->|triggers| C["Startup warning in response"]
  C -->|causes| D["header() calls fail"]
  D -->|results in| E["Malformed response"]
  F["docker/librespeed-php.ini"] -->|sets 32M limits| G["Proper response handling"]
  G -->|enables| H["Clean HTTP 200 response"]
Loading

Grey Divider

File Changes

1. Dockerfile ⚙️ Configuration changes +5/-0

Add PHP config installation to Debian variant

• Copies docker/librespeed-php.ini to PHP's conf.d directory
• Uses PHP_CONFIG_FILE_SCAN_DIR to discover correct path dynamically
• Installs config with 0644 permissions as 99-librespeed.ini
• Follows NN-name.ini convention for proper load order

Dockerfile


2. Dockerfile.alpine ⚙️ Configuration changes +5/-0

Add PHP config installation to Alpine variant

• Copies docker/librespeed-php.ini to Alpine PHP's conf.d directory
• Uses explicit /usr/bin/php to query correct scan dir for mod_php
• Installs config with 0644 permissions as 99-librespeed.ini
• Ensures compatibility with Alpine's PHP packaging structure

Dockerfile.alpine


3. docker/librespeed-php.ini ⚙️ Configuration changes +28/-0

Create PHP configuration for upload limits

• Sets post_max_size = 32M to accommodate 20MB upload chunks
• Sets upload_max_filesize = 32M for consistent upload handling
• Sets memory_limit = 256M conservatively above upload ceiling
• Includes detailed comments explaining the PHP limits issue and rationale

docker/librespeed-php.ini


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented Apr 26, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Context used

Grey Divider


Remediation recommended

1. Hardcoded /usr/bin/php 🐞 Bug ☼ Reliability ⭐ New
Description
Dockerfile.alpine invokes /usr/bin/php to discover PHP_CONFIG_FILE_SCAN_DIR but never ensures
that binary exists, so the image build will fail if the Alpine PHP packages stop providing that path
(or only ship versioned PHP binaries). This fragility can also prevent the ini override from being
installed in the intended scan dir on Alpine if the PHP binary layout changes.
Code

Dockerfile.alpine[28]

+RUN scan_dir="$(/usr/bin/php -r 'echo rtrim(PHP_CONFIG_FILE_SCAN_DIR);')" \
Evidence
The scan-dir discovery is pinned to an absolute path (/usr/bin/php) and the Dockerfile does not
explicitly validate that it exists before using it; the only PHP-related installs are via apk
packages, so the existence of that exact path is an implicit assumption.

Dockerfile.alpine[1-16]
Dockerfile.alpine[27-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Dockerfile.alpine` hardcodes `/usr/bin/php` to read `PHP_CONFIG_FILE_SCAN_DIR`. If that path ever changes/moves (e.g., versioned binaries only), the build fails before installing `99-librespeed.ini`.

### Issue Context
The image already contains at least one PHP binary (from the base image) and also installs Alpine PHP packages for Apache. The Dockerfile should be resilient to binary path differences.

### Fix Focus Areas
- Dockerfile.alpine[27-31]

### Suggested fix
- Add an explicit check like `[ -x /usr/bin/php ]` with a clear error message.
- Or discover the correct apk-provided PHP binary dynamically (e.g., `php_bin="$(command -v php)"` if PATH is set to the intended one, or probe common Alpine versioned paths) and use that for `-r 'echo PHP_CONFIG_FILE_SCAN_DIR'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Brittle scan-dir install 🐞 Bug ☼ Reliability
Description
The Docker build installs the ini into the path returned by PHP_CONFIG_FILE_SCAN_DIR without
validating it or ensuring the directory exists, so the config can be written to an unintended
location (e.g., /99-librespeed.ini) or the build can fail if the scan-dir is empty/missing. This
pattern is used in both Debian and Alpine Dockerfiles.
Code

Dockerfile[R13-16]

+COPY docker/librespeed-php.ini /tmp/librespeed-php.ini
+RUN install -m 0644 /tmp/librespeed-php.ini \
+        "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-librespeed.ini" \
+    && rm /tmp/librespeed-php.ini
Evidence
Both Dockerfiles use an inline command substitution for the destination path and call install
without creating the parent directory or checking that the scan dir is non-empty, making the build
dependent on the base image’s PHP scan-dir layout.

Dockerfile[13-16]
Dockerfile.alpine[27-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Docker build step writes `docker/librespeed-php.ini` into the directory reported by `PHP_CONFIG_FILE_SCAN_DIR` but does not verify that the value is non-empty nor ensure the destination directory exists. This can cause a failed build or an ini written to an unintended path.
### Issue Context
This affects both Debian (`Dockerfile`) and Alpine (`Dockerfile.alpine`) variants.
### Fix Focus Areas
- Dockerfile[13-16]
- Dockerfile.alpine[27-30]
### Suggested change
- Capture the scan dir into a variable, trim it, and fail fast if empty.
- Ensure the directory exists before installing (e.g., `install -D ...` or `mkdir -p "$scan_dir"`).
Example pattern:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

3. Opaque scan_dir build failure 🐞 Bug ⚙ Maintainability ⭐ New
Description
Both Dockerfiles will fail the build if PHP_CONFIG_FILE_SCAN_DIR resolves to an empty string, but
the [ -n "$scan_dir" ] check provides no diagnostic output, making base-image changes harder to
debug. This creates an avoidable failure mode with low observability.
Code

Dockerfile[R14-16]

+RUN scan_dir="$(php -r 'echo rtrim(PHP_CONFIG_FILE_SCAN_DIR);')" \
+    && [ -n "$scan_dir" ] \
+    && install -D -m 0644 /tmp/librespeed-php.ini "$scan_dir/99-librespeed.ini" \
Evidence
The RUN command chains && [ -n "$scan_dir" ]; when false, it exits non-zero without printing a
reason, and the same pattern appears in both Dockerfiles.

Dockerfile[13-17]
Dockerfile.alpine[27-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
If `scan_dir` is empty, the build fails without indicating why, since `[ -n "$scan_dir" ]` emits no output.

### Issue Context
This is a defensive build-time validation; adding a message makes failures actionable when base images change.

### Fix Focus Areas
- Dockerfile[13-17]
- Dockerfile.alpine[27-31]

### Suggested fix
Replace the silent check with an explicit error, e.g.:
```sh
[ -n "$scan_dir" ] || { echo "ERROR: PHP_CONFIG_FILE_SCAN_DIR is empty" >&2; exit 1; }
```
(then continue with `install ...`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit d088379

Results up to commit f870aa8


🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Context used

Remediation recommended
1. Brittle scan-dir install 🐞 Bug ☼ Reliability
Description
The Docker build installs the ini into the path returned by PHP_CONFIG_FILE_SCAN_DIR without
validating it or ensuring the directory exists, so the config can be written to an unintended
location (e.g., /99-librespeed.ini) or the build can fail if the scan-dir is empty/missing. This
pattern is used in both Debian and Alpine Dockerfiles.
Code

Dockerfile[R13-16]

+COPY docker/librespeed-php.ini /tmp/librespeed-php.ini
+RUN install -m 0644 /tmp/librespeed-php.ini \
+        "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-librespeed.ini" \
+    && rm /tmp/librespeed-php.ini
Evidence
Both Dockerfiles use an inline command substitution for the destination path and call install
without creating the parent directory or checking that the scan dir is non-empty, making the build
dependent on the base image’s PHP scan-dir layout.

Dockerfile[13-16]
Dockerfile.alpine[27-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The Docker build step writes `docker/librespeed-php.ini` into the directory reported by `PHP_CONFIG_FILE_SCAN_DIR` but does not verify that the value is non-empty nor ensure the destination directory exists. This can cause a failed build or an ini written to an unintended path.

### Issue Context
This affects both Debian (`Dockerfile`) and Alpine (`Dockerfile.alpine`) variants.

### Fix Focus Areas
- Dockerfile[13-16]
- Dockerfile.alpine[27-30]

### Suggested change
- Capture the scan dir into a variable, trim it, and fail fast if empty.
- Ensure the directory exists before installing (e.g., `install -D ...` or `mkdir -p "$scan_dir"`).

Example pattern:
```sh
scan_dir="$(php -r 'echo rtrim(PHP_CONFIG_FILE_SCAN_DIR);')"
[ -n "$scan_dir" ]
install -D -m 0644 /tmp/librespeed-php.ini "$scan_dir/99-librespeed.ini"
```
(and analogous using `/usr/bin/php` on Alpine).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

@akirayamamoto akirayamamoto force-pushed the fix/php-post-max-size-upload-chunks branch 2 times, most recently from 7dd8342 to 362a1f1 Compare April 26, 2026 09:32
speedtest_worker.js uploads in 20 MB chunks by default
(xhr_ul_blob_megabytes: 20) but the official Docker images inherit
PHP's stock post_max_size = 8M / upload_max_filesize = 2M, so every
upload chunk:

* triggers a "POST Content-Length ... exceeds the limit" warning
  (leaked into the response body of /backend/empty.php on the Debian
  variant where display_errors is on; suppressed but still emitted on
  Alpine where it's off);
* causes empty.php's subsequent header() calls to fail with "Cannot
  modify header information - headers already sent", leaving the
  response without proper status, cache, or CORS directives.

Ship a small docker/librespeed-php.ini with post_max_size = 32M,
upload_max_filesize = 32M, memory_limit = 256M and COPY it into the
right conf.d for each base image (/usr/local/etc/php/conf.d on
Debian, /etc/php84/conf.d on Alpine). 99- prefix follows the
NN-name.ini packaging convention so this loads after distro defaults
but never silently shadows operator overrides.

Verified post-fix on both variants: a 20 MB POST to /backend/empty.php
returns HTTP 200 / 0 bytes with no warning leakage.
@akirayamamoto akirayamamoto force-pushed the fix/php-post-max-size-upload-chunks branch from 362a1f1 to d088379 Compare April 26, 2026 09:38
@akirayamamoto
Copy link
Copy Markdown
Author

/review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented Apr 26, 2026

Persistent review updated to latest commit d088379

The previous build-time discovery via PHP_CONFIG_FILE_SCAN_DIR worked
but invoked a PHP binary just to learn a path that's already a stable
contract of each base image:

* php:8-apache exports PHP_INI_DIR=/usr/local/etc/php as part of the
  docker-library image template, stable across PHP majors. Reference
  it directly in the COPY destination — no RUN, no validation needed.

* Alpine's apk php-apache2 always installs mod_php's conf.d at
  /etc/phpXX/conf.d (currently /etc/php84). The FROM php:8-alpine
  image also ships its own PHP at /usr/local/etc/php/conf.d, but
  mod_php doesn't read from there. Glob /etc/php*/conf.d to track
  the apk-installed PHP major automatically; explicit error if the
  glob matches nothing.

Net effect: Debian goes from a 5-line RUN block to a 1-line COPY.
Alpine keeps a small RUN block but no longer invokes a PHP binary,
so it doesn't matter which of the two PHP installs `php` resolves
to via $PATH.

Verified: both variants build, install the override at the right
conf.d, mod_php reports post_max_size=32M, and a 20 MB POST to
/backend/empty.php returns HTTP 200 / 0 bytes with no warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akirayamamoto akirayamamoto marked this pull request as draft April 27, 2026 04:16
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.

Default PHP upload limits (8M/2M) reject the worker's 20 MB upload chunks

1 participant