Getting Started with the WordPress Chainguard Image

Tutorial on how to get started with the WordPress Chainguard Image

The WordPress Chainguard Image is a container image suitable for creating and running WordPress projects. Designed to work as a drop-in replacement for the official WordPress FPM-Alpine image, the Chainguard WordPress Image features a distroless variant for increased security on production environments. The image is built with the latest PHP and WordPress versions, and includes the necessary PHP extensions to run WordPress.

In this guide, we’ll demonstrate 3 different ways in which you can use the WordPress Chainguard Image to build and run WordPress projects.

Preparation

This tutorial requires Docker to be installed on your local machine. If you don’t have Docker installed, you can download and install it from the official Docker website.

Cloning the Demos Repository

Start by cloning the demos repository to your local machine:

git clone git@github.com:chainguard-dev/edu-images-demos.git

Locate the wordpress demo and cd into its directory:

cd edu-images-demos/php/wordpress

Here you will find three folders, each with a different demo that we’ll cover in this guide.

Example 1: Testing the Image with a Fresh WordPress Install

You can use the latest-dev variant of the Chainguard WordPress Image to create a project from scratch and go through the installation wizard. This method is useful for testing the image and getting familiar with its features, however, changes made to the WordPress installation will not persist unless you set up a volume with proper permissions to share container contents with the host machine. We’ll see how to do that in the next example.

The files for this demo are located in the 01-preview directory. You can access this directory and open the docker-compose.yaml file in your editor of choice to follow along.

Here’s the content of the docker-compose.yaml file from our first demo:

services:
  app:
    image: cgr.dev/chainguard/wordpress:latest-dev
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: mariadb
      WORDPRESS_DB_USER: $WORDPRESS_DB_USER
      WORDPRESS_DB_PASSWORD: $WORDPRESS_DB_PASSWORD
      WORDPRESS_DB_NAME: $WORDPRESS_DB_NAME
    volumes:
      - document-root:/var/www/html

  nginx:
    image: cgr.dev/chainguard/nginx
    restart: unless-stopped
    ports:
      - 8000:8080
    volumes:
      - document-root:/var/www/html
      - ./nginx.conf:/etc/nginx/nginx.conf

  mariadb:
    image: cgr.dev/chainguard/mariadb
    restart: unless-stopped
    environment:
      MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1
      MARIADB_USER: $WORDPRESS_DB_USER
      MARIADB_PASSWORD: $WORDPRESS_DB_PASSWORD
      MARIADB_DATABASE: $WORDPRESS_DB_NAME
    ports:
      - 3306:3306

volumes:
  document-root:

In this Docker Compose example, we define 3 services: app, nginx, and mariadb. Here’s a breakdown of each service:

  • The app service uses the latest-dev variant of the Chainguard WordPress Image, and is configured to connect to the mariadb service. The entrypoint script in the WordPress image looks for environment variables to set up a custom wp-config.php file. The volume document-root defines a volume that will be shared between the app and the nginx services.
  • The nginx service uses the Chainguard nginx Image, and is configured to serve the WordPress application on port 8000.
  • The mariadb service uses the Chainguard MariaDB Image, and is configured with the necessary environment variables to create a database for the WordPress application.

The environment variables used in this example are defined in a .env file located in the same directory as the docker-compose.yaml file. To check for its contents, run:

cat .env
WORDPRESS_DB_HOST=mariadb
WORDPRESS_DB_USER=wp-user
WORDPRESS_DB_PASSWORD=wp-password
WORDPRESS_DB_NAME=wordpress

Although not necessary, you can change these values to suit your needs. Notice this is a hidden file and might not be visible in your file explorer, but you can open it in your terminal using a text editor like nano or vim.

To start the services, run:

docker compose up

If you navigate to http://localhost:8000 in your browser, you should access the WordPress installation page. Follow the on-screen instructions to complete the WordPress setup. Keep in mind that any customizations will be lost once the environment is turned down.

To stop the services, type CTRL+C in the terminal where the services are running, and then run:

docker compose down

