Skip to content

WordPress Customization

Eric Mann edited this page Mar 19, 2026 · 2 revisions

WordPress Customization Guide

This guide covers how to customize WordPress deployments with Displace, including adding custom themes, plugins, and using the hot-reload development workflow.

Overview

flowchart TB
    subgraph local["Local Development"]
        themes_local["themes/"]
        plugins_local["plugins/"]
        compose["docker-compose"]
        browser["Browser"]
    end

    subgraph production["Production Build"]
        dockerfile["Dockerfile"]
        image["Container Image"]
        k8s["Kubernetes Pod"]
    end

    themes_local -->|"Hot-reload<br/>(volume mount)"| compose
    plugins_local -->|"Hot-reload<br/>(volume mount)"| compose
    compose --> browser

    themes_local -->|"COPY into image"| dockerfile
    plugins_local -->|"COPY into image"| dockerfile
    dockerfile --> image
    image --> k8s
Loading

Two Development Modes:

  • Hot-reload (Development): Edit files locally, see changes instantly
  • Baked-in (Production): Themes and plugins are built into the container image

Project Structure

After running displace project init wordpress, you'll have:

my-wordpress-site/
├── themes/                 # Custom WordPress themes
│   └── .gitkeep
├── plugins/                # Custom WordPress plugins
│   └── .gitkeep
├── mu-plugins/             # Must-use plugins (auto-activated)
│   └── .gitkeep
├── uploads/                # Persistent uploads (PVC in production)
│   └── .gitkeep
├── docker/                 # Container configuration
│   ├── nginx.conf
│   ├── supervisord.conf
│   └── wp-config.php
├── manifests/              # Kubernetes manifests
├── Dockerfile              # Multi-stage build
├── docker-compose.yaml     # Local development
├── Makefile                # Build and deploy commands
├── .credentials            # Generated passwords (git-ignored)
└── README.md

Adding Custom Themes

Step 1: Create or Copy Your Theme

# Option A: Create a new theme from scratch
mkdir -p themes/my-theme
touch themes/my-theme/style.css
touch themes/my-theme/index.php
touch themes/my-theme/functions.php

# Option B: Copy an existing theme
cp -r /path/to/my-theme themes/

# Option C: Download a theme
cd themes
curl -O https://example.com/theme.zip
unzip theme.zip && rm theme.zip

Theme Structure Requirements

At minimum, a WordPress theme needs:

themes/my-theme/
├── style.css           # Required: Theme metadata
├── index.php           # Required: Main template
├── functions.php       # Optional: Theme functions
├── header.php          # Optional: Header template
├── footer.php          # Optional: Footer template
├── screenshot.png      # Optional: Theme preview image
└── ...

style.css header (required):

/*
Theme Name: My Custom Theme
Theme URI: https://example.com/my-theme
Author: Your Name
Author URI: https://example.com
Description: A custom WordPress theme
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 8.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: my-theme
*/

Adding Custom Plugins

Step 1: Create or Copy Your Plugin

# Option A: Create a new plugin
mkdir -p plugins/my-plugin
touch plugins/my-plugin/my-plugin.php

# Option B: Copy an existing plugin
cp -r /path/to/my-plugin plugins/

# Option C: Download from WordPress.org
cd plugins
curl -O https://downloads.wordpress.org/plugin/plugin-name.zip
unzip plugin-name.zip && rm plugin-name.zip

Plugin Structure

A minimal plugin:

plugins/my-plugin/
├── my-plugin.php       # Main plugin file with header
├── includes/           # Optional: PHP includes
├── assets/             # Optional: CSS/JS/images
└── readme.txt          # Optional: Plugin readme

Plugin header (required in main file):

<?php
/**
 * Plugin Name: My Custom Plugin
 * Plugin URI: https://example.com/my-plugin
 * Description: A custom WordPress plugin
 * Version: 1.0.0
 * Requires at least: 6.0
 * Requires PHP: 8.0
 * Author: Your Name
 * Author URI: https://example.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: my-plugin
 */

// Your plugin code here

Must-Use Plugins (mu-plugins)

Must-use plugins are automatically activated and cannot be deactivated via the admin:

# Add a must-use plugin
cp my-critical-plugin.php mu-plugins/

Use mu-plugins for:

  • Critical functionality that shouldn't be disabled
  • Performance optimizations
  • Security hardening
  • Custom site configurations

Dev Mode Hot Reload Workflow

This is the primary development workflow. Edit files locally, see changes instantly in your browser.

Step 1: Deploy Stock WordPress

# Create project
mkdir my-wordpress-site && cd my-wordpress-site
displace project init wordpress

# Deploy to local cluster
make deploy

Expected output:

Building WordPress Docker image...
  PHP Version: 8.3
  WordPress Version: 6.9.4
  Image: my-wordpress-site:latest
[+] Building 45.2s (12/12) FINISHED
✓ Build completed!

Deploying WordPress to Kubernetes...
  ✓ namespace/my-wordpress-site created
  ✓ secret/database-secret created
  ...
