Upload Limits when deploying WordPress to Google App Engine

TL;DR App Engine Standard limits deployments to 10,000 files. For WordPress this limit can be exceeded because of plugins. App Engine will run a composer file is present, so you can use WordPress Packagist to install the plugins and leave them out of the deployment upload. Some tricks are needed for public files (.css, .js, etc) to be accessible.

Google App Engine Standard provides some convenient options for deployment, scalability and cost on the Google Cloud platform for less complex projects. Cost wise it allows you to scale to zero and to utilize the F and B class instances which provide free credit hours per day. As a trade off it is restrictive with a number of limitations and quotas in place. Many of which can make running WordPress a bit of a challenge. You can read more about these limits and when to choose Standard vs Flexible environments at https://cloud.google.com/appengine/docs/the-appengine-environments.

One such restriction is that deployments are limited to 10,000 files. This is more than enough to accommodate the WordPress codebase, but it is common these days for plugin authors to use the composer package manager and include a vendor folder which the plugins requires. Similar to npm and node_modules, composer and the vendor folder can often contains thousands upon thousands of files. Exacerbating this is that many plugins will use the same popular vendor plugins such as Guzzle, resulting in duplicate copies littered around your plugins folder. As a result we can very quickly exceed this file limit.

Thankfully Google App Engine Standard performs a composer install in the docroot folder at the last step of a deployment. This lets us use the WordPress Packagist repository, a mirror of WordPress plugins and themes, to install plugins in App Engine and avoid including them in the code we upload. As an additional tip, it can also be used to avoid keeping plugin source and updates in your source control repositories.

...
  "repositories": [
    {
      "type": "composer",
      "url": "https://wpackagist.org",
      "only": [
        "wpackagist-plugin/*"
      ]
    }
  ],
  "extra": {
    "installer-paths": {
      "wp-content/plugins/{$name}/": [
        "type:wordpress-plugin"
      ]
    }
  },
...
 "require": {
    "wpackagist-plugin/akismet": "4.1.9",
    "wpackagist-plugin/elementor": "3.2.3",
    "wpackagist-plugin/google-site-kit": "1.32.0",
    "wpackagist-plugin/query-monitor": "3.6.8",
    "wpackagist-plugin/redis-cache": "2.0.18",
    "wpackagist-plugin/wordfence": "7.5.3",
    "wpackagist-plugin/wp-crontrol": "1.10.0",
    "wpackagist-plugin/wp-mail-smtp": "2.8.0",

  }
...

With that issue solved, the next challenge is not uploading the plugin files in the deployment. Depending on how your deployment pipeline is setup the specific method may differ, however the general approach is to either ignore the files or delete them in your build/deploy scripts.

To ignore the files you can make use of a .gcloudignore file, which is based on the .gitignore format. We can ignore the entire plugins folder with something like this:

# default rules created by gcloud
.gcloudignore

.git
.gitignore

/vendor/

# ignore all plugins
/wp-content/plugins/*

However this brings us to the next challenge. Most plugins include static assets that are meant to be public such as .css, .js or .jpg files to function properly. App Engine Standard operates in a secured sandbox and all requests are sent through a front controller (index.php or a php file of your choice). To serve static files directly instead they must be specified using the static_dir or static_files directives in app.yaml (see Serving files directly from your app for more details). To complicate things, it will only serve files that were uploaded in the deployment. This excludes files created during the composer install action. So if we completely exclude our plugins, these static files cannot be served directly from App Engine.

To get around this, we will instead ignore all php files instead the entire plugin. This will keep the file count low while including the required static assets.

wp-content/plugins/**/*.php

Were we to deploy this now, we would quickly find out that our composer install action will skip all of the partial plugin folders, leaving us with no actual plugins installed. Just a bunch of .css and .js files. To fix this we need to delete our partial folders to have the composer install run cleanly. We can make use of the composer scripts directive to achieve this. It allows us to run a script or command before the install actions using “pre-install-cmd”.

...
"scripts": {
    "pre-install-cmd": "rm -rf wp-content/plugins/*"
}
...

By cleaning out the plugins folder first, composer install should now run as expected… except it won’t. Not always. As mentioned in Declaring Dependencies in the official docs, “Scripts defined in your composer.json file will not run when Composer can use a cached result.” To optimize deployment time App Engine will use cached data. The caching mechanic unfortunately seems to cache our partial folders from earlier and use those when installing dependencies. The end result is we again have no actual plugins installed. To correct this we need to tell it to not use this cache when we deploy using the –no-cache option. We will unfortunately lose the deployment speed boost from the cached dependencies, but in return composer install will run as expected.

gcloud app deploy --no-cache

Now finally all of our WordPress plugins will get installed and the static files served from App Engine.

Depending on your situation you may need to run more complex pre-install-cmd scripts, or add private repositories in composer to pull in larger custom code as needed. But you should now be able to keep your WordPress deployment to App Engine under the 10,000 file limit.