{"id":5,"date":"2020-06-20T18:31:00","date_gmt":"2020-06-20T18:31:00","guid":{"rendered":"http:\/\/localhost:8000\/?p=5"},"modified":"2021-02-03T09:39:00","modified_gmt":"2021-02-03T09:39:00","slug":"wordpress-as-a-modern-12-factor-app","status":"publish","type":"post","link":"http:\/\/localhost:8000\/2020\/06\/20\/wordpress-as-a-modern-12-factor-app\/","title":{"rendered":"WordPress as a 12-factor app"},"content":{"rendered":"\n
PHP was the first programming (scripting, yes, I know) language that I learned. I had some experience using and managing WordPress, I also liked its features, and I knew that all my needs would already be covered by some plugin of the WordPress ecosystem.<\/p>\n\n\n\n
Still, I almost didn\u2019t use WordPress to power my blog. I wanted my blog to be versioned (in Git), packaged in a Docker container, and cloud ready. Some people might call it over-engineering, but I think it\u2019s just good practice.<\/p>\n\n\n\n
I didn\u2019t like how WordPress is structured, how installing plugins and themes is handled. Everything in the same folder, plugins are installed by clicking, there is no way of committing your code to git, making multiple changes, and then deploying everything to production later – without breaking the site. Or so I thought!<\/p>\n\n\n\n
Fortunately, WordPress\u2019 Core supports customization of the structure of your project, which makes it work with PHP\u2019s dependency management system – Composer. There are multiple approaches to it, but I\u2019ve decided to use Bedrock<\/a>, because it nicely follows the principles of the 12-factor app<\/a>.<\/p>\n\n\n\n Let\u2019s start!<\/p>\n\n\n\n WordPress requires<\/a>:<\/p>\n\n\n\n Additionally, it is recommended to run PHP behind an Apache<\/a> or nginx<\/a> HTTP server.<\/p>\n\n\n\n We will use PHP 7.3 with Apache packaged in a Docker image, and another image for MySQL.<\/p>\n\n\n\n I was surprised to see that PHP 7.3.11 is already included in the macOS Catalina (10.15.4). I decided to run the development database in Docker<\/a> (btw: I\u2019m a huge Docker fan).<\/p>\n\n\n\n All I had to install to begin with development was Composer by running Before we begin, let\u2019s verify that we have the right PHP version installed by running: Let\u2019s create a new WordPress project based on roots\/bedrock in the current folder:<\/p>\n\n\n\n Let\u2019s spin up a MySQL 5.6 server with Docker:<\/p>\n\n\n\n NOTE: For production purposes, a non-root database user should be used.<\/p>\n\n\n\n If you wish to persist data on local disk add the following flag Edit .env file to connect to the created database, configure URLs and generate salts:<\/p>\n\n\n\n NOTE: It is important to set 127.0.0.1 as database host (DB_HOST) instead of localhost, because PHP\u2019s MySQL library would try to connect to the mysql.socket instead of making a TCP connection to localhost.<\/p>\n\n\n\n Run the project with PHP\u2019s built-in web server. I recommend executing this command in another Terminal\/tab so you can execute other commands, but note that your working directory must be the project folder:<\/p>\n\n\n\n Now, open http:\/\/localhost:8000<\/a> in browser.<\/p>\n\n\n\n This will redirect us to WordPress Installation process, where we select a language, and enter the desired administrator username, password and email along with the site URL. Installation is now complete.<\/p>\n\n\n\n Plugins and themes can be easily installed using the WordPress Packagist<\/a> repository. Simply run one of the following commands with the correct plugin\/theme name:<\/p>\n\n\n\n We will install two plugins. Jetpack<\/a> plugin to secure the blog, and Akismet<\/a> plugin to defend against spam<\/a>.<\/p>\n\n\n\n We still need to manually activate the plugin (or theme) in administration panel. It would be great if plugins could also be activated and configured via files, but a line has to be drawn somewhere and we have to be thankful to the WordPress community for getting us this far. \ud83d\ude42<\/p>\n\n\n\n So far, we have set up WordPress with Composer locally, and installed plugins with a dependency manager. In the next part, we will build a Docker image so our blog will be ready to be deployed to the cloud.<\/p>\n\n\n\n Let’s create a file called Dockerfile:<\/p>\n\n\n\n Before building the Docker image, we need to edit the .env file to change the database hostname. <\/p>\n\n\n\n Let’s build a Docker image with the following command:<\/p>\n\n\n\n Finally, let’s run the image with:<\/p>\n\n\n\n WordPress should now be available at: http:\/\/localhost\/<\/a>. The created Docker image is now ready to be pushed to a repository, and then it can be deployed to the cloud.<\/p>\n\n\n\n NOTE: If you want a smaller image, take a look at webdevops\/php-apache<\/a>.<\/p>\n\n\n\n For easier testing and development, we can use Docker Compose. Create a docker-compose.yaml file:<\/p>\n\n\n\n and run the project with:<\/p>\n\n\n\n In this blog post, we have created a new WordPress project based on Bedrock WordPress and packaged it in a Docker image, which is ready to be deployed to the cloud, and conforms more to the definition of a 12-factor app<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":" PHP was the first programming (scripting, yes, I know) language that I learned. I had some experience using and managing WordPress, I also liked its features, and I knew that all my needs would already be covered by some plugin of the WordPress ecosystem. Still, I almost didn\u2019t use WordPress to power my blog. I […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[8,2],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/posts\/5"}],"collection":[{"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/comments?post=5"}],"version-history":[{"count":26,"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/posts\/5\/revisions"}],"predecessor-version":[{"id":75,"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/posts\/5\/revisions\/75"}],"wp:attachment":[{"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/media?parent=5"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/categories?post=5"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/localhost:8000\/wp-json\/wp\/v2\/tags?post=5"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}homebrew install composer<\/code>.<\/p>\n\n\n\n
php -v<\/code>. And verify that Composer is installed:
composer -V<\/code>.<\/p>\n\n\n\n
composer create-project roots\/bedrock .<\/code><\/pre>\n\n\n\n
docker run \\\n --name mysql-blog \\\n -e MYSQL_ROOT_PASSWORD=my-secret-pw \\\n -e MYSQL_DATABASE=wp-blog \\\n -p 3306:3306 \\\n -d \\\n mysql:5.6<\/code><\/pre>\n\n\n\n
-v \/absolute\/path\/to\/mysql-data:\/var\/lib\/mysql<\/code> to the command above.<\/p>\n\n\n\n
DB_HOST='127.0.0.1'\nDB_NAME='wp-blog'\nDB_USER='root'\nDB_PASSWORD='my-secret-pw'\n\u2026\nWP_ENV='development'\nWP_HOME='http:\/\/my.blog'\nWP_SITEURL=\"${WP_HOME}\/wp\"\nWP_DEBUG_LOG=..\/debug.log<\/code><\/pre>\n\n\n\n
php -S localhost:8000 -t web<\/code><\/pre>\n\n\n\n
composer require wpackagist-plugin\/<plugin name>\ncomposer require wpackagist-theme\/<theme name>:*<\/code><\/pre>\n\n\n\n
composer require wpackagist-plugin\/jetpack\ncomposer require wpackagist-plugin\/akismet<\/code><\/pre>\n\n\n\n
FROM php:7.3-apache\n\nEXPOSE 80\n\n# Fix \"Fatal error: Uncaught Error: Call to undefined function mysql_connect() in \/var\/www\/wordpress\/web\/wp\/wp-includes\/wp-db.php\"\nRUN apt-get update && \\\n apt-get install -y libzip-dev zlib1g-dev && \\\n docker-php-ext-install zip mysqli pdo pdo_mysql && \\\n a2enmod rewrite\n\nENV WP_BASE_DIR=\/var\/www\/wordpress \\\n APACHE_DOCUMENT_ROOT=\/var\/www\/wordpress\/web\n\nRUN sed -ri -e 's!\/var\/www\/html!${APACHE_DOCUMENT_ROOT}!g' \/etc\/apache2\/sites-available\/*.conf && \\\n sed -ri -e 's!\/var\/www\/!${WP_BASE_DIR}!g' \/etc\/apache2\/apache2.conf \/etc\/apache2\/conf-available\/*.conf\n\nADD . $WP_BASE_DIR<\/code><\/pre>\n\n\n\n
docker build -t my-blog .<\/code><\/pre>\n\n\n\n
docker run --name my-blog -p 80:80 my-blog<\/code><\/pre>\n\n\n\n
version: '2'\n\nnetworks:\n blog-network:\n driver: bridge\n\nservices:\n db:\n image: mysql:5.6\n networks:\n - blog-network\n ports:\n - 3306:3306\n volumes:\n - \/your\/path\/to\/blog\/mysql-data:\/var\/lib\/mysql\n environment:\n - MYSQL_ROOT_PASSWORD=my-secret-pw\n - MYSQL_DATABASE=wp-blog\n wordpress:\n image: wordpress\n networks:\n - blog-network\n ports:\n - 80:80\n depends_on:\n - db\n environment:\n DATABASE_URL: 'mysql:\/\/root:my-secret-pw@db:3306\/wp-blog'\n WP_ENV: 'development'\n WP_HOME: 'http:\/\/localhost'\n WP_SITEURL: \"http:\/\/localhost\/wp\"\n WP_DEBUG_LOG: '..\/debug.log'<\/code><\/pre>\n\n\n\n
docker-compose up -d<\/code><\/pre>\n\n\n\n