# Pubwhale — headless WordPress demo, portable bundle. # # Runs from pre-built images loaded by ./load.sh; nothing is compiled here and # no source tree is required. # # ./load.sh # import images, create .env, fix upload perms # docker compose up -d # open http://localhost:3026 (site) # open http://localhost:8080/wp-admin (CMS) # # WHERE STATE LIVES — see README.md "Storage and media" for the full story. # db-data volume ....... every post, page, setting (the real content store) # wp-data volume ....... the WordPress install itself (core + plugins) # ./content/uploads .... media files, deliberately a plain host directory so # they are visible, backup-able and travel with this # bundle instead of hiding inside a Docker volume name: pubwhale-demo services: db: image: mariadb:11 restart: unless-stopped environment: MARIADB_DATABASE: ${WORDPRESS_DB_NAME:-pubwhale} MARIADB_USER: ${WORDPRESS_DB_USER:-pubwhale} MARIADB_PASSWORD: ${WORDPRESS_DB_PASSWORD:?set WORDPRESS_DB_PASSWORD in .env} MARIADB_ROOT_PASSWORD: ${WORDPRESS_DB_ROOT_PASSWORD:?set WORDPRESS_DB_ROOT_PASSWORD in .env} volumes: - db-data:/var/lib/mysql # MariaDB runs everything in this directory the FIRST time it starts with # an empty data volume — that is how the seeded content gets restored. # On every later start the directory is ignored. - ./content/db.sql.gz:/docker-entrypoint-initdb.d/10-pubwhale.sql.gz:ro healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 12 networks: [pubwhale] wordpress: image: wordpress:6-php8.3-apache restart: unless-stopped depends_on: db: condition: service_healthy environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME:-pubwhale} WORDPRESS_DB_USER: ${WORDPRESS_DB_USER:-pubwhale} WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD} PUBWHALE_FRONTEND_URL: ${PUBWHALE_FRONTEND_URL:-http://web:3000} PUBWHALE_FRONTEND_ORIGIN: ${PUBWHALE_FRONTEND_ORIGIN:-http://localhost:3026} PUBWHALE_REVALIDATE_SECRET: ${REVALIDATE_SECRET:?set REVALIDATE_SECRET in .env} WORDPRESS_CONFIG_EXTRA: | define('WP_HOME', '${WORDPRESS_PUBLIC_URL:-http://localhost:8080}'); define('WP_SITEURL', '${WORDPRESS_PUBLIC_URL:-http://localhost:8080}'); /* Headless: the CMS serves JSON, not pages. */ define('DISALLOW_FILE_EDIT', true); /* Harmless on plain HTTP; required if you put this behind a TLS proxy. */ if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $$_SERVER['HTTPS'] = 'on'; } volumes: - wp-data:/var/www/html # Read-only: the repository is the source of truth for the custom post # types. Losing this bind mount makes News/Governance/IR/People vanish. - ./wordpress/mu-plugins:/var/www/html/wp-content/mu-plugins:ro # Media. A host directory rather than a volume so it is easy to back up. - ./content/uploads:/var/www/html/wp-content/uploads ports: - "${WORDPRESS_PORT:-8080}:80" networks: [pubwhale] web: image: pubwhale-web:bundle restart: unless-stopped depends_on: - wordpress environment: # Server-side CMS calls stay on the Docker network. WORDPRESS_API_URL: ${WORDPRESS_API_URL:-http://wordpress} NEXT_PUBLIC_WORDPRESS_BASE_URL: ${WORDPRESS_PUBLIC_URL:-http://localhost:8080} NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3026} WORDPRESS_REVALIDATE_SECONDS: ${WORDPRESS_REVALIDATE_SECONDS:-300} REVALIDATE_SECRET: ${REVALIDATE_SECRET} ports: - "${WEB_PORT:-3026}:3000" networks: [pubwhale] # One-shot helper, only for a bundle started WITHOUT the bundled database # dump (i.e. you deleted content/db.sql.gz and want a clean seeded install). # docker compose --profile init run --rm wp-init wp-init: image: wordpress:cli-php8.3 profiles: [init] depends_on: db: condition: service_healthy user: "33:33" environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME:-pubwhale} WORDPRESS_DB_USER: ${WORDPRESS_DB_USER:-pubwhale} WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD} PUBWHALE_FRONTEND_URL: ${PUBWHALE_FRONTEND_URL:-http://web:3000} PUBWHALE_REVALIDATE_SECRET: ${REVALIDATE_SECRET} WORDPRESS_PUBLIC_URL: ${WORDPRESS_PUBLIC_URL:-http://localhost:8080} WORDPRESS_SITE_TITLE: ${WORDPRESS_SITE_TITLE:-Pubwhale CMS} WORDPRESS_ADMIN_USER: ${WORDPRESS_ADMIN_USER:-pubwhale} WORDPRESS_ADMIN_PASSWORD: ${WORDPRESS_ADMIN_PASSWORD:?set WORDPRESS_ADMIN_PASSWORD in .env} WORDPRESS_ADMIN_EMAIL: ${WORDPRESS_ADMIN_EMAIL:-admin@example.com} volumes: - wp-data:/var/www/html - ./wordpress/mu-plugins:/var/www/html/wp-content/mu-plugins:ro - ./content/uploads:/var/www/html/wp-content/uploads - ./wordpress/seed:/var/www/seed:ro - ./wordpress/scripts:/scripts:ro entrypoint: ["/bin/sh", "/scripts/init.sh"] networks: [pubwhale] volumes: db-data: wp-data: networks: pubwhale: