This post is Deprecated, please refer to the shorter version.
This is the archived version of the fifth post of “Running WordPress on Managed VMs” series. In this series, I have been doing some experiments to run WordPress on App Engine Managed VMs. I think this is a good time to summarize them and show you how to configure WordPress from scratch for running on App Engine flexible environment. This is a long post, and also there are some redundant parts in it, but I’d like to make this post a complete standalone guide for running WordPress on App Engine flexible environment and I’ll update this guide and try to make it shorter overtime, so please bare with me.
In this post, we’ll configure WordPress as follows:
- Leverage App Engine’s autoscaler
- Use Cloud SQL 2nd generation
- Use read-only file system for better security
- Use Google Cloud Storage for media upload
- Use Memcached service for caching pages
Prerequisites
- Create a new Cloud Project at Developers Console
- Enable Billing on that project
- Enable Cloud SQL API
- Create App Engine default bucket at App Engine Settings Page
- Install Google Cloud SDK
Configure Google Cloud SDK
Configure Google Cloud SDK with your account and the project.
$ gcloud auth login ... ... $ gcloud config set project YOUR_PROJECT_ID
Let’s configure the GCS bucket for later use. The default App Engine bucket looks like YOUR_PROJECT_ID.appspot.com. Change the default acl of that bucket as follows:
$ gsutil defacl ch -u AllUsers:R gs://YOUR_PROJECT_ID.appspot.com
Create and configure a Cloud SQL second generation instance
Let’s say we will use wp for various resource names; in particular for the instance name, the database name, and the user name.
You can create a new Cloud SQL second generation instance with the following command:
$ gcloud sql instances create wp \
--activation-policy=ALWAYS \
--tier=db-n1-standard-1
Then change the root password for your instance:
$ gcloud sql instances set-root-password wp \
--password YOUR_INSTANCE_ROOT_PASSWORD # Don't use this password!
To access this MySQL instance, we’ll use Cloud SQL Proxy. Please download an appropriate binary from the download page, make it executable.
If you haven’t created a service account for the project, please create it(Choose a new service account). Download the JSON key file and save it in a secure place.
Run the proxy by the following command:
$ cloud_sql_proxy \
-dir /tmp/cloudsql \
-instances=YOUR_PROJECT_ID:us-central1:wp=tcp:3306 \
-credential_file=PATH_TO_YOUR_SERVICE_ACCOUNT_JSON
Now you can access to the Cloud SQL instance with the normal MySQL client. Please create a new database and a user as follows:
$ mysql -h 127.0.0.1 -u root -p
mysql> create database wp;
mysql> create user 'wp'@'%' identified by 'PASSWORD'; // Don't use this password!
mysql> grant all on wp.* to 'wp'@'%';
mysql> exit
Bye
In the above example, I created a new database wp and a new user wp.
Download and configure WordPress
First create a directory for our project. Let’s say we’ll have wp-mvm directory.
$ mkdir wp-mvm $ cd wp-mvm
Then download the latest WordPress and untar it:
$ wget https://wordpress.org/latest.tar.gz -O /tmp/latest.tar.gz $ tar zxvf /tmp/latest.tar.gz
Now you have wordpress directory in your project.
If you have not install Composer, please install it. Then run the following:
$ composer require google/appengine-php-sdk
Then create the following files.
app.yaml:
runtime: custom vm: true beta_settings: cloud_sql_instances: YOUR_PROJECT:us-central1:wp env_variables: DOCUMENT_ROOT: /app/wordpress
Dockerfile:
# This file will go away once gcloud implements fingerprinting. FROM gcr.io/php-mvm-a/php-nginx:latest
nginx-app.conf:
location / { try_files $uri $uri/ /index.php?q=$uri&$args; } location /_ah/health { return 200 'ok'; }
php.ini:
suhosin.executor.func.blacklist="" extension=bcmath.so zend_extension=opcache.so
Copy the template config file.
$ cp wordpress/wp-config-sample.php wordpress/wp-config.php
Then edit the file, first add the following lines at the top (use actual project id for MY_BUCKET):
// Register GCS stream wrapper set_include_path(__DIR__ . '/../vendor/google/appengine-php-sdk'); require_once(__DIR__ . '/../vendor/autoload.php'); stream_wrapper_register( 'gs', '\google\appengine\ext\cloud_storage_streams\CloudStorageStreamWrapper', 0); // Bucket name for the media upload define('MY_BUCKET', 'PROJECT_ID.appspot.com'); // true on production. define('ON_MVM', filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN)); // Cache settings define('WP_CACHE', ON_MVM); $memcached_servers = array( 'default' => array( getenv('MEMCACHE_PORT_11211_TCP_ADDR') . ':' . getenv('MEMCACHE_PORT_11211_TCP_PORT') ) ); // Auto detect the URL fails. define('WP_HOME', ON_MVM ? 'https://'.$_SERVER['HTTP_HOST'] : 'http://'.$_SERVER['HTTP_HOST']); define('WP_SITEURL', ON_MVM ? 'https://'.$_SERVER['HTTP_HOST'] : 'http://'.$_SERVER['HTTP_HOST']); // Need this for cookies. define('FORCE_SSL_ADMIN', ON_MVM); // Get HTTPS value from the App Engine specific header. $_SERVER['HTTPS'] = ON_MVM ? $_SERVER['HTTP_X_APPENGINE_HTTPS'] : false;
Then the MySQL settings as follows:
/** The name of the database for WordPress */ define('DB_NAME', 'wp'); /** MySQL database username */ define('DB_USER', 'wp'); /** MySQL database password */ define('DB_PASSWORD', 'MYSQL_USER_PASSWORD'); /** MySQL hostname */ define('DB_HOST', ON_MVM ? 'localhost:/cloudsql/PROJECT_ID:us-central1:wp' : '127.0.0.1');
Don’t forget replacing Authentication Unique Keys and Salts. You can generate those Keys and Salts at the following URL:
https://api.wordpress.org/secret-key/1.1/salt/
Phew, configuration is done!
Install plugins
Download Batcache and Memcached into wordpress/wp-content/plugins directory.
Create a new file `wordpress/wp-content/plugins/gcs-media.php` with the following:
<?php
/**
* @package GCS media
* @version 0.1
*/
/*
Plugin Name: GCS media
Plugin URI: https://wp.gaeflex.ninja/
Description: Use Google Cloud Storage for media upload.
Author: Takashi Matsuo
Version: 0.1
Author URI: https://wp.gaeflex.ninja/
License: Apache 2.0
*/
namespace GCS\Media;
defined( 'ABSPATH' ) or die('No direct access!');
add_filter('upload_dir', 'GCS\Media\filter_upload_dir');
function filter_upload_dir( $values ) {
$basedir = 'gs://' . MY_BUCKET;
$baseurl = 'https://storage.googleapis.com/' . MY_BUCKET;
$values = array(
'path' => $basedir . $values['subdir'],
'subdir' => $values['subdir'],
'error' => false,
);
$values['url'] = rtrim($baseurl . $values['subdir'], '/');
$values['basedir'] = $basedir;
$values['baseurl'] = $baseurl;
return $values;
}
Create shell wrapper for deployment/local run
deploy-wrapper.sh:
#!/bin/sh set -x # Temporary copy the dropins cp wordpress/wp-content/plugins/batcache/advanced-cache.php \ wordpress/wp-content/advanced-cache.php cp wordpress/wp-content/plugins/memcached/object-cache.php \ wordpress/wp-content/object-cache.php $@ # Remove the file for local run rm wordpress/wp-content/advanced-cache.php \ wordpress/wp-content/object-cache.php
wp.php:
Copy the content from Run WordPress Locally Using PHP’s Built-In Web Server.
Run WordPress locally and set username and password
Run the following command to run WordPress locally:
$ php -S localhost:8000 -t wordpress wp.php
Then access http://localhost:8000/. Follow the installation steps, create the first user and its password. Login to the Dashboard and update if any of the plugins have update. I’m assuming that the local network is secure here. If you think differently, maybe you can first deploy the app, and access the live site immediately, then create the first username and its password there.
Now it’s ready for the first deployment.
Deployment
Use the shell script wrapper for deployment as follows:
$ gcloud app deploy --promote --stop-previous-version app.yaml
Then access your site, use the username and the password you created locally.
https://PROJECT_ID.appspot.com/
Go to the Dashboard, and in the Plugins page, activate the GCS media plugin. Try uploading a media and confirm the image is uploaded to the GCS bucket.
Check if Batcache is working
In the plugin page, you should see these 2 drop-ins are activated as follows:
To make sure it’s really working, you can open an incognito window and visit the site because the cache plugin only serves from cache to anonymous users. Also you should access the site several times because the plugin only caches pages which are considered popular. You will see the following Batcache stats in the HTML source:
<!-- generated 31 seconds ago generated in 0.165 seconds served from batcache in 0.009 seconds expires in 269 seconds -->
Various workflows
Install/Update plugins/themes
Because the wp-content directory on the server is read-only, you have to do this locally. Run WordPress locally and update plugins/themes in the local Dashboard, then deploy, then activate them in the production Dashboard.
Remove plugins/themes
First Deactivate them in the production Dashboard, then remove them completely locally. The next deployment will remove those files from the production environment.
Update WordPress itself
Most of the case, just download the newest WordPress and overwrite the existing wordpress directory. It is still possible that the existing config files are not compatible with the newest WordPress, so please update the config file manually in that case.
Update the image
We sometimes release the security update for the php-docker image. Then you’ll have to re-deploy your WordPress instance to get the security update.
Conclusion
We walked though how to run WordPress on App Engine flexible environment. Because the flexible environment is securely configured and we’re using Cloud SQL proxy for SQL access, so you can run your WordPress site in a secure manner. It performs well because we’re leveraging Memcached service provided by App Engine. Also thanks to the App Engine autoscaler, Google will automatically spin up new instances when your site is getting lots of traffic.
If you need to run new WordPress site, please give this guide a try and let us know what you think.