Deployment completed!

Step 2: Add a Custom Theme

# Create custom theme
mkdir -p themes/developer-theme

# Create style.css
cat > themes/developer-theme/style.css << 'EOF'
/*
Theme Name: Developer Theme
Description: A theme for development
Version: 1.0.0
*/
body {
    font-family: system-ui, sans-serif;
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
}
EOF

# Create index.php
cat > themes/developer-theme/index.php << 'EOF'
<?php get_header(); ?>
<main>
    <?php if (have_posts()): while (have_posts()): the_post(); ?>
        <article>
            <h2><?php the_title(); ?></h2>
            <?php the_content(); ?>
        </article>
    <?php endwhile; endif; ?>
</main>
<?php get_footer(); ?>
EOF

# Create header.php
cat > themes/developer-theme/header.php << 'EOF'
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<header>
    <h1><?php bloginfo('name'); ?></h1>
</header>
EOF

# Create footer.php
cat > themes/developer-theme/footer.php << 'EOF'
<footer>
    <p>&copy; <?php echo date('Y'); ?> <?php bloginfo('name'); ?></p>
</footer>
<?php wp_footer(); ?>
</body>
</html>
EOF

Step 3: Enable Dev Mode for Hot Reload

make dev

Expected output:

Starting development environment...
Themes and plugins are mounted for hot-reload

Development environment started!

Access:
  WordPress: http://localhost:8080
  Admin: http://localhost:8080/wp-admin

Hot-reload directories:
  themes/     - Edit themes, changes appear immediately
  plugins/    - Edit plugins, changes appear immediately
  mu-plugins/ - Edit must-use plugins

Commands:
  make dev-logs  - View container logs
  make dev-down  - Stop development environment

Step 4: Develop with Live Preview

sequenceDiagram
    participant Editor as Code Editor
    participant FS as File System
    participant Docker as Docker Container
    participant Browser as Browser

    Editor->>FS: Save file
    FS->>Docker: Volume mount updates
    Browser->>Docker: Refresh page
    Docker->>Browser: Serve updated content
    Note over Editor,Browser: Changes visible immediately!
Loading
  1. Open your browser: http://localhost:8080/wp-admin
  2. Log in with credentials from .credentials
  3. Activate your theme: Appearance → Themes → Developer Theme
  4. Edit theme files in your editor
  5. Refresh browser to see changes instantly

When you edit a file:

# Edit the theme
vim themes/developer-theme/style.css

Changes appear immediately in your browser - no rebuild needed!

Step 5: View Dev Mode Logs (Optional)

# In another terminal
make dev-logs

Expected output:

wordpress-1  | [php-fpm] GET /wp-admin/ 200
wordpress-1  | [php-fpm] GET /wp-content/themes/developer-theme/style.css 200
mariadb-1    | Version: '11.5.2-MariaDB'  socket: '/run/mysqld/mysqld.sock'

Step 6: Stop Dev Mode

make dev-down

Production Deployment Workflow

When your theme is ready, deploy to production with the theme baked into the image.

flowchart LR
    themes["themes/"]
    plugins["plugins/"]
    build["make build"]
    image["Docker Image"]
    push["displace registry push"]
    deploy["make deploy"]
    cluster["Production Cluster"]

    themes --> build
    plugins --> build
    build --> image
    image --> push
    push --> deploy
    deploy --> cluster
Loading

Step 1: Build Production Image

make build

Expected output:

Building WordPress Docker image...
  PHP Version: 8.3
  WordPress Version: 6.9.4
  Image: my-wordpress-site:latest

[+] Building 52.3s (15/15) FINISHED
 => [wordpress-source] Downloading WordPress 6.9.4...
 => [production] Installing PHP extensions...
 => [production] COPY themes/ /var/www/html/wp-content/themes/
 => [production] COPY plugins/ /var/www/html/wp-content/plugins/
 => [production] COPY mu-plugins/ /var/www/html/wp-content/mu-plugins/

✓ Build completed!

Your themes and plugins are now baked into the image.

Step 2: Push to Registry (For Remote Clusters)

# Push to cluster registry
displace registry push my-wordpress-site:latest

Expected output:

Pushing my-wordpress-site to registry: k3d-displace-registry
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Tagging image for localhost:5000...

✓ Successfully pushed my-wordpress-site

Image available at:
  From host:    localhost:5000/my-wordpress-site:latest
  From cluster: k3d-displace-registry:5000/my-wordpress-site:latest

Step 3: Deploy to Cluster

# Deploy to local cluster
make deploy

# Or deploy to production
displace project deploy --cluster production

Expected output:

Deploying WordPress to Kubernetes...
  ✓ namespace/my-wordpress-site created
  ✓ secret/database-secret created
  ✓ persistentvolumeclaim/mariadb-pvc created
  ✓ deployment/mariadb created
  ✓ service/mariadb-service created
  ✓ deployment/wordpress created
  ✓ service/wordpress-service created
  ✓ ingress/wordpress-ingress created