This will remove the containers and networks created by the docker compose up command. In the next example, we’ll demonstrate how you can set up a volume with proper permissions to be able to persist customizations such as themes and plugins.

Example 2: Customizing a New WordPress Installation

To persist customizations made to your WordPress site, such as the installation of new themes and plugins, you’ll need to set up a volume with proper permissions in order to keep data between container rebuilds. This requires having a system user in the container with the same UID as your local system user on the host machine. To set this up, we’ll create a custom Dockerfile that adds a wordpress user with the specified UID (set to 1000 by default, which is typically the UID of a regular user on Linux-based systems) to the latest-dev variant of the Chainguard WordPress Image. The Dockerfile also changes default permissions on the /var/www/html directory to allow the wordpress user to write to it.

Navigate to the 02-customizing directory to follow along. This is how the described Dockerfile included in this directory looks:

FROM cgr.dev/chainguard/wordpress:latest-dev
ARG UID=1000

USER root
RUN addgroup wordpress && adduser -SD -u "$UID" -s /bin/bash wordpress wordpress
RUN chown -R wordpress:wordpress /var/www/html

USER wordpress

In the docker-compose.yaml file, we’ll reference the custom Dockerfile and pass the UID as a build argument:

services:
  app:
    image: wordpress-local-dev
    build:
      context: .
      dockerfile: Dockerfile
      args:
        UID: 1000
    user: wordpress
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: mariadb
      WORDPRESS_DB_USER: $WORDPRESS_DB_USER
      WORDPRESS_DB_PASSWORD: $WORDPRESS_DB_PASSWORD
      WORDPRESS_DB_NAME: $WORDPRESS_DB_NAME
    volumes:
      - ./wp-content:/var/www/html/wp-content
      - document-root:/var/www/html

  nginx:
    image: cgr.dev/chainguard/nginx
    restart: unless-stopped
    ports:
      - 8000:8080
    volumes:
      - document-root:/var/www/html
      - ./nginx.conf:/etc/nginx/nginx.conf

  mariadb:
    image: cgr.dev/chainguard/mariadb
    restart: unless-stopped
    environment:
      MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1
      MARIADB_USER: $WORDPRESS_DB_USER
      MARIADB_PASSWORD: $WORDPRESS_DB_PASSWORD
      MARIADB_DATABASE: $WORDPRESS_DB_NAME
    ports:
      - 3306:3306

volumes:
  document-root:

Only the app service has changed in this example. We’ve added a build section that references the custom Dockerfile, and we’ve set the UID build argument to 1000 by default. This can be overwritten at runtime when you call docker compose up. We’ve also added a volume share to persist the contents of the wp-content folder to the host machine.

To build your custom image and pass along your own UID as build argument, run:

docker compose build --build-arg UID=$(id -u) app

You should get output indicating that the image was successfully built. Now you can get your environment up with:

docker compose up

Once the environment is up and running, you can access your WordPress installation from your browser at localhost:8000.

If you go to another terminal window and check the contents of the 02-customizing/wp-content folder, you’ll notice that it was populated with the default WordPress themes and plugins:

❯ ls -la wp-content
total 24
drwxrwxr-x  4 erika erika 4096 Jul 18 21:16 .
drwxrwxr-x  3 erika erika 4096 Jul 18 21:15 ..
-rw-rw-r--  1 erika erika   14 Jul 18 21:05 .gitignore
-rw-r--r--  1 erika 65533   28 Jan  1  1970 index.php
drwxr-xr-x  2 erika 65533 4096 Jul 18 21:16 plugins
drwxr-xr-x 16 erika 65533 4096 Jul 18 21:16 themes

This is only possible because of the custom Dockerfile we created, which added a wordpress user with the same UID as the local user on the host machine.

You can now install new themes and plugins, and they will persist between container rebuilds.

To stop the services, type CTRL+C in the terminal where the services are running, and then run:

docker compose down

In the next example, we’ll see how you can create a distroless WordPress runtime for your production environment.

Example 3: Using the Distroless Variant of the WordPress Image

This demo uses a multi-stage Docker build to create a final distroless image to improve overall security. The distroless image contains the necessary dependencies to run WordPress and won’t allow for new package installations or shell access, reducing the image attack surface.

The main difference here is that we’re calling the entrypoint script at build time instead of run time. This is done to ensure the image is self-contained and doesn’t rely on volumes set up within the host machine in order to work. Any customizations should be included in the wp-content folder that will be copied to the image at build time. Although this increases final image size due to the inclusion of custom content at build time, it limits what can be changed or added to the image once it’s built.

This demo includes a theme (Cue, a simple blogging theme) and a plugin (Imsanity, a popular plugin used to resize images) to demonstrate how to include custom content in the image.

Navigate to the 03-distroless directory to follow along. This is what the Dockerfile included in this directory looks like:

FROM cgr.dev/chainguard/wordpress:latest-dev as builder
#trigger wp-config.php creation
ENV WORDPRESS_DB_HOST=foo

#copy wp-content folder
COPY ./wp-content /usr/src/wordpress/wp-content

#run entrypoint script
RUN /usr/local/bin/docker-entrypoint.sh php-fpm --version

FROM cgr.dev/chainguard/wordpress:latest

COPY --from=builder --chown=php:php /var/www/html /var/www/html

Notice that we’re copying the contents of the local wp-content folder to the /usr/src/wordpress folder in the container. This is the location of the WordPress source files. These will be copied to the document root by the entrypoint script that is executed right afterward. At the builder stage, we’re also setting up a single environment variable to trigger the creation of the wp-config.php file that relies on a set of environment variables to configure database access.

In the docker-compose.yaml file, we reference the custom Dockerfile:

services:
  app:
    image: wordpress-local-distroless
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: mariadb
      WORDPRESS_DB_USER: $WORDPRESS_DB_USER
      WORDPRESS_DB_PASSWORD: $WORDPRESS_DB_PASSWORD
      WORDPRESS_DB_NAME: $WORDPRESS_DB_NAME
      WORDPRESS_CONFIG_EXTRA: |
        # Disable plugin and theme update and installation
        define( 'DISALLOW_FILE_MODS', true );
        # Disable automatic updates
        define( 'AUTOMATIC_UPDATER_DISABLED', true );        
    volumes:
      - document-root:/var/www/html

  nginx:
    image: cgr.dev/chainguard/nginx
    restart: unless-stopped
    ports:
      - 8000:8080
    volumes:
      - document-root:/var/www/html
      - ./nginx.conf:/etc/nginx/nginx.conf

  mariadb:
    image: cgr.dev/chainguard/mariadb
    restart: unless-stopped
    environment:
      MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1
      MARIADB_USER: $WORDPRESS_DB_USER
      MARIADB_PASSWORD: $WORDPRESS_DB_PASSWORD
      MARIADB_DATABASE: $WORDPRESS_DB_NAME
    ports:
      - 3306:3306

volumes:
  document-root:

You can now build and run your environment with:

docker compose up --build

The behavior of this WordPress setup should be similar to the previous examples, but this time, the image is self-contained and doesn’t rely on volumes set up within the host machine to work, in addition to not allowing new package installations or login through a shell. We also set up the WORDPRESS_CONFIG_EXTRA environment variable to disable the installation of new themes and plugins, and to block automatic updates. This increases security by blocking file changes in the container.

To stop the services, type CTRL+C in the terminal where the services are running, and then run:

docker compose down

To keep your WordPress installation up-to-date with latest versions, you can use digestabot, a GitHub Action that works in a similar way to Dependabot, sending a pull request to a repository whenever a new version of a container image is available. This will ensure you’re always running the most recent version of WordPress available in Wolfi.

Advanced Usage

If your project requires a more specific set of packages that aren't included within the general-purpose WordPress Chainguard Image, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of an image other than the wolfi-base image, the image will run as a non-root user. Because of this, if you need to install packages with apk install you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as base for your app. Check the "Using the wolfi-base Image" section of our images quickstart guide for more information.

If the packages you need are not available, you can build your own apks using melange. Please refer to this guide for more information.

Last updated: 2024-07-19 11:07