From 36771d04d92b72ed7dfeaa5ccb0ae3d94743a1e3 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 14 Jul 2026 13:06:02 -0400 Subject: [PATCH 1/2] Add the Cloudflare page-cache helper mu-plugin Signals page cacheability to the Worker edge cache (marks search, preview, and logged-in views non-cacheable via X-WP-Cacheable, and sends cache-friendly headers otherwise), and purges the edge cache on content changes by calling the Worker's /__cache/purge endpoint (authenticated with the CACHE_PURGE_SECRET forwarded from the Worker into the container). The purge is a global cache-version bump, so no per-URL enumeration is needed. Auto-installed like the other mu-plugins. Co-Authored-By: Claude Fable 5 --- mu-plugins/cloudflare-page-cache.php | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 mu-plugins/cloudflare-page-cache.php diff --git a/mu-plugins/cloudflare-page-cache.php b/mu-plugins/cloudflare-page-cache.php new file mode 100644 index 0000000..b3ac373 --- /dev/null +++ b/mu-plugins/cloudflare-page-cache.php @@ -0,0 +1,118 @@ + array( 'X-WP-Cache-Secret' => $secret ), + 'blocking' => false, + 'timeout' => 2, + ) + ); +} + +/* + * Purge on content and configuration changes. A version bump invalidates + * the whole cache, so there is no need to enumerate affected URLs. + */ +foreach ( + array( + 'save_post', + 'deleted_post', + 'trashed_post', + 'untrashed_post', + 'comment_post', + 'edit_comment', + 'wp_set_comment_status', + 'switch_theme', + 'customize_save_after', + 'activated_plugin', + 'deactivated_plugin', + 'wp_update_nav_menu', + ) as $wp_cf_cache_hook +) { + add_action( $wp_cf_cache_hook, 'wp_cf_cache_purge', 10, 0 ); +} From aeb11e63be40b61eb79f7f823f1d5bd4da2745f2 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 14 Jul 2026 13:14:05 -0400 Subject: [PATCH 2/2] Bake WordPress core into the image (pinned 7.0.1) Instead of downloading WordPress at every cold start (ephemeral disk), bake a pinned core (fetchzip 7.0.1) at /usr/src/wordpress and install it from there on first boot, falling back to WORDPRESS_SOURCE_URL only when the baked core is absent. This makes cold starts fast and offline, and fixes the core version so the Cloudflare Worker can serve the exact matching static-asset set from Worker Assets. Verified against the built image: core is present and installs into a writable docroot. Co-Authored-By: Claude Fable 5 --- docker-entrypoint.sh | 50 +++++++++++++++++++++++------------------- modules/containers.nix | 18 +++++++++++++++ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index b5d0a0c..5d102d9 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -30,30 +30,36 @@ fi # Default WordPress URL if not provided WORDPRESS_SOURCE_URL=${WORDPRESS_SOURCE_URL:-"https://wordpress.org/latest.zip"} -# Function to download and install WordPress +# Function to install WordPress: from the core baked into the image when +# present (fast, offline), otherwise downloaded from WORDPRESS_SOURCE_URL. install_wordpress() { - echo "WordPress not found. Downloading and installing from: $WORDPRESS_SOURCE_URL" - wget -O wordpress.zip "$WORDPRESS_SOURCE_URL" - - # Create a temporary directory for extraction - TEMP_DIR="/tmp/wordpress" - mkdir -p "$TEMP_DIR" - unzip wordpress.zip -d "$TEMP_DIR" - - # Find WordPress files - WP_ROOT=$(find "$TEMP_DIR" -name wp-config-sample.php -exec dirname {} \; | head -n 1) - - if [ -z "$WP_ROOT" ]; then - echo "Error: WordPress files not found in the downloaded archive." - exit 1 + if [ -f /usr/src/wordpress/wp-includes/version.php ]; then + echo "Installing bundled WordPress core from /usr/src/wordpress" + cp -r /usr/src/wordpress/. /var/www/html/ + chmod -R u+w /var/www/html + else + echo "WordPress not found. Downloading and installing from: $WORDPRESS_SOURCE_URL" + wget -O wordpress.zip "$WORDPRESS_SOURCE_URL" + + # Create a temporary directory for extraction + TEMP_DIR="/tmp/wordpress" + mkdir -p "$TEMP_DIR" + unzip wordpress.zip -d "$TEMP_DIR" + + # Find WordPress files + WP_ROOT=$(find "$TEMP_DIR" -name wp-config-sample.php -exec dirname {} \; | head -n 1) + + if [ -z "$WP_ROOT" ]; then + echo "Error: WordPress files not found in the downloaded archive." + exit 1 + fi + + # Move WordPress files to the correct location + mv "$WP_ROOT"/* /var/www/html/ + + # Clean up + rm -rf "$TEMP_DIR" wordpress.zip fi - - # Move WordPress files to the correct location - mv "$WP_ROOT"/* /var/www/html/ - - # Clean up - rm -rf "$TEMP_DIR" wordpress.zip - # chown -R nobody:nobody /var/www/html # Import database if WORDPRESS_DB_URL is set if [ -n "${WORDPRESS_DB_URL:-}" ]; then diff --git a/modules/containers.nix b/modules/containers.nix index bba3afa..1489e4e 100644 --- a/modules/containers.nix +++ b/modules/containers.nix @@ -15,10 +15,22 @@ d1DriverSrc ? null, # Package set providing the Rust toolchain for the native extensions. rustPkgs ? pkgs, + # WordPress core is baked into the image (at /usr/src/wordpress) so cold + # starts don't re-download it, and so the exact static-asset set is known + # (the Cloudflare Worker serves those files from Worker Assets — keep this + # version in sync with wordpress-cloudflare's WORDPRESS_VERSION). + wordpressVersion ? "7.0.1", + wordpressHash ? "sha256-vkzmfQpcj/qYT26PXi+V2ji/F5tKJhk0zZJ9QkHwQoY=", }: let phpBuild = import ../lib/php.nix { inherit pkgs php; }; + # WordPress core, extracted (fetchzip strips the leading "wordpress/" dir). + wordpressCore = pkgs.fetchzip { + url = "https://wordpress.org/wordpress-${wordpressVersion}.zip"; + hash = wordpressHash; + }; + phpExtensions = if d1DriverSrc == null then null @@ -94,6 +106,12 @@ pkgs.dockerTools.buildLayeredImage { mkdir -p var/www/html cp ${../conf/wp-config.php} wp-config.php + # Bake WordPress core into the image. The entrypoint installs it into the + # docroot on first boot (no runtime download). + mkdir -p usr/src + cp -r ${wordpressCore} usr/src/wordpress + chmod -R u+w usr/src/wordpress + # The APCu persistent object cache drop-in. The entrypoint installs it # as wp-content/object-cache.php unless WORDPRESS_OBJECT_CACHE=none. cp ${../conf/object-cache.php} object-cache.php