A modern headless CMS setup using WordPress as a backend with a custom React frontend
- Decoupled Architecture - Use WordPress solely as a content management backend via REST API
- Modern Frontend - Build with React and modern JavaScript frameworks for optimal performance
- Scalability - Scale frontend and backend independently based on your needs
- Developer Experience - Local development environment with Docker, HTTPS, and observability tools included
- Flexibility - Complete control over your frontend while leveraging WordPress's powerful content management
✨ Complete Docker Setup - Pre-configured Docker Compose with all services 🔒 HTTPS in Development - Local SSL certificates using mkcert 📊 Observability Stack - Integrated Grafana, Loki, and Promtail for monitoring 🔄 Reverse Proxy - Nginx reverse proxy with separate admin and API endpoints 🗄️ Database Management - Adminer included for easy database access 🚀 Production-Ready - Architecture designed for easy deployment to production
- Docker Desktop installed
- mkcert for local SSL certificates (optional, certs included)
- Make utility (usually pre-installed on Mac/Linux)
# Build the Docker images
make dev-build
# Start all services
make dev-up
# Stop all services
make dev-downOnce started, you can access:
- Admin Portal: https://admin.mycompany.local:8443
- REST API: https://api.mycompany.local:8443/wp-json
- Frontend: https://mycompany.local:8443 (proxied to localhost:3000 in dev)
- Direct WordPress: http://localhost:8080
- Adminer (DB GUI): http://localhost:9091
- Grafana: http://localhost:3001
- https://admin.mycompany.local:8443 → always show the WP login/admin (no public site)
- https://api.mycompany.local:8443 → WP REST API only
- https://mycompany.local:8443 → React front-end (proxied to localhost:3000 in dev)
- Certificate and key are generated by mkcert
mkcert \ -cert-file reverse-proxy/certs/certs/api.crt \ -key-file reverse-proxy/certs/private/api.key \ "api.mycompany.local" "admin.mycompany.local" "localhost" "127.0.0.1" "::1"
- It's the development environment included in the repository.
This project uses a decoupled headless CMS architecture where WordPress serves as the content management backend through its REST API, while a separate React frontend handles the user interface.
graph TB
subgraph "Client Layer"
Browser[Web Browser]
end
subgraph "Reverse Proxy Layer - Port 8443"
Nginx[Nginx Reverse Proxy<br/>SSL Termination]
end
subgraph "Application Layer"
Frontend[React Frontend<br/>Port 3000<br/>Modern UI]
WordPress[WordPress Core<br/>Port 80<br/>Headless CMS]
Setup[Setup Container<br/>WP Install & Config]
WPCli[WP-CLI<br/>Management Tools]
end
subgraph "Data Layer"
MariaDB[(MariaDB<br/>Port 3306<br/>Content DB)]
end
subgraph "Observability Stack"
Promtail[Promtail<br/>Log Collector]
Loki[Loki<br/>Log Storage]
Grafana[Grafana<br/>Port 3001<br/>Dashboards]
end
subgraph "Management Tools"
Adminer[Adminer<br/>Port 9091<br/>DB Admin]
end
Browser -->|HTTPS:8443<br/>mycompany.local| Nginx
Browser -->|HTTPS:8443<br/>admin.mycompany.local| Nginx
Browser -->|HTTPS:8443<br/>api.mycompany.local| Nginx
Browser -->|HTTP:3001| Grafana
Browser -->|HTTP:9091| Adminer
Nginx -->|Proxy Pass<br/>mycompany.local| Frontend
Nginx -->|Proxy Pass<br/>admin.mycompany.local| WordPress
Nginx -->|Proxy Pass<br/>api.mycompany.local/wp-json| WordPress
Frontend -.->|REST API Calls| Nginx
WordPress -->|SQL Queries| MariaDB
Setup -->|Configure & Install| WordPress
WPCli -->|CLI Commands| WordPress
WordPress -->|Write Logs| Promtail
Nginx -->|Write Logs| Promtail
MariaDB -->|Write Logs| Promtail
Promtail -->|Stream Logs| Loki
Loki -->|Query Logs| Grafana
Adminer -->|SQL Connection| MariaDB
style Browser fill:#e1f5ff
style Nginx fill:#90EE90
style Frontend fill:#FFD700
style WordPress fill:#0073aa
style MariaDB fill:#003545
style Grafana fill:#F46800
style Loki fill:#F46800
style Promtail fill:#F46800
sequenceDiagram
actor User
participant Browser
participant Nginx as Nginx Proxy<br/>(Port 8443)
participant React as React App<br/>(Port 3000)
participant WP as WordPress<br/>(Port 80)
participant DB as MariaDB<br/>(Port 3306)
Note over User,DB: Frontend Content Request
User->>Browser: Visit mycompany.local:8443
Browser->>Nginx: HTTPS Request
Nginx->>React: Proxy to localhost:3000
React-->>Browser: HTML/JS/CSS
Note over Browser,DB: API Data Fetch
React->>Nginx: GET /wp-json/wp/v2/posts
Nginx->>WP: Forward to api.mycompany.local
WP->>DB: SELECT * FROM wp_posts
DB-->>WP: Post records
WP-->>Nginx: JSON Response
Nginx-->>React: JSON Data
React-->>Browser: Rendered Content
Note over User,DB: Admin Login
User->>Browser: Visit admin.mycompany.local:8443
Browser->>Nginx: HTTPS Request
Nginx->>WP: Proxy to WordPress admin
WP-->>Browser: WP Admin Interface
Note over Browser,DB: Authenticated API Call
Browser->>Nginx: POST /jwt-auth/v1/token
Nginx->>WP: Forward auth request
WP->>DB: Verify credentials
DB-->>WP: User valid
WP-->>Nginx: JWT Token
Nginx-->>Browser: Token response
Browser->>Nginx: POST /wp-json/wp/v2/posts<br/>(with JWT header)
Nginx->>WP: Authenticated request
WP->>WP: Validate JWT
WP->>DB: INSERT new post
DB-->>WP: Post created
WP-->>Nginx: 201 Created
Nginx-->>Browser: Success response
-
Nginx Reverse Proxy: Routes traffic to appropriate backends with SSL termination
mycompany.local→ React Frontendadmin.mycompany.local→ WordPress Adminapi.mycompany.local→ WordPress REST API
-
WordPress: Headless CMS providing REST API and admin interface
- JWT Authentication for secure API access
- REST API endpoints for content
- Media management and uploads
-
MariaDB: Relational database for WordPress data
- Content storage (posts, pages, media)
- User authentication data
- WordPress configuration and metadata
-
React Frontend: Custom UI consuming WordPress REST API
- Modern, fast user experience
- Decoupled from WordPress presentation layer
-
Grafana Stack: Log aggregation and monitoring
- Promtail: Collects logs from all Docker containers
- Loki: Aggregates and indexes logs
- Grafana: Visualizes logs and creates dashboards
-
Adminer: Web-based database management tool
- Direct database access for development
- SQL query interface
Our CI/CD pipeline ensures code quality through automated testing on every push and pull request.
sequenceDiagram
actor Developer
participant GitHub
participant GH Actions as GitHub Actions
participant Docker
participant WordPress
participant MariaDB
participant Nginx
participant Jest as Jest Test Runner
participant Codecov
Developer->>GitHub: Push/PR to main branch
GitHub->>GH Actions: Trigger workflow
Note over GH Actions: 1. Environment Setup
GH Actions->>GH Actions: Checkout code
GH Actions->>GH Actions: Setup Node.js 20
GH Actions->>GH Actions: Setup Docker Buildx
GH Actions->>GH Actions: Add hosts to /etc/hosts
Note over GH Actions,Docker: 2. Build Services
GH Actions->>Docker: make dev-build
Docker->>Docker: Build WordPress image
Docker->>Docker: Build reverse-proxy image
Docker->>Docker: Build monitoring images
Docker-->>GH Actions: Build complete
Note over GH Actions,Nginx: 3. Start Services
GH Actions->>Docker: make dev-up
Docker->>MariaDB: Start database
MariaDB-->>Docker: Ready
Docker->>WordPress: Start WordPress
WordPress->>MariaDB: Connect to DB
MariaDB-->>WordPress: Connection established
Docker->>Nginx: Start reverse proxy
Nginx->>WordPress: Connect upstream
WordPress-->>Nginx: Connection ready
Docker->>WordPress: Run setup container
WordPress->>WordPress: Install WordPress
WordPress->>WordPress: Activate JWT Auth plugin
Docker-->>GH Actions: All services running
Note over GH Actions,WordPress: 4. Health Checks
GH Actions->>Nginx: curl https://api.mycompany.local:8443/wp-json/
Nginx->>WordPress: Forward request
WordPress-->>Nginx: API discovery response
Nginx-->>GH Actions: 200 OK
GH Actions->>Nginx: Check JWT Auth endpoint
Nginx->>WordPress: Forward JWT request
WordPress-->>Nginx: JWT Auth available
Nginx-->>GH Actions: JWT ready
GH Actions->>Docker: Check setup container logs
Docker-->>GH Actions: Setup successful
GH Actions->>WordPress: wp plugin list
WordPress-->>GH Actions: Plugins active
Note over GH Actions,Jest: 5. Run Test Suite
GH Actions->>Jest: npm ci (install dependencies)
Jest-->>GH Actions: Dependencies installed
GH Actions->>Jest: npm run test:ci
Jest->>Nginx: Test 1: JWT Authentication
Nginx->>WordPress: POST /jwt-auth/v1/token
WordPress-->>Nginx: JWT token
Nginx-->>Jest: âś“ Auth successful
Jest->>Nginx: Test 2: Posts CRUD
Nginx->>WordPress: GET /wp/v2/posts
WordPress->>MariaDB: Query posts
MariaDB-->>WordPress: Post data
WordPress-->>Nginx: Posts response
Nginx-->>Jest: âś“ Posts retrieved
Jest->>Nginx: Test 3: Pages & Media
Nginx->>WordPress: GET /wp/v2/pages
WordPress->>MariaDB: Query pages
MariaDB-->>WordPress: Page data
WordPress-->>Nginx: Pages response
Nginx-->>Jest: âś“ Pages retrieved
Jest->>Nginx: Test 4: Taxonomy
Nginx->>WordPress: GET /wp/v2/categories
WordPress->>MariaDB: Query categories
MariaDB-->>WordPress: Category data
WordPress-->>Nginx: Categories response
Nginx-->>Jest: âś“ Taxonomy working
Jest->>Nginx: Test 5: CORS & Security
Nginx->>WordPress: OPTIONS /wp/v2/posts
WordPress-->>Nginx: CORS headers
Nginx-->>Jest: âś“ Security validated
Jest-->>GH Actions: All tests passed âś“
Note over GH Actions,Codecov: 6. Upload Results
GH Actions->>Jest: Generate coverage report
Jest-->>GH Actions: coverage/lcov.info
GH Actions->>Codecov: Upload coverage
Codecov-->>GH Actions: Coverage uploaded
GH Actions->>GitHub: Upload test artifacts
GitHub-->>GH Actions: Artifacts stored
Note over GH Actions,Docker: 7. Cleanup
GH Actions->>Docker: make dev-down
Docker->>Nginx: Stop reverse proxy
Docker->>WordPress: Stop WordPress
Docker->>MariaDB: Stop database
Docker-->>GH Actions: Cleanup complete
GH Actions->>GitHub: Update build status âś“
GitHub->>Developer: Notification: Build passed
Workflow File: .github/workflows/api-tests.yml
The automated test suite covers:
-
JWT Authentication (
01-jwt-auth.test.ts)- Login flows, token validation, security checks
-
Posts API (
02-posts.test.ts)- CRUD operations, pagination, search, filtering
-
Pages & Media (
03-pages-media.test.ts)- Page management, media uploads, metadata
-
Taxonomy (
04-taxonomy.test.ts)- Categories, tags, users, custom taxonomies
-
CORS & Security (
05-cors-security.test.ts)- CORS headers, authentication, authorization, error handling
For detailed test documentation, see tests/README.md
# 1. Start development environment
make dev-up
# 2. Access the services
# - WordPress Admin: https://admin.mycompany.local:8443
# - REST API: https://api.mycompany.local:8443/wp-json
# - Frontend: https://mycompany.local:8443
# 3. Make your changes to WordPress or frontend code
# 4. Run tests locally
cd tests
npm install
npm test
# 5. Stop services when done
make dev-down- Modify WordPress configuration in
compose.core.yaml - Add plugins/themes to the WordPress container
- Update environment variables as needed
- Rebuild:
make dev-build
- Edit React components (not included in this repo)
- Frontend runs on
localhost:3000 - Nginx proxies to React dev server
- Modify Docker Compose files:
compose.core.yaml- Core servicescompose.reverse-proxy.yaml- Nginx configurationcompose.monitoring.yaml- Observability stack
- Update Nginx configuration in
reverse-proxy/ - Rebuild and restart:
make dev-build && make dev-up
make dev-build # Build Docker images
make dev-up # Start all services
make dev-down # Stop all services
make dev-logs # View service logs
make dev-restart # Restart servicesAccess Grafana at http://localhost:3001 to view:
- Application logs from all services
- WordPress access and error logs
- Nginx proxy logs
- Database query logs
Default credentials: admin/admin (change on first login)
The stack uses:
- Promtail: Collects logs from Docker containers
- Loki: Stores and indexes logs
- Grafana: Visualizes logs and creates dashboards
# View WordPress logs
docker compose logs -f wordpress
# View database logs
docker compose logs -f db
# View all logs
make dev-logs
# Check container health
docker compose psBefore deploying to production:
-
Security
- Change all default passwords
- Generate new JWT secret keys
- Use proper SSL certificates (not self-signed)
- Enable WordPress security plugins
- Configure proper CORS origins
-
Database
- Use managed database service (RDS, Cloud SQL)
- Set up regular backups
- Enable SSL connections
- Tune performance parameters
-
WordPress
- Enable caching (Redis, Memcached)
- Configure CDN for media files
- Set up automated backups
- Enable security headers
-
Nginx
- Configure rate limiting
- Enable HTTP/2
- Set up proper caching headers
- Add DDoS protection
-
Monitoring
- Set up alerts for errors
- Monitor API response times
- Track database performance
- Configure log retention policies
Key environment variables to configure:
# Database
DB_NAME=wordpress
DB_USER=wp_user
DB_PASSWORD=secure_password
# WordPress
WP_ADMIN_USER=admin
WP_ADMIN_PASS=secure_password
WP_ADMIN_EMAIL=admin@example.com
# JWT
JWT_SECRET_KEY=generate_secure_key_here
# Domains
ADMIN_HOST=admin.yoursite.com
API_HOST=api.yoursite.comProblem: Browser shows SSL warnings or certificate errors
Solution:
# Ensure certificates are valid
ls -la reverse-proxy/certs/certs/
ls -la reverse-proxy/certs/private/
# Regenerate certificates if needed
mkcert -cert-file reverse-proxy/certs/certs/api.crt \
-key-file reverse-proxy/certs/private/api.key \
"api.mycompany.local" "admin.mycompany.local" "localhost"Problem: Docker services fail to start
Solution:
# Check for port conflicts
lsof -i :8080 # WordPress
lsof -i :8443 # Nginx
lsof -i :3306 # MariaDB
# Stop all services and restart
make dev-down
docker system prune -f
make dev-build
make dev-upProblem: WordPress can't connect to database
Solution:
# Check database is running
docker compose ps db
# View database logs
docker compose logs db
# Verify credentials match in compose.core.yaml
# Check DB_HOST, DB_NAME, DB_USER, DB_PASSWORDProblem: WordPress installation hangs or fails
Solution:
# Check setup container logs
docker compose logs setup
# Restart setup container
docker compose up setup --force-recreate
# Verify plugins installed
docker compose run --rm wpcli wp plugin list --allow-rootProblem: Tests fail locally or in CI
Solution:
# Ensure all services are healthy
curl -k https://api.mycompany.local:8443/wp-json/
# Check test environment variables
cat tests/.env.test
# Run tests with verbose output
cd tests
npm test -- --verbose
# Check WordPress logs during test run
docker compose logs -f wordpressProblem: Cannot start services due to port conflicts
Solution:
# Find process using port
lsof -i :8080
# Kill the process or change port in compose files
# Edit compose.core.yaml or compose.reverse-proxy.yaml- Check GitHub Issues
- Review tests/README.md for test-specific issues
- Check Docker logs:
make dev-logs - Verify
/etc/hostsconfiguration
We welcome contributions! Here's how to get started:
- Fork the repository
- Clone your fork:
git clone https://github.com/yourusername/Headless-Wordpress.git - Create a branch:
git checkout -b feature/your-feature-name - Set up the environment:
make dev-build && make dev-up
- Make your changes to the codebase
- Add tests for new features (see
tests/README.md) - Run tests locally:
cd tests && npm test - Ensure all tests pass before submitting
- Use clear, descriptive commit messages
- Follow existing code structure and patterns
- Add comments for complex logic
- Update documentation for new features
- Ensure all tests pass locally
- Update README.md if adding features
- Add test coverage for new functionality
- Push to your fork:
git push origin feature/your-feature-name - Open a Pull Request with:
- Clear description of changes
- Link to related issues
- Screenshots (if UI changes)
- Test results
Before submitting:
# Run full test suite
cd tests
npm test
# Run with coverage
npm run test:coverage
# Ensure coverage meets requirementsWhen reporting bugs, include:
- Steps to reproduce
- Expected behavior
- Actual behavior
- Environment details (OS, Docker version)
- Relevant logs (
make dev-logs)
For feature requests:
- Describe the use case
- Explain the expected benefit
- Provide examples if possible
- WordPress Core Team
- React Community
- Docker & Docker Compose
- Grafana Labs (Loki & Grafana)
- All contributors