Local WordPress stack (Docker Compose) with wp-cli auto-install and GCA Intranet themes.
This repo also includes an AWS-friendly WordPress container build for deployment to EC2 / ECS-style environments.
- WordPress runs from the official WordPress Docker image
- WordPress core is not committed to Git
Custom themes and plugins live under:
wp-content/themes/gca-intranet-foundation(parent theme)wp-content/themes/gca-intranet(child theme)wp-content/plugins/gca-custom
Note: The entire local ./wp-content folder is bind-mounted to the container locally. If you install a new plugin via the WordPress Admin GUI, it will sync to your local machine so it can be committed to Git.
- MySQL 8.0 for local development (via Docker Compose)
A wp-cli container handles the one-time WordPress setup:
- Creates
wp-config.php - Installs WordPress
- Activates the GCA theme
- Sets permalinks
A Dockerfile is included for AWS container platforms.
In AWS you would typically use RDS MySQL.
DB credentials should be provided via environment variables or secrets.
- Docker Desktop (or Docker Engine) installed and running
- Git
- No local PHP, Node.js, or npm required (Docker handles all dependencies).
Copy the example file and adjust values if needed:
cp .env.example .envCommon values you may want to change:
WP_URLWP_PORT- Admin username / password
- Important: Ensure
WP_HOMEandWP_SITEURLexplicitly match your URL and port (e.g.,http://localhost:8090) to prevent WordPress redirect loops.
If you're on local you need to pass in the compose file to use: docker-compose.local.yml.
LOCAL build:
docker compose -f docker-compose.local.yml up -d --buildSERVER build:
docker compose --env-file .env up -d --build(Optional) Confirm services are running:
docker compose psNote: the first run can take a short while while MySQL initialises.
Run the wp-cli bootstrap container. This is safe to re-run — it will skip steps if WordPress is already installed.
docker compose --env-file .env run --rm wpcliThis will:
- Ensure WordPress core exists
- Create
wp-config.php(if missing) - Install WordPress
- Activate the GCA theme
- Set permalink structure
- Site:
http://localhost:8080(or whateverWP_PORTis set to) - Admin:
http://localhost:8080/wp-admin
Admin credentials are defined in .env:
WP_ADMIN_USERWP_ADMIN_PASSWORD
If things get into a bad state, do a full reset:
docker compose down -v
docker compose --env-file .env up -d --build
docker compose --env-file .env run --rm wpcliThis removes all local data and re-initialises WordPress.
The gca-feature-flags plugin provides a lightweight feature-flag system for toggling functionality without code deploys.
- Flags are registered in code (in the theme) and toggled via the WordPress Admin UI.
- The Admin UI lives at WP Admin → Settings → Feature Flags.
- Each flag has a label, optional description, optional tags, and a default on/off state.
- Flags that have never been saved in the admin fall back to their registered
default.
wp-content/plugins/gca-feature-flags/gca-feature-flags.php
This plugin is committed to Git and activated alongside the theme.
Each feature lives in its own file under wp-content/themes/gca-intranet/inc/features/. All files in that directory are automatically loaded by inc/features.php.
Create a new file, e.g. inc/features/my-feature.php:
gca_register_feature_flag('my-feature', [
'label' => 'My Feature',
'description' => 'Short description of what this controls.',
'default' => false, // on or off by default
'tags' => ['ui', 'beta'], // optional — used to filter flags in the admin UI
]);The flag slug (my-feature) must be a unique, URL-safe string. It is used as the key for checking and storing the flag's state.
Call gca_flag_enabled() anywhere in template files, theme PHP, or plugins:
if (gca_flag_enabled('my-feature')) {
// render the new feature
}- Go to WP Admin → Settings → Feature Flags.
- Use the toggle switch next to the flag you want to change.
- Click Save Changes — changes take effect immediately.
The admin page supports search and tag filtering to quickly find flags in large lists.
The built-in environment-banner flag (inc/features/environment-banner.php) is a working example:
gca_register_feature_flag('environment-banner', [
'label' => 'Environment Banner',
'description' => 'Shows a yellow banner at the top of every page indicating the current environment. Never shown in production.',
'default' => false,
'tags' => ['ui', 'environment'],
]);
add_action('wp_body_open', function (): void {
if (!gca_flag_enabled('environment-banner')) {
return;
}
// ... render the banner
});| Function | Description |
|---|---|
gca_register_feature_flag($id, $args) |
Register a flag (call before any is_enabled check) |
gca_flag_enabled($id) |
Returns true if the flag is enabled, false otherwise |
Each feature file lives in wp-content/themes/gca-intranet-foundation/inc/features/.
| Flag ID | Label | Description | Cron job |
|---|---|---|---|
environment-banner |
Environment Banner | Shows a yellow banner at the top of every page indicating the current environment. Never shown in production. | — |
cron-manager |
Cron Manager | Admin interface (Tools → Cron Jobs) to view, create, edit, delete, and manually run WordPress cron jobs. | — |
workday-user-sync |
Workday User Sync | Syncs WordPress users with the Workday staff list API. Schedule via Tools → Cron Jobs or run manually with wp gca sync-users. |
gca_sync_workday_users |
google-profile-picture |
Google Profile Picture Sync | Downloads the user's Google profile picture on each SSO login and uses it as their avatar across the site. Generated letter-avatars (Google's default when no real photo is set) are automatically discarded using GD colour analysis — only genuine photographs are stored. Run wp gca sync-profile-pictures to retroactively clean up any letter-avatars stored before this check was added. Requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env. |
— |
purge-events |
Purge Events | Archives event posts more than one month after their end date (or start date if no end date is set) by moving them to the gca_archived post status. Archived events are hidden from the frontend but remain recoverable in the admin. Schedule via Tools → Cron Jobs or run manually with wp gca purge-events. |
gca_purge_events |
author-selector |
Author Selector | Replaces the default WordPress author meta box on blog and work_update posts with a searchable Select2 dropdown. Each option shows the user's profile image (Google SSO photo → Gravatar → WP default). All users are pre-loaded as inline JSON — no AJAX required. |
— |
The purge-events feature archives rather than hard-deletes old event posts. Key details:
- Custom post status —
register_archived_status()registersgca_archivedon the WordPressinithook withpublic: falseandexclude_from_search: true, so archived events are invisible on the frontend but visible in the admin under a separate Archived status filter. - Archive instead of delete —
wp_delete_post( $post_id, true )has been replaced withwp_update_post( ['ID' => $post_id, 'post_status' => 'gca_archived'] ), making the operation fully reversible. - No double-processing — the query that selects events to evaluate is scoped to
['publish', 'private', 'draft'], so events already ingca_archivedare naturally excluded from subsequent cron runs. - Stats & logs — all log messages and the stats array use
archived(wasdeleted) to reflect the new behaviour.
- Themes live directly in this repo under
wp-content/themes/ - No symlinks are used
- Changes to themes should be committed to Git
- WordPress core is provided entirely by Docker
If your theme uses an npm build step for CSS/JS, use the built-in Docker container to compile assets (no local Node installation required).
To install dependencies and build once:
docker compose -f docker-compose.local.yml run --rm theme-builderTo watch for file changes during development:
docker compose -f docker-compose.local.yml run --rm theme-builder npm run watchWhether compiled assets should be committed depends on repo convention. Follow existing patterns in this repo.
Use these checks when validating header/footer changes.
-
Desktop (≥992px)
- Logo sits in the left gutter (outside the boxed container alignment)
- Boxed area: row 1 = utility links + search, row 2 = primary nav
- Dropdown chevrons appear to the right of nav items (outlined “V”)
-
Mobile (<992px)
- Row 1: logo left, utility links top-right (stacked)
- Row 2: search left, menu toggle right
- Keyboard: Tab through skip link → utility → search → nav; focus visible throughout
- Mobile menu toggle updates
aria-expanded - Dropdowns open via click and keyboard (Enter/Space)
- Set
WP_URLto the EC2 public IP or DNS name
e.g.http://<public-ip>orhttps://intranet.example.gov.uk - Set
WP_PORT=80if exposing WordPress directly
In production:
- Use RDS MySQL
- Do not use the local MySQL container
.envis local only — never commit.env- Use
.env.exampleas the template
Start Docker Desktop and retry.
- Change
WP_PORTin.env - Restart containers
WordPress relies on absolute URLs. Ensure WP_HOME and WP_SITEURL in your .env file explicitly include your custom port (e.g., http://localhost:8090). Restart the container and test in an Incognito Window to clear cached browser redirects.
Run the wp-cli bootstrap again:
docker compose --env-file .env run --rm wpcliIf still broken, do a full reset (see above).
Confirm MySQL container is running:
docker compose psCheck DB logs:
docker compose logs db