Why use Bedrock ?
Bedrock is a WordPress boilerplate. It is one of the best out there and it uses Composer to manage dependencies. This will allow us to easily upgrade it in a DevOps way.
Other reasons for using Bedrock:
- Bedrock uses a better folder structure than what you get with your standard WordPress installation.
- It uses WordPress Core functionality for customizations so there’s no risk of incompatibility with 3rd party plugins or themes (unless they don’t follow best practices).
- The Bedrock folder structure allows for better and easier Git integration.
- It is the most widely used Composer-based WordPress setup.
- It’s been around.
- It brings added security because its folder structure limits access to non-public files and it locks down updates and changes through the AUTOMATIC_UPDATER_DISABLED, DISALLOW_FILE_EDIT and DISALLOW_FILE_MODS configuration settings.
- It uses Dotenv to manage and separate environment specific configurations.
Check out the official documentation for more information.
For our purposes we’re going to deploy Bedrock into a Docker container so it’s easy to deal with during development and to deploy in Kubernetes.
First, install Bedrock (replace mysite with the name you want to give to your installation):
composer create-project roots/bedrock mysite
Cd into mysite and create a docker subdirectory and a docker/Dockerfile that contains the following:
# We are going to use the PHP 8.3 FPM Alpine image. Feel free to adapt to your needs.
FROM php:8.3-fpm-alpine AS bedrockfpm
LABEL maintainer="you@youraddress.com"
# Create a phpfpm:phpfpm user and group.
RUN addgroup -g 1000 phpfpm && adduser -D -u 1000 -G phpfpm phpfpm
# Install some dependencies - feel free to adapt to your needs.
RUN apk add --update git openssh-client libzip-dev freetype-dev libjpeg-turbo-dev libwebp-dev libpng-dev
RUN docker-php-ext-configure gd --with-freetype --with-webp --with-jpeg && \
docker-php-ext-install gd
RUN docker-php-ext-install mysqli zip pdo pdo_mysql && docker-php-ext-enable mysqli && docker-php-ext-enable pdo_mysql
# Install WordPress CLI
RUN curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
&& chmod +x /usr/local/bin/wp
# Install Composer
RUN php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/local/bin/ --filename=composer
# Copy local files to tune configuration (see further)
COPY ./docker/php.ini /usr/local/etc/php/conf.d/custom.ini
COPY ./docker/fpm.conf /usr/local/etc/php-fpm.d/zz-fpm.conf
COPY --chown=1000:1000 ./docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && chown 1000:1000 /var/www
# Create a wwinit directory in which we will put all the files needed by our WordPress instance. We'll copy those during exection of the entrypoint
RUN mkdir /var/wwwinit && chown 1000:1000 /var/wwwinit
# Create the /var/www directory in which we'll install WordPress
WORKDIR /var/www
# Become user phpfpm
USER phpfpm
# Copy local files to tune configuration (see further)
COPY --chown=1000:1000 ./composer.json /var/wwwinit/
COPY --chown=1000:1000 ./docker/wp-cli.yml /var/wwwinit/
COPY --chown=1000:1000 ./docker/config/ /var/wwwinit/config/
COPY --chown=1000:1000 ./docker/web/ /var/wwwinit/web/
# Execute our entrypoint whenever we start this container
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# Ultimately we want to execute php-fpm
CMD ["php-fpm"]
The Docker file mentions a number of files that we need to create or adapt. Starting with docker/php.ini that will be copied to /usr/local/etc/php/conf.d/custom.ini to alter the configuration of PHP:
# Adapt to your needs.
memory_limit = 512M
max_execution_time = 300
max_input_vars = 5000
upload_max_filesize = 1024M
post_max_size = 1024M
We also need a docker/fpm.conf which will be copied to /usr/local/etc/php-fpm.d/zz-fpm.conf and which will alter the configuration of FPM:
# Adapt to your needs.
[www]
pm = static
pm.max_children = 10
And we need our entrypoint script. Create a file docker/docker-entrypoint.sh. It will be copied to /usr/local/bin/docker-entrypoint.sh and will be executed each time our container starts.
#!/bin/sh
set -e
set -x
cd /var/www
if ! $(wp core is-installed); then
cp -aR /var/wwwinit/* /var/www/
composer install
wp core install --url=$WP_HOME \
--title="$WP_NAME" \
--admin_user=admin \
--admin_email=you@youraddress.com \
--admin_password=$ADMINPASSWORD
# Activate theme
wp theme activate twentytwentyfive
fi
# Execute the CMD in Dockerfile, which in our case is php-fpm
exec "$@"
This entrypoint script detects whether WordPress is already installed or not. If it is it doesn’t do anything and starts php-fpm. If it’s not, it installs Composer dependencies, WordPress and activates a default theme. We need to pass it some environment variables to define the URL, name of the instance and admin password.
Before installing Composer, the entrypoint script copies the contents of the wwwinit directory to /var/www. We need to create a few files that will be copied there upon installation.
The composer.json is already there. It was created when you created the Bedrock project. We’ll see later how we can modify this file to add plugins and other dependencies to our project. The web directory is already there too. It contains WordPress Core as well as wp-config.php file and the app directory structure in which we have plugins, themes, and uploaded media. The wp-config.php is there solely because WordPress needs it there but it actually loads configuration from the config directory. Do not modify wp-config.php. Instead, modify configuration in the config directory.
Move the wp-cli.yml file to the docker subdirectory. It will be copied to the root of our Bedrock installation. This is the configuration of WP CLI. This file tells WP CLI where our WordPress installation is.
Move the config directory that has been created in the root directory of Bedrock during the installation of Bedrock, into the docker directory. The entrypoint script will copy it to the Bedrock installation inside the container. In the docker/config/environments/ directory copy staging.php to production.php so that we have a file ready to tune our production environment.
You should end up with the following directory structure:
./.env.example # A .env example
./.gitignore # .gitignore tailored to our Bedrock file structure
./LICENSE.md # A standard LICENSE file
./README.md # A standard README file
./composer.json # Our Composer requirements
./composer.lock # Our Composer lock file to be committed to Git
./pint.json # Laravel Pint - opinionated PHP code style fixer for minimalists
./wp-cli.yml # WP-CLI configuration
./vendor # Composer dependencies
./docker/Dockerfile # Our Dockerfile
./docker/php.ini # PHP configuration
./docker/fpm.conf # FPM configuration
./docker/docker-entrypoint.sh # Our Docker entrypoint
./docker/wp-cli.yml # WP-CLI configuration (TODO)
./docker/config/application.php # Our base configuration
./docker/config/environments/development.php # Configuration specific to our development environment
./docker/config/environments/staging.php # Configuration specific to our staging environment
./docker/config/environments/production.php # Configuration specific to our production environment
./docker/web/app/mu-plugins # WordPress Must-Use plugins
./docker/web/app/plugins # WordPress plugins
./docker/web/app/themes # WordPress themes
./docker/web/app/uploads # WordPress media
./docker/web/index.php # PHP entrypoint
./docker/web/wp-config.php # Redirect to config/
./docker/web/wp # WordPress Core
Let’s build and run our container:
docker build -t mysite -f docker/Dockerfile .
docker run -d --name mysite --rm -e WP_HOME=http://localhost -e WP_NAME=mysite -e ADMINPASSWORD=password -p 9000:9000 mysite
docker logs -f mysite
You should see the following output:
+ cd /var/www
+ wp core is-installed
Error: This does not seem to be a WordPress installation.
The used path is: /var/www/
Pass --path=`path/to/wordpress` or run `wp core download`.
+
+ cp -aR /var/wwwinit/composer.json /var/wwwinit/config /var/wwwinit/web /var/wwwinit/wp-cli.yml /var/www/
+ composer install
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 18 installs, 0 updates, 0 removals
- Locking composer/installers (v2.3.0)
- Locking graham-campbell/result-type (v1.1.3)
- Locking laravel/pint (v1.21.0)
- Locking oscarotero/env (v2.1.1)
- Locking phpoption/phpoption (1.9.3)
- Locking roave/security-advisories (dev-latest 70eb886)
- Locking roots/bedrock-autoloader (1.0.4)
- Locking roots/bedrock-disallow-indexing (2.0.0)
- Locking roots/wordpress (6.7.2)
- Locking roots/wordpress-core-installer (1.100.0)
- Locking roots/wordpress-no-content (6.7.2)
- Locking roots/wp-config (1.0.0)
- Locking roots/wp-password-bcrypt (1.2.0)
- Locking symfony/polyfill-ctype (v1.31.0)
- Locking symfony/polyfill-mbstring (v1.31.0)
- Locking symfony/polyfill-php80 (v1.31.0)
- Locking vlucas/phpdotenv (v5.6.1)
- Locking wpackagist-theme/twentytwentyfive (1.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 18 installs, 0 updates, 0 removals
- Downloading roots/wordpress-core-installer (1.100.0)
- Downloading composer/installers (v2.3.0)
- Downloading symfony/polyfill-mbstring (v1.31.0)
- Downloading laravel/pint (v1.21.0)
- Downloading symfony/polyfill-ctype (v1.31.0)
- Downloading oscarotero/env (v2.1.1)
- Downloading roots/bedrock-autoloader (1.0.4)
- Downloading roots/bedrock-disallow-indexing (2.0.0)
- Downloading roots/wordpress-no-content (6.7.2)
- Downloading roots/wp-config (1.0.0)
- Downloading roots/wp-password-bcrypt (1.2.0)
- Downloading symfony/polyfill-php80 (v1.31.0)
- Downloading phpoption/phpoption (1.9.3)
- Downloading graham-campbell/result-type (v1.1.3)
- Downloading vlucas/phpdotenv (v5.6.1)
- Downloading wpackagist-theme/twentytwentyfive (1.1)
0/16 [>---------------------------] 0%
3/16 [=====>----------------------] 18%
10/16 [=================>----------] 62%
13/16 [======================>-----] 81%
14/16 [========================>---] 87%
15/16 [==========================>-] 93%
16/16 [============================] 100%
- Installing roots/wordpress-core-installer (1.100.0): Extracting archive
- Installing composer/installers (v2.3.0): Extracting archive
- Installing symfony/polyfill-mbstring (v1.31.0): Extracting archive
- Installing laravel/pint (v1.21.0): Extracting archive
- Installing symfony/polyfill-ctype (v1.31.0): Extracting archive
- Installing oscarotero/env (v2.1.1): Extracting archive
- Installing roave/security-advisories (dev-latest 70eb886)
- Installing roots/bedrock-autoloader (1.0.4): Extracting archive
- Installing roots/bedrock-disallow-indexing (2.0.0): Extracting archive
- Installing roots/wordpress-no-content (6.7.2): Extracting archive
- Installing roots/wordpress (6.7.2)
- Installing roots/wp-config (1.0.0): Extracting archive
- Installing roots/wp-password-bcrypt (1.2.0): Extracting archive
- Installing symfony/polyfill-php80 (v1.31.0): Extracting archive
- Installing phpoption/phpoption (1.9.3): Extracting archive
- Installing graham-campbell/result-type (v1.1.3): Extracting archive
- Installing vlucas/phpdotenv (v5.6.1): Extracting archive
- Installing wpackagist-theme/twentytwentyfive (1.1): Extracting archive
0/14 [>---------------------------] 0%
12/14 [========================>---] 85%
13/14 [==========================>-] 92%
14/14 [============================] 100%
3 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating optimized autoload files
14 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
+ wp core install '--url=http://localhost' '--title=mysite' '--admin_user=admin' '--admin_email=you@youraddress.com' '--admin_password=password'
Error: Error establishing a database connection. This either means that the username and password information in your `wp-config.php` file is incorrect or that contact with the database server at `localhost` could not be established. This could mean your host’s database server is down.
This shows Composer installing its dependencies and wp core install failing because it cannot connect to a database. This is of course normal as we’re missing a MariaDB container. We also wouldn’t be able to test this because the FPM container needs to have a web server in front of it.
In the following chapter, we’ll use docker compose to deploy a complete development stack with nginx, phpmyadmin, and SSL.