- Refresh the PHP 8.5 Alpine base image to PHP 8.5.10, pulling patched
libexpat2.8.4-r0 andrsync3.5.0-r0 packages. The resulting production and XDebug images have no vulnerabilities reported by Trivy. - Make the Trivy workflow fail on fixable medium, high, or critical vulnerabilities while continuing to upload its SARIF report.
- The
igbinaryextension, so Appwrite can store cache entries as binary payloads through theIgbinarycodec inutopia-php/cacheinstead of JSON. - Weekly dependency automation (
.github/workflows/dependencies.yml). A scheduled job resolves the newest upstream release for every pinned Dockerfile source, rewrites the pins, opens a pull request, waits for the exact CI runs for that head, approves and merges it, then tags, builds, and publishes the release. Arecoverstep resumes a run that died between merge and publish, so a half-finished release is completed rather than duplicated. - PHP automation domain under
.github/scripts—Dependency(catalog, resolvers, Dockerfile pin rewriting, reporting),Automation(release orchestration, version selection, merge and target validation, recovery),Command, andParity. Entry points arebin/dependencies.php,bin/orchestrator.php, andbin/parity.php. - Composer tooling for the automation:
lint(Pint),check(PHPStan),test(PHPUnit),parity(asserts every source class has covering tests), andverifyto run all four. CI runscomposer verifybefore touching any dependency. verify.ymlrunscomposer validate --strict,composer check-platform-reqs, andcomposer verifyon every push, so the automation is gated at pull-request time rather than only on the Monday run that uses it.- Dependabot now tracks the
composerecosystem. The automation is only as trustworthy as the Pint, PHPStan, and PHPUnit versions gating it.
-
The updater rewrote
PHP_*_VERSIONand leftPHP_*_COMMIT/PHP_*_CHECKSUMat the superseded release. Protobuf failed loudly on the checksum, but the git-sourced extensions did not: the build fetched the old commit and shipped, say, brotli 0.20.0 in an image labelled 0.21.0.Dockerfile::pins()only ever located the version variable, so no companion reference was ever a candidate for replacement. Every dependency now carries its reference variable through the catalog, resolver, selector, and rewriter, and both move together or neither does. -
git ls-remote --tags --refsreturns the annotated tag object, not the commit it points at —refs/tags/6.3.0on phpredis isaa4302d, while the commit isdf4fab2. Resolving references from that output would have replaced correct commit pins with tag-object SHAs. The resolver now reads the peeled^{}entry when a tag is annotated and falls back to the object for lightweight tags. -
The release step could never succeed.
createDraftasks GitHub forgenerate_release_notes=true, so the returned body is the automation's body plus the generated changelog — and bothassertDraftandvalidateDraftthen required the body to equal what was sent. Every run died atDraft release <id> is unsafeafter tagging and drafting. Both checks now require the body to open with the automation markers, which is what the safety property actually depends on;RecoverySelector::matchesalready worked this way. -
Draft release <id> is unsafenamed none of the six fields it compared, so diagnosing it needed the API and the source side by side. It now says which ones mismatched. -
Recovery treated any automation merge without a tag as an unfinished release, and
mergedPullRequests()paginates the entire closed-PR history — so an abandoned release stayed recoverable forever. A merge whose release was deliberately dropped would be re-tagged and published on the next run, from a commit main had already moved past. An untagged automation merge is now recoverable only while it is still the tip ofmain; once main has moved on, the release was abandoned, not interrupted. The head lookup is lazy, so recovering an already-tagged release never depends on it. -
A reference that has drifted from its version is now corrected on the next run even when the version itself is unchanged, so a hand-edited or stale pin self-heals instead of persisting. Every reference is resolved from upstream unconditionally — the peeled commit for git, a fresh hash of the selected tarball for PECL — rather than carrying forward whatever the file already held. A pinned tag that upstream no longer publishes now fails the run instead of passing silently.
-
Duplicate release builds —
build-and-push.ymlno longer triggers onrelease: published. Tag pushes already trigger it, so publishing a release rebuilt and repushed the same image a second time. -
The XDebug
container-structure-teststep ranplexsystems/container-structure-test-action@v0.1.0— a mutable tag two minor versions behind the production step directly above it, and the only action reference in the repo not pinned to a commit SHA. Both steps now pin the samev0.3.0commit.
- Publish the production image instead of the XDebug variant. Every workflow built with
docker image build ... .and no--target, and Docker defaults to the last stage — which isxdebug. Soappwrite/basehas shipped XDebug in the production image since the variant was introduced in 1.2.0, and Trivy and dive were measuring the wrong image too. All four build workflows now pass an explicit--target. The stage order cannot be fixed instead:xdebugisFROM final, so it must be declared afterfinal, which necessarily makes it last —--targetis the only reliable control.
- Publish the XDebug variant under
-xdebugtags (<sha>-xdebug,<tag>-xdebug, per-arch and manifest). It was documented as a build target since 1.2.0 but never published, so consumers that want XDebug — such as Appwrite'sdevelopmentimage, which supplies an ini expectingxdebug.soto already exist — now have a real image to pin. - Wire
tests-xdebug.yamlinto the structure-test workflow. It had existed since 1.2.0 with no workflow consuming it. tests.yamlassertion that XDebug is absent.tests.yamlonly ever asserted module presence, so the XDebug image satisfied it and CI stayed green while shipping the wrong image.
- Ship a hardened ImageMagick
policy.xmlin the final image to mitigate image-decompression-bomb DoS via the Appwrite storage/avatars preview pipeline. A crafted image (small on disk, huge dimensions) previously decoded unbounded, spilling ImageMagick's pixel cache to disk and filling the volume — killing MongoDB or the container. The policy capsdisk(4GiB spill limit — the primary control; sized for a 150MP Q16-HDRI photo) plus generouswidth/heightbackstops (50KP, well above any real camera so legitimate high-res photos are never rejected), setsmemory/map/area/threadlimits, and disables coders/modules never used for previews (PS/EPS/PDF/XPS/MSL/MVG/HTTP/etc., plus SVG/SVGZ/MSVG via amoduledeny that also closes the SVG SSRF/XXE delegate route). Installed into ImageMagick's configure dir (discovered at build time); the build fails if the policy does not load. Added atests.yamlassertion verifying the policy is active.
- Run
apk upgradein the final image to pull patchedmuslandxz-libs— resolves CVE-2025-26519 (muslqsortstack corruption), the musliconvGB18030 DoS, and thexzindex-decoding buffer overflow (CVE-2026-34743, fixed inxz-libs5.8.3-r0). The compile stage already ranapk upgrade, but the runtime stage didn't, so the published image was shipping unpatched libs from the base.
- Pin Swoole base image to
phpswoole/swoole:6.2.0-php8.5-alpine(released 6.2.0, was previously tracking nightlyphp8.5-alpine) for reproducible builds tests.yamlPHP assertion bumped to 8.5.4 and Swoole assertion pinned to 6.2.0 to match the pinned base
- Manifest workflow tag reference —
manifest_build_and_push_on_tagnow usesgithub.ref_nameinstead ofgithub.event.release.tag_name, which is empty on plain tag-push events and broke the1.2.2tag run withdocker manifest create: invalid reference format
- PHP
opentelemetryextension — its observer hooks overridezend_execute_exand disable opcache JIT on PHP 8.5
- Restore
gitin final image — unintentionally dropped from runtime apk install in 1.2.0; required by VCS-dependent services
- container-structure-test for
gitcommand
tests.yamlPHP assertion bumped to 8.5.5 (upstreamphpswoole/swoole:php8.5-alpineupdate)tests.yamlSwoole assertion bumped to 6.2.1
- container-structure-test checks for PHP GD supported formats
- PHP GD compiled with AVIF, FreeType, JPEG, PNG, and WebP support
- tests-xdebug.yaml for testing the XDebug variant
- XDebug optional build variant — build with
--target xdebug
core-extensionsbuild stage compiles gd, intl, pdo_mysql, pdo_pgsql, sockets- Final image now uses runtime-only packages (no
-devpackages or build tools) - PHP extension
.sofiles stripped of debug symbols to reduce size - PHP extensions compiled in isolated build stages and copied into final image
- PHP version bumped to 8.5.4
- .github/workflows/build-and-push.yml manifest_build_and_push_on_feature no longer triggers on tag creation
- .github/workflows/build-and-push.yml manifest_build_and_push_on_tag now correctly builds on tag creation
- .dockerignore
- .github/workflows/pr-scan.yml to scan all commit pushes for vulnerabilities
- base_image and php_build_date to container labels
- container image build action to publish image using commit sha
- container-structure-test to check PHP version (currently set to 8.5.3)
- container-structure-test to check swoole version (currently set to 6.2.0)
- SECURITY.md to align with appwrite/appwrite
- .github/*.yml steps updated to latest versions
- Better document use of
docker buildx ...for local builds - Better noted and organized the different build processes for PHP extensions
- Date component of PHP extension shared objects directory now a build argument
- Dockerfile base now based on
phpswoole/swoole:php8.5-alpine - Dockerfile compile and final stage system packages aligned
- GitHub action for container-structure-test now uses a marketplace action
- GitHub action runners pinned to Ubuntu 24.04
- ImageMagick version bumped to 7.1.2.15 via APK
- PHP version bumped to 8.5
- Refactored multi-arch build process to prevent cross-arch builds requiring long wait times
- README.md usage instructions more detailed
- Build tools from final stage of Dockerfile
- GitHub action to Setup QEMU as GitHub now provides native ARM runners