Website · Documentation · Get in Touch · Report a Bug
Docker-based WordPress development and publishing platform with two modes:
- Local Mode — Full-featured local WordPress development tool (like Local by Flywheel / Laragon) with multi-PHP, multi-DB support, and a
wplCLI - Agency Mode — Demo hosting platform that spins up temporary WordPress sites pre-loaded with your plugins/themes, auto-deleted after expiration
Each site runs in its own Docker container with a choice of SQLite, MySQL, or MariaDB, routed through Traefik with automatic subdomain assignment.
User clicks "Launch Demo"
→ API creates a Docker container with your product pre-installed
→ Traefik auto-routes a unique subdomain to the container
→ WordPress auto-installs via entrypoint script
→ User gets a working WordPress site in ~10 seconds
→ Site auto-deletes after expiration (default: 1 hour)
- Instant provisioning — Pre-built Docker images with plugins baked in (~10s launch time)
- Full isolation — Each site is a separate container, no shared databases
- 3 database engines — SQLite (fastest), MySQL 8.4, or MariaDB 11 per site
- 3 PHP versions — PHP 8.1, 8.2, or 8.3 selectable per site
- Per-site PHP config — Tune memory_limit, upload size, execution time, and toggle extensions (Redis, Xdebug, etc.) per site — live, no restart needed
wplCLI — Global command to start/stop services, manage sites, run WP-CLI, and more- Persistent data — Local mode sites survive container restarts via Docker volumes
- Auto-cleanup — Agency mode sites auto-expire and get removed
- One-click admin login — Auto-login URLs for instant WP admin access
- Email testing — Built-in Mailpit catches all outgoing emails locally
- Multi-product — Host demos for multiple products from a single installation
- Wildcard subdomains — Each site gets a unique URL like
coral-sunset-7x3k.localhost - Site password protection — Optional basic auth on frontend only, admin only, or entire site for private previews
- Export site as ZIP — Download wp-content + DB dump as tar.gz to migrate demo customizations
- Webhook notifications — HMAC-SHA256 signed HTTP POST on site.created/expired/deleted with admin UI for management
- Site Health Monitoring — Real-time container CPU, memory, network stats with color-coded dashboard
- Scheduled Site Launch — Schedule sites to launch automatically at a future time (up to 7 days)
- Modern dashboard — React SPA with real-time provisioning progress
| Component | Technology |
|---|---|
| Management API | Node.js + Express + TypeScript |
| Management DB | SQLite (better-sqlite3) |
| WordPress DB | SQLite, MySQL 8.4, or MariaDB 11 (per product) |
| Reverse Proxy | Traefik v3 (auto-discovery via Docker labels) |
| Dashboard | React + Vite + TypeScript |
| Container Base | wordpress:6.9 (PHP 8.1 / 8.2 / 8.3) |
wp-launcher/
├── docker-compose.yml # Infrastructure (Traefik + API + Dashboard)
├── .env.example # Environment configuration
├── install.sh # One-click VPS installer
├── install-local.sh # Local mode installer
├── bin/wpl # Global CLI command
├── packages/
│ ├── api/ # Management API (Express/TypeScript)
│ ├── provisioner/ # Docker container management service
│ └── dashboard/ # React SPA (Vite)
├── wordpress/ # Custom WP Docker image
│ ├── Dockerfile
│ ├── entrypoint.sh # Auto-installs WP + activates plugins
│ ├── wp-config-docker.php
│ └── mu-plugins/ # Admin restrictions, branding & auto-login
├── products/ # Product configs (agency mode)
│ └── _default.json
├── templates/ # Template configs (local mode)
│ └── starter.json
├── product-assets/ # Local plugins/themes per product
│ └── my-product/
│ └── plugins/
├── traefik/ # Reverse proxy config
├── scripts/
│ ├── setup.sh # Initial setup script
│ └── build-wp-image.sh # Builds product Docker images
└── guides/ # Documentation
├── getting-started.md
├── creating-products.md
├── working-with-wordpress-files.md # Edit themes/plugins in Docker
├── adding-product-images.md
├── vps-deployment.md
├── cloudflare-dns-setup.md
└── upgrading.md
Use WP Launcher as a local WordPress development tool — like Local by Flywheel or Laragon, but Docker-based with multi-PHP support.
git clone https://github.com/msrbuilds/wp-launcher.git
cd wp-launcher
bash install-local.shThat's it. The installer will:
- Check for Docker & Docker Compose
- Generate
.envwith local mode defaults - Build WordPress images for PHP 8.1, 8.2, and 8.3
- Install the
wplCLI command globally - Start all services
- Open http://localhost in your browser
What you get:
- No authentication, no site limits, no WordPress restrictions (file mods, updates, cron all enabled)
- Choose PHP version (8.1 / 8.2 / 8.3), database engine (MySQL / MariaDB / SQLite), and admin credentials per site
- Sites at
http://{subdomain}.localhost(works in Chrome, Firefox, Edge — no hosts file needed) - Persistent site data via Docker volumes (survives restarts)
- Built-in email testing via Mailpit at
http://localhost:8025 - Global
wplCLI command (see CLI Reference below)
Host temporary WordPress demo sites for your products — with auth, site limits, auto-expiration, and admin restrictions.
Run the one-click installer on a fresh Ubuntu VPS:
curl -sSL https://scripts.msrbuilds.com/wplauncher/install.sh | bashThis will:
- Install Docker & Docker Compose
- Clone the repository
- Prompt for your domain, email, and SMTP settings
- Generate secure secrets
- Configure Traefik with automatic Let's Encrypt SSL
- Build all Docker images
- Start all services
For a detailed step-by-step guide, see guides/vps-deployment.md.
Prerequisites: Docker Desktop, Node.js 18+, Git Bash (Windows) or any Unix shell.
git clone https://github.com/msrbuilds/wp-launcher.git
cd wp-launcher
cp .env.example .env
# Edit .env with your settings (see Configuration section)Build the WordPress images:
bash scripts/build-wp-image.shAdd your first product — create products/my-product.json:
{
"id": "my-product",
"name": "My Awesome Plugin",
"plugins": {
"preinstall": [
{ "source": "wordpress.org", "slug": "my-plugin", "activate": true }
],
"remove": ["hello", "akismet"]
},
"demo": {
"default_expiration": "1h",
"max_concurrent_sites": 5,
"admin_user": "demo",
"landing_page": "/wp-admin/admin.php?page=my-plugin"
},
"restrictions": {
"disable_file_mods": true,
"blocked_capabilities": [
"install_plugins", "install_themes",
"edit_plugins", "edit_themes", "update_core"
]
},
"database": "sqlite",
"docker": {
"image": "wp-launcher/my-product:latest"
},
"branding": {
"banner_text": "Demo site — expires in {time_remaining}.",
"description": "Try My Awesome Plugin in a live WordPress environment."
}
}Build the product image and start:
bash scripts/build-wp-image.sh my-product
docker compose up -dVisit http://localhost — the dashboard is ready.
| Variable | Description | Default |
|---|---|---|
APP_MODE |
agency (auth, limits, restrictions) or local (no auth, no limits) |
agency |
BASE_DOMAIN |
Your domain (subdomains will be *.BASE_DOMAIN) |
localhost |
NODE_ENV |
development or production |
development |
API_KEY |
Admin API key (bypasses rate limits, site limits, ownership checks) | (required) |
JWT_SECRET |
Secret for user JWT tokens | (required) |
PUBLIC_URL |
Public URL for email verification links | http://localhost |
SMTP_HOST |
SMTP server for verification emails | mailpit |
SMTP_PORT |
SMTP port | 1025 |
SMTP_USER |
SMTP username | — |
SMTP_PASS |
SMTP password | — |
SMTP_FROM |
Sender address for emails | — |
EMAIL_PROVIDER |
smtp or brevo (HTTP API, bypasses SMTP port blocks) |
smtp |
BREVO_API_KEY |
Brevo API key (when EMAIL_PROVIDER=brevo) |
— |
WP_IMAGE |
Default WordPress image | wp-launcher/wordpress:latest |
MAX_SITES_PER_USER |
Max active sites per user (0 = unlimited) | 3 |
MAX_TOTAL_SITES |
Max total active sites across all users (0 = unlimited) | 50 |
CONTAINER_MEMORY |
Per-container memory limit in bytes | 268435456 (256MB) |
CONTAINER_CPU |
Per-container CPU limit | 0.5 |
PRODUCT_ASSETS_PATH |
Absolute host path to product-assets/ dir (required for local plugins) |
— |
SITES_HOST_PATH |
Absolute host path to sites/ dir — enables direct file access to wp-content |
— |
PROVISIONER_INTERNAL_KEY |
Shared secret for API ↔ provisioner communication | (required) |
JWT_EXPIRES_IN |
JWT token expiry duration | 7d |
CARD_LAYOUT |
Dashboard card layout: full or compact |
full |
API_PORT |
Host port the API is exposed on (internal container port is always 3737) |
3737 |
CUSTOM_DOMAIN_CERT_RESOLVER |
Cert resolver for custom domains. Leave empty when using Cloudflare proxy (Cloudflare handles SSL). Set to httpchallenge for direct DNS (no proxy) to get real Let's Encrypt certs |
`` (empty) |
CORS_ALLOWED_ORIGINS |
Comma-separated allowed CORS origins | — |
SMTP_SECURE |
Use TLS for SMTP (true / false) |
false |
ACME_EMAIL |
Email for Let's Encrypt certificate notifications | — |
CF_API_EMAIL |
Cloudflare API email (for DNS-01 wildcard certs) | — |
CF_DNS_API_TOKEN |
Cloudflare DNS API token (for DNS-01 wildcard certs) | — |
Each JSON file in products/ defines a demo product. The filename must match the id field (e.g., my-product.json → "id": "my-product").
| Field | Description |
|---|---|
id |
Unique identifier (must match filename) |
name |
Display name on the dashboard |
plugins.preinstall |
Array of plugins to install (see Plugin Sources) |
plugins.remove |
Plugin slugs to remove from default WP install |
themes.install |
Array of themes to install |
demo.default_expiration |
How long demos last (1h, 2h, 24h) |
demo.max_concurrent_sites |
Max active demos for this product |
demo.admin_user |
Demo admin username (password is auto-generated) |
demo.landing_page |
Redirect path after WP login |
restrictions.disable_file_mods |
Block plugin/theme installs |
restrictions.blocked_capabilities |
WP capabilities to remove |
database |
"sqlite" (default), "mysql", or "mariadb" — use MySQL/MariaDB for plugins that require it |
docker.image |
Docker image tag (set after building) |
branding.banner_text |
Banner shown in wp-admin ({time_remaining} placeholder) |
branding.description |
Card description on the dashboard |
branding.image_url |
Product card image URL |
Three ways to include plugins:
{
"plugins": {
"preinstall": [
{ "source": "wordpress.org", "slug": "contact-form-7", "activate": true },
{ "source": "url", "url": "https://example.com/pro-plugin.zip", "activate": true },
{ "source": "local", "path": "product-assets/my-product/plugins/my-plugin.zip", "activate": true }
]
}
}| Source | Description |
|---|---|
wordpress.org |
Downloaded from the WP plugin directory by slug |
url |
Downloaded from any URL (must be a .zip) |
local |
Copied from product-assets/ directory (path relative to project root, must be a .zip) |
Each site supports per-site PHP settings, configurable at creation time and live-updatable from the Sites dashboard (PHP button).
Configurable settings: memory_limit, upload_max_filesize, post_max_size, max_execution_time, max_input_vars, display_errors
Optional extensions (pre-installed in the Docker image, disabled by default):
| Extension | Description |
|---|---|
redis |
Redis object cache |
xdebug |
Step debugger (auto-configures for host.docker.internal:9003) |
sockets |
Socket functions |
calendar |
Calendar conversion functions |
pcntl |
Process control |
ldap |
LDAP directory access |
gettext |
GNU gettext internationalization |
PHP settings are written to /usr/local/etc/php/conf.d/99-wp-launcher.ini inside the container and applied via Apache graceful reload — no container restart required.
| Behavior | Local Mode | Agency Mode |
|---|---|---|
| Authentication | None (auto-authenticated) | Email verification + JWT |
| Site limits | Unlimited | Configurable per-user and global |
| WordPress restrictions | None (full admin) | DISALLOW_FILE_MODS, blocked capabilities |
| WordPress updates | Allowed (core, plugins, themes) | Blocked |
| WP-Cron | Enabled | Disabled |
| Site expiration | Default: never | Default: 1 hour |
| Data persistence | Docker volumes (survives restarts) | Ephemeral (deleted on expiry) |
# 1. Create/edit the product JSON in products/
# 2. Build the Docker image and restart the API:
bash scripts/build-wp-image.sh my-product && docker compose restart apiThe new product appears on the dashboard immediately.
In local mode, each site's wp-content directory is bind-mounted to your host at sites/{subdomain}/wp-content/. Edit plugins, themes, and files directly — no Docker commands needed.
# From the CLI
wpl code <subdomain>
# Or open manually
code sites/coral-sunset-7x3k/wp-content/plugins/my-pluginThe dashboard also has VS Code and Copy Path buttons on each site card.
wpl browse <subdomain>The install-local.sh installer configures this automatically. For existing installations, add to .env:
SITES_HOST_PATH=/absolute/path/to/wp-launcher/sites
Then rebuild: docker compose up -d --build api provisioner
wpl wp <subdomain> plugin list # Run WP-CLI
wpl shell <subdomain> # Bash into containerFor more methods and tips, see Working with WordPress Files Guide.
# Stop the dashboard container
docker compose stop dashboard
# Start Vite dev server (proxies API calls to the Docker API)
cd packages/dashboard && npm run devDashboard available at http://localhost:4000 with instant hot reload.
Note: The Vite dev proxy reads
API_PORTfrom the shell environment. If you changedAPI_PORTin.env, export it before starting the dev server:export API_PORT=3800 npm run dev
The API runs inside Docker. After editing API source files:
docker compose build api && docker compose up -d api| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
— | Send verification email |
POST |
/api/auth/verify |
— | Verify email token |
GET |
/api/products |
— | List all products |
GET |
/api/products/:id |
— | Get product config |
POST |
/api/sites |
User | Create a demo site |
GET |
/api/sites |
Optional | List sites (user's or all) |
GET |
/api/sites/:id |
— | Get site details |
GET |
/api/sites/:id/ready |
— | Check if site setup is complete (plugins, themes, content) |
GET |
/api/sites/:id/status |
— | Docker container status |
GET |
/api/sites/:id/php-config |
— | Read current PHP configuration from running site |
PATCH |
/api/sites/:id/php-config |
User | Update PHP settings (live, Apache graceful reload) |
DELETE |
/api/sites/:id |
User | Delete a demo site |
The wpl command is installed globally by install-local.sh and works from any directory.
wpl start # Start all services
wpl stop # Stop all services
wpl restart # Restart all services
wpl rebuild # Rebuild and restart (after code changes)
wpl status # Show running containers
wpl logs [service] # Tail logs (all or specific service)
wpl sites # List active WordPress sites
wpl open # Open dashboard in browser
wpl open mail # Open Mailpit in browser
wpl open <subdomain> # Open a site in browser
wpl shell <subdomain> # Bash into a site container
wpl wp <subdomain> plugin list # Run WP-CLI in a site container
wpl code <subdomain> # Open site's wp-content in VS Code
wpl browse <subdomain> # Open site's wp-content in file manager
wpl build:wp # Rebuild WordPress images (all PHP versions)
wpl dir # Print project directory path
wpl help # Show all commandsYou can also use npm scripts from the project directory:
npm start # Start services
npm stop # Stop services
npm run rebuild # Rebuild and restart
npm run status # Show containers
npm run logs # Tail logsEach demo container uses approximately:
- RAM: ~100MB (WordPress + PHP + Apache + SQLite)
- Disk: ~200MB per container
- CPU: Minimal when idle
A server with 4GB RAM can comfortably run 20-30 concurrent demo sites.
MIT

