From 21bf46882e9478ed837b1bf1be059dd429cc6c40 Mon Sep 17 00:00:00 2001 From: Joe Corall Date: Sun, 28 Jun 2026 12:08:34 +0000 Subject: [PATCH 1/4] Align local compose and image workflow --- .dockerignore | 2 ++ .gitignore | 1 + Dockerfile | 1 - README.md | 52 ++++++++++++++++++++++++++++++------- conf/traefik/wordpress.tmpl | 8 +++--- docker-compose.yaml | 4 +-- scripts/generate-secrets.sh | 9 ++++--- scripts/test.sh | 3 ++- 8 files changed, 59 insertions(+), 21 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3ad5da8..68d7b8a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,8 @@ .git/ .env secrets/ +docker-compose.override.yml +docker-compose.override.yaml vendor/ web/wp/ web/app/uploads/ diff --git a/.gitignore b/.gitignore index da7d7b7..5174c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .env secrets !secrets/.keep +docker-compose.override.yml docker-compose.override.yaml vendor/ web/wp/ diff --git a/Dockerfile b/Dockerfile index eb939cf..e49dcd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,3 @@ -# syntax=docker/dockerfile:1.20.0@sha256:26147acbda4f14c5add9946e2fd2ed543fc402884fd75146bd342a7f6271dc1d ARG BASE_IMAGE=libops/wp:nginx-1.30.3-php84 FROM ${BASE_IMAGE} diff --git a/README.md b/README.md index e0447f1..84a7224 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # WordPress Bedrock Docker Template -LibOps Docker Compose template for running a Composer-managed [Bedrock](https://roots.io/bedrock/) WordPress site with Traefik, MariaDB, and the LibOps WordPress PHP/nginx image. +LibOps Docker Compose template for running a Composer-managed [Bedrock](https://roots.io/bedrock/) WordPress site with Traefik, MariaDB, and the LibOps WordPress PHP/nginx image. Use it with [`sitectl-wp`](https://github.com/libops/sitectl-wp). + +Docs: + +- [Managed application architecture](https://sitectl.libops.io/apps) +- [WordPress sitectl plugin](https://sitectl.libops.io/plugins/wordpress) ## Requirements - [sitectl](https://sitectl.libops.io/install) installed on the host that will run the site. +- [`sitectl-wp`](https://github.com/libops/sitectl-wp) installed for WordPress create, validation, healthcheck, and helper commands. - Docker with the Compose v2 plugin installed on the same host. ## Quick start @@ -22,39 +28,65 @@ sitectl create wp/default \ The site is served through Traefik at `http://localhost`. The first boot runs `wp-cli` automatically. The default admin account is `admin`; its password is generated in `./secrets/WORDPRESS_ADMIN_PASSWORD`. -## Basic operations with sitectl +## Local image build + +The `wp` service builds this checkout on top of the LibOps WordPress base image. The Dockerfile copies Composer lockfiles before local plugins and themes so Docker can reuse dependency layers when only site customizations change. Local builds use the platform selected by the Docker CLI and do not push images. + +## Basic Operations Run these from the generated checkout, or add `--context ` when operating from elsewhere. +Start or update the stack with [`sitectl compose`](https://sitectl.libops.io/commands/compose): + ```bash -# Start or update the Compose stack sitectl compose up --remove-orphans -d +``` + +Check the site and context configuration with [`sitectl healthcheck`](https://sitectl.libops.io/commands/healthcheck) and [`sitectl validate`](https://sitectl.libops.io/commands/validate): -# Check the site and context configuration +```bash sitectl healthcheck sitectl validate +``` + +Update image tags or pin a full image reference with [`sitectl image`](https://sitectl.libops.io/commands/image): -# Update image tags or pin a full image reference +```bash sitectl image set --tag wp=nginx-1.30.3-php84 sitectl image set --image wp=libops/wp:nginx-1.30.3-php84@sha256:... +``` + +Enable local development bind mounts with [`sitectl set`](https://sitectl.libops.io/commands/set), then apply the component change with [`sitectl converge`](https://sitectl.libops.io/commands/converge): -# Enable local development bind mounts +```bash sitectl set dev-mode enabled sitectl converge +``` -# Switch TLS modes +Switch TLS modes with the [Traefik service commands](https://sitectl.libops.io/plugins/traefik): + +```bash sitectl traefik tls mkcert --domain wordpress.localhost sitectl traefik tls letsencrypt --email ops@example.org +``` + +Trust an upstream load balancer or reverse proxy with [`sitectl set`](https://sitectl.libops.io/commands/set), then apply it with [`sitectl converge`](https://sitectl.libops.io/commands/converge): -# Trust an upstream load balancer or reverse proxy +```bash sitectl set reverse-proxy enabled --trusted-ip 203.0.113.10/32 sitectl converge +``` + +Raise upload limits with [`sitectl set`](https://sitectl.libops.io/commands/set), then apply them with [`sitectl converge`](https://sitectl.libops.io/commands/converge): -# Raise upload limits for larger media +```bash sitectl set upload-limits enabled --max-upload-size 2G --upload-timeout 10m sitectl converge +``` + +Run WordPress-specific helpers documented in the [WordPress plugin docs](https://sitectl.libops.io/plugins/wordpress): -# Run WordPress-specific helpers from the plugin +```bash sitectl wp cli plugin list sitectl wp composer sitectl wp db export ./backup.sql diff --git a/conf/traefik/wordpress.tmpl b/conf/traefik/wordpress.tmpl index b0ff2af..86ed2b3 100644 --- a/conf/traefik/wordpress.tmpl +++ b/conf/traefik/wordpress.tmpl @@ -17,7 +17,7 @@ http: regex: '^https?://[^/]+/wp-content/uploads/(.*)' replacement: '/app/uploads/${1}' permanent: true - {{- if eq (env "TRAEFIK_TLS_ENABLED") "true" }} + {{- if eq (env "URI_SCHEME") "https" }} wordpress-https-redirect: redirectScheme: scheme: https @@ -41,7 +41,7 @@ http: middlewares: - wordpress-uploads-redirect service: noop@internal - {{- if eq (env "TRAEFIK_TLS_ENABLED") "true" }} + {{- if eq (env "URI_SCHEME") "https" }} {{- if hasPrefix "www." (env "DOMAIN") }} wordpress-apex-websecure: rule: 'Host(`{{ trimPrefix "www." (env "DOMAIN") }}`)' @@ -68,12 +68,12 @@ http: rule: 'Host(`{{ env "DOMAIN" | default "localhost" }}`)' entryPoints: - web - {{- if eq (env "TRAEFIK_TLS_ENABLED") "true" }} + {{- if eq (env "URI_SCHEME") "https" }} middlewares: - wordpress-https-redirect {{- end }} service: wordpress - {{- if eq (env "TRAEFIK_TLS_ENABLED") "true" }} + {{- if eq (env "URI_SCHEME") "https" }} wordpress-websecure: rule: 'Host(`{{ env "DOMAIN" }}`)' entryPoints: diff --git a/docker-compose.yaml b/docker-compose.yaml index e11ba7e..c3a10b9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -46,9 +46,9 @@ services: - --entrypoints.web.transport.respondingTimeouts.readTimeout=300s environment: DOMAIN: "${DOMAIN:-localhost}" - TRAEFIK_TLS_ENABLED: "false" + URI_SCHEME: "${URI_SCHEME:-http}" ports: - - "${HOST_INSECURE_PORT:-80}:80" + - "80:80" volumes: - ./conf/traefik/wordpress.tmpl:/etc/traefik/dynamic/wordpress.yml:ro depends_on: diff --git a/scripts/generate-secrets.sh b/scripts/generate-secrets.sh index 7aa6fd1..fa8ef78 100755 --- a/scripts/generate-secrets.sh +++ b/scripts/generate-secrets.sh @@ -17,8 +17,11 @@ yq -r '.secrets[].file' docker-compose.yaml | uniq | while read -r SECRET; do fi done -if [ -f docker-compose.override.yaml ]; then - yq -r '.secrets[].file' docker-compose.override.yaml | uniq | while read -r SECRET; do +for OVERRIDE_FILE in docker-compose.override.yml docker-compose.override.yaml; do + if [ ! -f "${OVERRIDE_FILE}" ]; then + continue + fi + yq -r '.secrets[].file' "${OVERRIDE_FILE}" | uniq | while read -r SECRET; do if [ ! -f "${SECRET}" ]; then echo "Creating: ${SECRET}" >&2 DIR=$(dirname "${SECRET}") @@ -28,4 +31,4 @@ if [ -f docker-compose.override.yaml ]; then (grep -ao "${CHARACTERS}" < /dev/urandom || true) | head "-${LENGTH}" | tr -d '\n' > "${SECRET}" fi done -fi +done diff --git a/scripts/test.sh b/scripts/test.sh index fb38bc9..1f1e343 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -8,6 +8,7 @@ docker compose up --remove-orphans -d max_attempts=20 attempt=0 +target_url="${SITE_URL:-http://localhost/}" while [ $attempt -lt $max_attempts ]; do attempt=$(( attempt + 1 )) @@ -15,7 +16,7 @@ while [ $attempt -lt $max_attempts ]; do sleep 10 - if curl -sf "http://localhost:${HOST_INSECURE_PORT:-80}/" | grep -qi "wordpress"; then + if curl -sf "$target_url" | grep -qi "wordpress"; then echo "WordPress is up!" exit 0 fi From dfd48a5450b3825f9b35705ac306ebdf8ac3e179 Mon Sep 17 00:00:00 2001 From: Joe Corall Date: Sun, 28 Jun 2026 12:19:51 +0000 Subject: [PATCH 2/4] Remove template init-if-needed script --- README.md | 2 +- scripts/init-if-needed.sh | 95 --------------------------------------- scripts/rollout.sh | 2 +- scripts/test.sh | 2 +- 4 files changed, 3 insertions(+), 98 deletions(-) delete mode 100755 scripts/init-if-needed.sh diff --git a/README.md b/README.md index 84a7224..4abbdcb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # WordPress Bedrock Docker Template -LibOps Docker Compose template for running a Composer-managed [Bedrock](https://roots.io/bedrock/) WordPress site with Traefik, MariaDB, and the LibOps WordPress PHP/nginx image. Use it with [`sitectl-wp`](https://github.com/libops/sitectl-wp). +The WordPress Bedrock Docker Template gives you a Docker Compose repository for running a Composer-managed [Bedrock](https://roots.io/bedrock/) WordPress site. It includes Traefik, MariaDB, and the LibOps WordPress PHP/nginx image, and is designed to be managed with [`sitectl-wp`](https://github.com/libops/sitectl-wp). Docs: diff --git a/scripts/init-if-needed.sh b/scripts/init-if-needed.sh deleted file mode 100755 index 9c38d15..0000000 --- a/scripts/init-if-needed.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -INIT_SERVICE="${INIT_SERVICE:-init}" -INIT_PROFILE="${INIT_PROFILE:-none}" - -if ! command -v docker >/dev/null 2>&1; then - echo "docker is required to inspect Compose state" >&2 - exit 1 -fi - -if ! command -v jq >/dev/null 2>&1; then - echo "jq is required to parse docker compose config output" >&2 - exit 1 -fi - -config_file="$(mktemp)" -trap 'rm -f "${config_file}"' EXIT -docker compose --profile "${INIT_PROFILE}" config --format json > "${config_file}" - -needs_init=0 -project="$( - jq -r \ - --arg fallback "${COMPOSE_PROJECT_NAME:-$(basename "${PWD}")}" \ - 'if (.name // "") != "" then .name else $fallback end' \ - "${config_file}" -)" -has_init="$(jq -r --arg service "${INIT_SERVICE}" '(.services // {}) | has($service)' "${config_file}")" - -while IFS= read -r secret_file; do - if [ ! -s "${secret_file}" ]; then - echo "Init required: missing secret file ${secret_file}" - needs_init=1 - fi -done < <( - jq -r --arg cwd "${PWD}" ' - (.secrets // {}) - | to_entries[] - | .value - | select(type == "object") - | .file? // empty - | if startswith("/") then . else "\($cwd)/\(.)" end - ' "${config_file}" | sort -u -) - -while IFS= read -r volume_key; do - volume_name="$( - jq -r --arg key "${volume_key}" --arg project "${project}" ' - (.volumes[$key] // {}) as $spec - | if ($spec | type) == "object" and (($spec.name // "") != "") then - $spec.name - elif ($spec | type) == "object" and ($spec.external == true) then - $key - elif ($spec | type) == "object" and (($spec.external | type) == "object") and (($spec.external.name // "") != "") then - $spec.external.name - else - "\($project)_\($key)" - end - ' "${config_file}" - )" - - if ! docker volume inspect "${volume_name}" >/dev/null 2>&1; then - echo "Init required: missing Docker volume ${volume_name}" - needs_init=1 - fi -done < <( - jq -r ' - (.services // {}) - | to_entries[] - | .value.volumes? // [] - | .[] - | if type == "object" then - select((.type // "volume") == "volume" and (.source // "") != "") - | .source - elif type == "string" and (index(":") != null) then - split(":")[0] - | select(. != "" and (startswith(".") | not) and (startswith("/") | not) and (startswith("~") | not)) - else - empty - end - ' "${config_file}" | sort -u -) - -if [ "${needs_init}" -eq 0 ]; then - echo "Init already satisfied." - exit 0 -fi - -if [ "${has_init}" != "true" ]; then - echo "Init is required, but Compose service ${INIT_SERVICE} was not found" >&2 - exit 1 -fi - -docker compose --profile "${INIT_PROFILE}" run --rm "${INIT_SERVICE}" diff --git a/scripts/rollout.sh b/scripts/rollout.sh index 5e16b47..043c643 100755 --- a/scripts/rollout.sh +++ b/scripts/rollout.sh @@ -23,7 +23,7 @@ checkout_ref docker compose pull --ignore-buildable --quiet || docker compose pull --ignore-buildable || true docker compose build --pull -./scripts/init-if-needed.sh +docker compose run --rm init docker compose up --remove-orphans --wait --pull missing --quiet-pull -d docker compose exec -T wp wp --allow-root --path=/var/www/bedrock/web/wp core update-db || echo "WordPress database update skipped or failed" diff --git a/scripts/test.sh b/scripts/test.sh index 1f1e343..3a91f29 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -3,7 +3,7 @@ set -eou pipefail docker compose build --pull -./scripts/init-if-needed.sh +docker compose run --rm init docker compose up --remove-orphans -d max_attempts=20 From 1059657e2744f6df2724f01e6051f32058c40cde Mon Sep 17 00:00:00 2001 From: Joe Corall Date: Sun, 28 Jun 2026 21:05:13 +0000 Subject: [PATCH 3/4] Fix compose startup dependencies --- docker-compose.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index c3a10b9..172050e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -69,7 +69,6 @@ services: image: libops/wp:nginx-1.30.3-php84 build: context: . - pull: true args: BASE_IMAGE: libops/wp:nginx-1.30.3-php84 working_dir: /var/www/bedrock @@ -100,7 +99,7 @@ services: - wordpress-uploads:/var/www/bedrock/web/app/uploads:rw depends_on: mariadb: - condition: service_started + condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q --spider http://localhost/ || exit 1"] interval: 30s @@ -115,3 +114,9 @@ services: - mariadb-data:/var/lib/mysql:rw secrets: - source: DB_ROOT_PASSWORD + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot --password=\"$$(cat /run/secrets/DB_ROOT_PASSWORD)\" --silent"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 30s From f8511238eda285420e3eb1d8f819316683e80932 Mon Sep 17 00:00:00 2001 From: Joe Corall Date: Sun, 28 Jun 2026 22:53:26 +0000 Subject: [PATCH 4/4] Remove template-owned rollout script --- Makefile | 5 +---- README.md | 2 +- docker-compose.yaml | 6 ------ scripts/rollout.sh | 34 ---------------------------------- 4 files changed, 2 insertions(+), 45 deletions(-) delete mode 100755 scripts/rollout.sh diff --git a/Makefile b/Makefile index a9bb5a9..55f9a60 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL := /bin/bash -.PHONY: help rollout test lint +.PHONY: help test lint .SILENT: -include custom.Makefile @@ -11,9 +11,6 @@ help: ## Show this help message echo 'Available targets:' awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%s\033[0m\t%s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort | column -t -s $$'\t' -rollout: ## Roll out the currently checked out WordPress stack - ./scripts/rollout.sh - test: ## Run template checks ./scripts/test.sh diff --git a/README.md b/README.md index 4abbdcb..c2e11f6 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ See the [WordPress sitectl plugin docs](https://sitectl.libops.io/plugins/wordpr The Makefile is intentionally small. It only keeps WordPress-specific targets that are not core sitectl operations: ```bash -make rollout +sitectl deploy make test make lint ``` diff --git a/docker-compose.yaml b/docker-compose.yaml index 172050e..9bff146 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -114,9 +114,3 @@ services: - mariadb-data:/var/lib/mysql:rw secrets: - source: DB_ROOT_PASSWORD - healthcheck: - test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot --password=\"$$(cat /run/secrets/DB_ROOT_PASSWORD)\" --silent"] - interval: 10s - timeout: 5s - retries: 12 - start_period: 30s diff --git a/scripts/rollout.sh b/scripts/rollout.sh deleted file mode 100755 index 043c643..0000000 --- a/scripts/rollout.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -log_file="${ROLLOUT_LOG:-$(pwd)/rollout.log}" -exec > >(tee -a "${log_file}") 2>&1 - -checkout_ref() { - if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - return - fi - - local target_ref="${GIT_REF:-${GIT_BRANCH:-${DOCKER_COMPOSE_BRANCH:-main}}}" - echo "Checking out ${target_ref}" - git fetch origin "${target_ref}" || git fetch origin - git checkout "${target_ref}" || git checkout FETCH_HEAD - if [ "$(git rev-parse --abbrev-ref HEAD)" != "HEAD" ]; then - git pull --ff-only || true - fi -} - -checkout_ref - -docker compose pull --ignore-buildable --quiet || docker compose pull --ignore-buildable || true -docker compose build --pull -docker compose run --rm init -docker compose up --remove-orphans --wait --pull missing --quiet-pull -d - -docker compose exec -T wp wp --allow-root --path=/var/www/bedrock/web/wp core update-db || echo "WordPress database update skipped or failed" -docker compose exec -T wp wp --allow-root --path=/var/www/bedrock/web/wp cache flush || true - -docker compose up --remove-orphans --wait --pull missing --quiet-pull -d - -echo "Rollout complete"