Deployment completed!

Access your site:
  Local access: Run 'make port-forward' then visit http://localhost:8080
  Admin panel: http://localhost:8080/wp-admin

Version Selection

Specifying PHP Version

Edit .credentials or set environment variables:

# In .credentials
PHP_VERSION=8.2

# Or as environment variable
PHP_VERSION=8.2 make build

Supported PHP versions:

  • 8.3 (default, recommended)
  • 8.2
  • 8.1

Specifying WordPress Version

# In .credentials
WORDPRESS_VERSION=6.6.2

# Or as environment variable
WORDPRESS_VERSION=6.6.2 make build

Check available versions:

curl -s https://api.wordpress.org/core/version-check/1.7/ | jq '.offers[].version'

Compatibility Considerations

WordPress Version Minimum PHP Recommended PHP
6.7.x 7.4 8.2+
6.6.x 7.4 8.2+
6.5.x 7.4 8.1+
6.4.x 7.4 8.1+

Check your plugin requirements before selecting PHP version:

// In your plugin header
* Requires PHP: 8.0

Complete Example Walkthrough

Here's a complete workflow from start to finish:

1. Initialize WordPress Project

mkdir my-blog && cd my-blog
displace project init wordpress

2. Deploy Stock WordPress

make deploy

3. Create Custom Theme

mkdir -p themes/minimalist

cat > themes/minimalist/style.css << 'EOF'
/*
Theme Name: Minimalist
Description: A clean, minimalist theme
Version: 1.0.0
*/

* { box-sizing: border-box; }
body {
    font-family: Georgia, serif;
    line-height: 1.8;
    max-width: 700px;
    margin: 0 auto;
    padding: 2rem;
    color: #333;
}
h1, h2, h3 { font-family: system-ui, sans-serif; }
a { color: #0066cc; }
article { margin-bottom: 3rem; }
EOF

cat > themes/minimalist/index.php << 'EOF'
<?php get_header(); ?>
<main>
<?php if (have_posts()): while (have_posts()): the_post(); ?>
    <article>
        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
        <time><?php the_date(); ?></time>
        <?php the_excerpt(); ?>
    </article>
<?php endwhile; endif; ?>
</main>
<?php get_footer(); ?>
EOF

cat > themes/minimalist/header.php << 'EOF'
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <?php wp_head(); ?>
</head>
<body>
<header>
    <h1><a href="<?php echo home_url(); ?>"><?php bloginfo('name'); ?></a></h1>
    <p><?php bloginfo('description'); ?></p>
</header>
EOF

cat > themes/minimalist/footer.php << 'EOF'
<footer>
    <p>Powered by WordPress</p>
</footer>
<?php wp_footer(); ?>
</body>
</html>
EOF

4. Enable Dev Mode for Hot Reload

make dev

5. Develop Theme with Live Preview

  1. Open http://localhost:8080/wp-admin
  2. Log in (credentials in .credentials)
  3. Go to Appearance → Themes
  4. Activate "Minimalist" theme
  5. Open http://localhost:8080 in another tab
  6. Edit themes/minimalist/style.css
  7. Refresh browser - changes appear immediately!

6. Build and Deploy to Production

When ready:

# Stop dev mode
make dev-down

# Build production image with theme baked in
make build

# Deploy to cluster
make deploy

7. Verify Production Deployment

# Check status
make status

# View in browser
make port-forward
# Visit http://localhost:8080

Troubleshooting

Theme Not Appearing in Admin

Check theme directory structure:

ls -la themes/my-theme/
# Must have style.css and index.php

Verify style.css has valid header:

head -20 themes/my-theme/style.css
# Must have "Theme Name:" line

Changes Not Showing in Dev Mode

Verify volumes are mounted:

docker compose ps
# Should show "Up" status

docker compose exec wordpress ls -la /var/www/html/wp-content/themes/
# Should show your theme

Clear WordPress cache:

# In wp-admin: Settings → Permalinks → Save (to flush rewrite rules)
# Or use WP-CLI if installed:
docker compose exec wordpress wp cache flush

Plugin Not Activating

Check plugin header:

head -20 plugins/my-plugin/my-plugin.php
# Must have "Plugin Name:" line

Check PHP errors:

make dev-logs | grep -i error

Build Fails

Check Dockerfile syntax:

docker build --no-cache -t test .

Verify file permissions:

ls -la themes/
ls -la plugins/
# Files should be readable

Best Practices

Version Control

# Add to .gitignore (already done by template)
.credentials
uploads/

# Commit your themes and plugins
git add themes/ plugins/
git commit -m "Add custom theme and plugins"

Development Workflow

  1. Always start with make dev for development
  2. Test locally before building production image
  3. Use make build only when ready to deploy
  4. Tag releases with version numbers

Security

  • Never commit .credentials file
  • Use strong passwords (auto-generated by template)
  • Keep WordPress and plugins updated
  • Review third-party plugins before using

Related Documentation

Clone this wiki locally