A macOS menu bar app that lists the ddev projects on your machine and runs lifecycle commands against them. No dock icon and no main window: the menu bar item is the only entry point.
Built with NativePHP (Laravel, Livewire and Electron).
- Lists every project
ddev listknows about, with its status and primary URL. - Start, stop, restart and delete a project from its row.
ddev powerofffor everything at once.- Search, for when the list runs to dozens of projects.
- Keeps up on its own: start or stop a project in a terminal and the list follows within seconds, whether the popup is open or not.
Status text mirrors ddev list exactly, including the running to OK substitution and
the mutagen suffix. Colour deliberately differs: ddev prints stopped in red, which reads
as "everything is broken" once you have a few dozen projects, so stopped is neutral here and
red is reserved for states that actually need attention.
Deleting a project never passes --omit-snapshot, so a mis-click stays recoverable with
ddev snapshot restore.
- macOS
- ddev on the machine (Homebrew or the install script; the path is auto-detected)
- PHP 8.3 or newer, Composer, and Node 22 for development
This project drives ddev, it is not itself a ddev project, so everything runs against the host toolchain. Electron needs a GUI and the macOS binaries the app shells out to.
Node 22 is required and pinned in .nvmrc. Node 24 silently truncates the zip extractions
NativePHP depends on, and every symptom of that looks like an unrelated bug.
nvm use
composer install
npm install
cp .env.example .env && php artisan key:generate
npm run dev # separate terminal, for Vite
php artisan native:run # needs a TTYPHP changes need native:run restarted. Only JS and CSS hot-reload through Vite.
Tests:
php artisan testddev list inspects every project's containers and takes seconds on a machine with many
projects. NativePHP serves the app with a single-worker php -S, so any blocking call
freezes the whole popup. No ddev command ever runs inside a request: jobs write a snapshot
to the cache, and the Livewire component only ever reads it.
ddev has nothing to subscribe to: it is a one shot CLI, and its hooks live in each project's
.ddev/config.yaml rather than globally. Docker does have an event stream, and ddev labels
every container it creates, so php artisan ddev:watch follows
docker events --filter label=com.ddev.platform=ddev and asks for a new snapshot once the
events settle. It runs as a supervised child process for the life of the app, and costs
nothing while Docker has nothing to report.
Set DDEV_WATCH_ENABLED=false to turn it off and go back to refreshing only when the popup
is opened.
php resources/icons/build-icons.php # then restart native:runpublic/ is the source of truth. resources/icons/app-icon.png produces public/icon.png,
and resources/icons/ddev-mark.svg produces the menu bar template images.
export GITHUB_TOKEN=$(gh auth token)
php artisan native:build mac arm64 --publishReleases are created as drafts on GitHub and have to be published by hand.
MIT.