This is lightweight, high-performance control panel to deploy and manage WordPress sites using Docker.
It’s designed for developers and agencies who want the speed and isolation of containerized hosting without paying monthly fees for SaaS management panels. Also for developers and agencies who want more control over their infrastructure.
You can find more details in the docs folder.
After cloning the repository and moving into the project directory, you can use the automated script or run the commands below.
You need Docker to work on this project in the way outlined below. If you prefer to work without Docker, it is possible, but you'll have to set up everything yourself.
From the repository root:
./run-dev.shOn a fresh clone, this runs full setup (Composer via Docker, Sail, .env, migrations with seeders, npm) and then starts
Vite. Later runs typically only start Sail and Vite. Use ./run-dev.sh --reset for a clean Docker volume state and a
fresh database. See docs/run-dev.md for options, logging, and how this differs from setup.sh.
If you prefer to run commands yourself, the sections below mirror what run-dev.sh performs on first-time setup.
docker run \
-u "$(id -u):$(id -g)" \
-v "$(pwd):/var/www/html" \
-w /var/www/html \
laravelsail/php84-composer:latest \
composer install --ignore-platform-reqsCopy .env.example to .env and configure it.
The defaults should be fine in most cases.
./vendor/bin/sail up -d
./vendor/bin/sail artisan key:generate
./vendor/bin/sail artisan migrate:fresh --seed# php artisan migrate:fresh --seed
composer mfs
# php artisan migrate:fresh
composer mf
./vendor/bin/sail npm install./vendor/bin/sail npm run devThe above steps are automated by run-dev.sh (see docs/run-dev.md). The setup script
performs a related Sail-based flow (including tunnel:sync) but does not run the initial Docker Composer install or the
first-run detection logic.
Open your browser and go to the URL specified in the .env file as APP_URL, or simply go to http://localhost.
You can then login using the test user [email protected] with password password.
Open your browser and go to the URL specified in the .env file as APP_URL with port 8080, or simply go to
http://localhost:8080. This will open Adminer.
Login using the database credentials in the .env file.
Now you can visually inspect & manage your local database.
To provision a server, the server needs to talk to your application. Because you are running locally, this is difficult.
But we can use a cloudflared tunnel for this. Such a tunnel is already running as part of the Docker Compose stack.
Before provisioning a server, you have to run ./vendor/bin/sail artisan tunnel:sync to update the APP_URL based on
the current cloudflared tunnel that has been created (the hostname will change randomly each time the container is
restarted). After that, the generated provisioning script will contain the correct tunneled URL.
This project includes a built-in Netdata dashboard to monitor all your provisioned servers in real-time.
- Dashboard: Access the local Netdata dashboard at
http://localhost:19999. - Security: The connection between your dashboard and the remote servers is secured using a streaming API key
derived from your unique
APP_KEY. No manual configuration is required. - Automated Monitoring Setup: The provisioning script automatically installs a Netdata Agent on the target server. This agent will immediately start streaming performance metrics back to your local dashboard. The agent is configured in "headless" mode, meaning it consumes minimal resources on the target server and does not store data locally.
Configure one or more Cloudflare accounts under Settings → Integrations. Both API Token and Global API Key auth are supported.
use App\Services\Cloudflare\CloudflareService;
use App\Actions\Cloudflare\UpsertCloudflareDnsRecord;
use GuzzleHttp\Psr7\HttpFactory;
use Psr\Http\Client\ClientInterface;
// Build the service from an Integration model
$service = new CloudflareService(
httpClient: app(ClientInterface::class),
httpFactory: new HttpFactory,
credentials: $integration->credentials,
);
// Auto-detect the zone from a domain
$zone = $service->findZoneForDomain($site->domain);
$zoneId = $zone['id'];
// Upsert (create or update) — recommended for site creation
(new UpsertCloudflareDnsRecord)->handle(
service: $service,
zoneId: $zoneId,
name: $site->domain,
type: 'A', // 'A', 'CNAME', or 'both'
content: $server->ip_address,
proxied: true,
ttl: 1, // 1 = Auto
cnameContent: null, // required when type = 'both'
);
// Delete by record ID
$service->deleteDnsRecord(zoneId: $zoneId, recordId: 'rec-abc123');