██╗██╗ ███╗ ███╗ █████╗ ███████╗ ████████╗ ██║█████╗ ████╗ ████║ ██╔══██╗ ██╔════╝ ╚══██╔══╝ ██║████████╗ ██╔████╔██║ ███████║ ███████╗ ██║ ██║█████╔═╝ ██║╚██╔╝██║ ██╔══██║ ╚════██║ ██║ ██║██╔═╝ ██║ ╚═╝ ██║ ██║ ██║ ███████║ ██║ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝
A Linux-first desktop control center for Laravel Sail — now also at home on macOS and Windows.
Manage all your Laravel Sail projects, containers, logs, workers, and development services from one place — without replacing Sail or Docker.
Linux-first · macOS & Windows supported · Open source
mast.sh · Download Mast · Features · Build from source
Starting a workspace in dependency order. Mast waits for each layer to become healthy before starting the next.
Laravel Sail works great from the terminal.
The friction starts when you're working across several Laravel projects at once — each with its own containers, logs, workers, Horizon, Reverb, databases, ports, and commands.
Mast gives your existing Sail projects a single control center.
- See every project and its current state at a glance.
- Start complete development workspaces in dependency order.
- Start, stop, and restart individual projects or services.
- Serve every project at a trusted
https://myapp.testaddress — no more port juggling. - View live logs without hunting through terminal tabs.
- Keep a container's last words when it dies, so the reason outlives the restart.
- Read the application log parsed — each error with its stack trace as one entry.
- See what each project costs in CPU and memory, and what to stop.
- Run Horizon, Reverb, queue workers, and project commands automatically.
- Diagnose and repair common local environment problems, with Fix buttons on failures.
- Share your running app at a temporary public URL — QR code included.
- Switch PHP and Node versions as single, verified operations.
- Copy database credentials and open service dashboards straight from their chips.
- Manage services and environment variables without leaving Mast.
- See what coding agents are running — their worktrees, dev servers, headless browsers — and clean up what they leave behind.
Your existing Sail and Docker Compose workflow remains intact.
Docker remains the source of truth, and Mast stays in sync with changes made from the terminal. Your existing compose.yaml, .env, and Sail commands continue to work normally.
Close Mast and go straight back to the terminal whenever you want.
| Platform | Status |
|---|---|
| 🐧 Linux | Primary platform — tested and supported |
| 🍎 macOS | Tested and supported (unsigned build: see the first-launch note below) |
| 🪟 Windows | Tested and supported (unsigned installer: SmartScreen warns on first run) |
Mast is Linux-first: Linux is the primary development and testing platform. macOS and Windows are fully supported — field-tested against real Sail projects, with prebuilt binaries in every release.
- Docker Engine, reachable by your user
- Docker Compose v2 (
docker compose)
Every release ships the CLI (mast and mast-daemon) alongside the desktop app. To install just the CLI on Linux or macOS:
curl -fsSL https://mast.sh/install | shThe binaries land in ~/.local/bin; no sudo is involved anywhere.
If the desktop app is already installed, the installer matches its version rather than taking the latest release. That is deliberate. The CLI and the app share a per-user daemon socket so that both drive one engine — one mutation owner, one observation loop — and they compare builds when that socket is opened. A mismatched pair refuses to run and says which two things to line up, instead of failing later inside a deserialization error that names nothing useful. Builds agreeing on major and minor are compatible; patch releases never move the wire format.
# Pin a release instead (note sh's -s -- , since the script is on stdin)
curl -fsSL https://mast.sh/install | sh -s -- --version v0.5.0
# Install somewhere else
curl -fsSL https://mast.sh/install | sh -s -- --dir /usr/local/bin
curl -fsSL https://mast.sh/install | MAST_INSTALL_DIR=/usr/local/bin shOn Windows, PowerShell gets its own installer — there is no POSIX shell to pipe into:
irm https://mast.sh/install.ps1 | iexIt installs under %LOCALAPPDATA%\Programs\Mast\bin and adds that to your user PATH; nothing needs administrator rights. Arguments cannot pass through iex, so to pin a release, run the downloaded text as a script block instead:
& ([scriptblock]::Create((irm https://mast.sh/install.ps1))) -Version v0.5.0Prebuilt CLI binaries cover linux-x86_64, linux-aarch64, macos-aarch64, macos-x86_64 and windows-x86_64. ARM Linux builds start at v0.5.0. Windows on ARM installs the x64 binaries, which it runs under emulation.
Download the latest Mast release from:
Download the .deb package and install it with:
sudo apt install ./Mast_<version>_<architecture>.debDownload the AppImage from the latest release. No installation is required.
Make it executable:
chmod +x Mast_<version>_<architecture>.AppImageThen run it:
./Mast_<version>_<architecture>.AppImageAfter starting Mast, open Settings, add the directories containing your projects, and import the projects Mast discovers.
Omarchy is Arch underneath, so the AppImage is the build to use. Install the CLI as above, then add the Mast bar widget — the running project count in the bar, and a panel to start, stop, restart and open projects:
omarchy plugin add https://github.com/stackrats/omarchy-mast.git --enableThe widget needs a mast CLI that supports mast status --json.
Download the .dmg for your chip (Apple Silicon aarch64 or Intel x64) from the latest release, open it, and drag Mast into Applications as normal.
The build is unsigned, so the first launch is a two-step dance with Gatekeeper:
- Open Mast — macOS warns that it cannot verify the app and refuses to open it.
- Go to System Settings → Privacy & Security, scroll down to the note about Mast being blocked, and click Open Anyway — then confirm in the dialog.
macOS remembers the choice; every later launch is normal.
Works with Docker Desktop, OrbStack and colima — Mast finds the docker CLI even when the app is launched from Finder.
Download the NSIS installer (Mast_<version>_x64-setup.exe) from the latest release. The installer is unsigned: SmartScreen will warn on first run (More info → Run anyway).
Requirements and notes:
- Docker Desktop (WSL2 backend) — Mast observes containers live over its named pipe and drives Sail projects through
docker composedirectly (thesailwrapper is a bash script Windows cannot run; Mast handles the translation, includingsail …custom commands). - Keep projects on a plain local path like
C:\dev\myapp— OneDrive-synced folders fight bind mounts, and projects living inside WSL2 (\\wsl$\…) are not supported yet. - Fresh projects created by Mast's wizard are configured for Windows automatically (ports, writable storage, in-container PHP user). For projects created elsewhere, the Diagnose button knows the Windows-specific traps and fixes each with one click.
Mast automatically discovers Laravel Sail and Docker Compose projects from the directories you configure.
Removing a watched directory also removes its imported projects from Mast unless another watched directory covers them. Managed commands stop before removal; project files remain on disk. A moved or missing project folder shows a path error until it is restored or its new location is imported.
Every project shows its services, application processes, saved commands, and live container state.
Start, stop, or restart an entire project or an individual service.
Mast reads the real Docker state rather than maintaining a separate idea of what should be running.
A service's chip also knows what the service is for. Mailpit, Meilisearch, or the MinIO console open in your browser from the chip — on whatever port they actually publish, so nobody memorises that Mailpit lives on 8025. A database chip offers Connection info: host, port, database, username, and password read fresh from .env, each with a copy button, plus a ready-made connection URL for TablePlus or DBeaver — and an Open in database client button that hands that URL straight to whichever client owns the scheme on your machine.
A Tinker button opens the REPL in the app container — no PHP needed on the host. Right-clicking any project in the sidebar offers start/stop/restart and the open-in verbs without leaving the list, and right-clicking a workspace offers start/stop/edit the same way; Ctrl+1–9 jumps straight to a project, a filter box narrows the sidebar by typing, and workspaces — or the whole Workspaces/Projects sections — fold shut and stay that way (a live filter always shows its matches, folded or not). Drag any row to reorder it, drop a project onto a workspace to add it, or onto the Projects heading to take it back out.
Press ? for the full list, written in your platform's own modifiers:
Group related projects into workspaces and start them together.
Dependencies can be defined between workspace members. Mast starts each layer in order and waits for its dependencies to become healthy before continuing.
That means a workspace containing an API, frontend, and supporting applications can be brought up with one action instead of manually starting each repository.
Run the processes that belong to your Laravel application alongside its containers.
Mast can automatically start:
- Reverb
- Horizon
- Queue workers
- Scheduler
- Saved project commands
These processes remain attached to the project they belong to rather than disappearing into another terminal window.
Saved commands can also run somewhere else: give one a working directory like ../frontend and your separate frontend's dev server lives on the same card — streamed, stoppable, and startable together with the backend it belongs to.
Queue workers, Horizon, the scheduler, and Reverb started through Mast restart when application code or configuration changes. Mast waits for the old process to stop inside its container before starting its replacement. Dependency directories, generated caches, and logs do not trigger these restarts.
Saved command menus include Restart, which waits for the current command to stop before running it again. Starting, stopping, and restarting commands show a pending indicator, and failed commands show a red status dot.
A command can auto-restart: any exit you didn't ask for relaunches it, with backoff, and a crash loop stops instead of hammering the same failure. Add restart-on-change globs — app/** config/** — and the command is stopped and relaunched when matching files change, which is exactly what a queue worker needs: it never sees new code until it restarts. Supervisor semantics, locally, with the output still streaming into the same panel.
Commands can live in a mast.yml committed at the project root instead of on one person's machine:
commands:
vite:
command: sail npm run dev
auto_start: true
ready_when: "ready in"
queue:
command: sail artisan queue:work
auto_start: true
after: vite
auto_restart: true
restart_when_changed:
- app/**
- config/**Clone, import, done — everyone gets the same commands, ordering, and readiness. Saving a command under the same name overrides the shared one locally, and the Share chip writes your current saved commands into a first mast.yml for you. Mistakes in the file surface as project warnings that name the line's problem; they never stop the project from loading.
Agents like Claude Code and Codex work on several things at once by giving each task its own git worktree, and from there they start things: a sail up that brings up a second stack, an npm run dev, a headless browser for a test. Those keep running after the session that started them ends, holding memory and ports nobody is using.
The Agents page shows all of it across your projects:
- Agent sessions working right now, in the checkout or a worktree, with what each has running.
- Left running — dev servers, workers,
sail/dockerclients and headless browsers whose session has ended, with their memory. The sidebar badge counts them. - In the app container — process trees Mast didn't start, from the card's own list.
- Worktrees, wherever they live (
.claude/worktrees/, Codex's and Cursor's folders, or anywheregit worktree addput them): branch, uncommitted changes, commits the checkout doesn't have, and any containers started from them — which usually want the same ports as your project.
Stop anything individually, stop or remove a worktree's containers, or remove a worktree: Mast stops what runs in it and removes its containers first, keeps its branch, and asks separately before discarding uncommitted changes. It refuses while an agent is still working there. Clean up lists every leftover with what nothing live is using already ticked, so one review clears it all; worktrees themselves are never removed from there.
Each project's card carries the same section for that project, with its own Clean up, so you see an agent's leftovers where you were already looking.
Processes on the host are watched on Linux; worktrees and their containers are shown on every platform.
sail share, as a button. Publish the running app at a temporary public URL, scan the QR code to open it on your phone, and keep the tunnel's own output in view — the place where share problems actually explain themselves.
Mast shows exactly what will be shared before anything starts, warns when a Vite dev server would break the shared page's assets, picks a free dashboard port instead of dying on a busy one, and reliably stops the tunnel when you do.
Trusted HTTPS at https://myapp.test
Stop typing localhost:8082. Give a project a .test domain and Mast serves it over HTTPS through one shared local proxy — with a certificate your browser trusts, from an authority that exists only on your machine.
That unlocks the parts of development that plain http://localhost quietly breaks: secure cookies, service workers, camera and clipboard APIs, and OAuth callbacks that insist on HTTPS. The address also never changes when ports move.
Projects running Reverb get their WebSocket on the same address: upgrades on wss://myapp.test/app go to the port the app container publishes for Reverb, while ordinary /app/… pages still reach the app. Sail doesn't publish that port by default, and Mast says so when it's missing. The proxy follows port changes on its own.
Mast asks before touching anything system-level. The two one-time steps — a line in /etc/hosts and trusting the certificate authority — appear as previewed Fix buttons, each showing exactly what will change before an elevation prompt runs it.
Prefer doing it yourself? The dialog shows the exact hosts line and the certificate — path and PEM, each copyable — with a button that opens your hosts file.
Follow any service's logs from inside Mast, without keeping a terminal tab open for each one.
A live log stream ends when its container does, and that is usually the moment the output becomes interesting. Worse, recreating a container — which up -d does whenever its configuration changed — starts a fresh log and discards the previous one entirely.
So Mast keeps the last minute of a container's output whenever it goes down:
- Before Mast stops, restarts, or rebuilds it — taken before the command runs, because a rebuild removes the container.
- When it exits or turns unhealthy on its own, including while Mast was closed.
- When a workspace start gives up waiting for it to become ready.
- On demand, from the service menu.
Captures are written to disk, so they survive both the container being replaced and Mast being restarted. Each one records why it was taken, and is listed newest first with a copy button.
Values that look like secrets in your .env are removed before a capture is stored — unlike a live stream, a capture is persisted and copyable.
And container logs are only half the story: the application confesses in storage/logs/laravel.log. The App log button shows it parsed — each error with its stack trace grouped as one entry, level badges, and an errors-only filter — instead of two hundred raw lines in an editor tab. Every file a trace names is a link: click /var/www/html/app/Jobs/Ship.php(25) in a stack trace — or in a capture — and your editor opens that file at that line. When the history outlives its usefulness, one button clears the file in place.
See what your containers actually cost, per service, per project, per workspace, and in total.
CPU is reported in cores rather than a percentage. Docker's percentage is a share of every core at once, so 800% is a real reading on an eight-core machine and a progress bar cannot express it. 2.3 of 8 cores needs no explanation.
Memory is the working set — reclaimable page cache excluded, the same figure docker stats shows. A raw cgroup reading counts cache and can overstate a database container by an order of magnitude.
Where a container has a memory limit, Mast shows how close it is to it, which is the number that predicts an out-of-memory kill. Where there is no limit, the same bar shows its share of the machine instead.
The Resources tab ranks every running service across every project, so the answer to "what do I stop" is the top row. Sort by service, CPU, or memory by clicking a column.
Short histories are kept alongside each reading, because one CPU number cannot tell a momentary spike apart from a steady climb.
Sampling only runs while the Mast window is visible. Measuring the machine costs CPU, and a minimised Mast should not be part of the problem it reports on.
You don't need to keep the Mast window open.
The system tray exposes the same project and workspace controls, so starting or stopping an environment is only a couple of clicks away.
Local Docker environments break.
Mast checks for common problems and presents each finding with a guided repair.
Every repair has a risk tier and a preview of what Mast intends to change before anything is written.
The checks go after the problems that actually eat afternoons:
- Database says Access denied after you changed
.env? Mast fixes the live server without touching your data. - Bumped a database version its data can't follow? Mast refuses the change that would crash-loop, and explains the safe path.
- Edited
.envand nothing happened? Mast points at the cached config, or the exact containers that need recreating. - Breakpoints never hit? The Xdebug check names which link in the chain is broken — one of them is a one-click fix.
- Pages suddenly have no CSS and rebuilding changes nothing? A leftover Vite dev-server file, deleted in one click.
- Still on MailHog, or files in
storage/owned by root? One step each.
And when an operation fails, Mast reads the output for known failure shapes and says what likely went wrong and how to fix it — where a repair applies, the failure carries its own Fix button, previewed before anything changes.
Add common development services without manually rebuilding Compose configuration from scratch.
Mast matches services against your existing Compose file and can expose available registry versions where applicable.
Insurance against the -v flag. Any service with named volumes — MySQL, Postgres, Redis, Meilisearch — gets Data snapshots on its chip: one click copies its volumes (container briefly stopped for a clean copy, restarted after) into labeled docker volumes on your machine.
Restore is deliberately harder than take: it's a two-step confirm that offers to snapshot the current data first, checked by default. Snapshots are plain docker volumes with mast.* labels — they survive reinstalls, show up in docker volume ls, and delete like anything else.
Taking one also works from the CLI (mast snapshot <project> <service>, mast snapshots <project>) and from coding agents via MCP — an agent can buy insurance before running your migration, but restoring stays in the app, behind its confirm.
Pick another PHP runtime from the project's Runtimes row and Mast runs the whole switch as one operation: the build context and the sail-X.Y/app image tag move together, the image rebuilds without cache, the container is recreated if it was running — and php -v inside the container has the last word.
Node gets the same treatment: pick a major, Mast pins the build argument, rebuilds, and node -v confirms it.
A version that can't work is refused up front, naming the ones that can.
The PHP chip also answers what the runtime actually is: the classic limits (memory_limit, upload sizes, execution time) and every loaded extension, read live from the running container. Buttons open the runtime's php.ini or Dockerfile in your editor, and Rebuild to apply makes the edit real — no hunting for which file feeds the image.
Edit .env values directly from the project view.
Sensitive values remain masked while Mast edits the existing file in place.
Mast can bootstrap new Laravel applications using containers.
You don't need a local PHP installation just to create the project that will ultimately run inside Docker anyway.
The same dialog takes a git URL. Mast clones it, then bootstraps exactly what a fresh clone is missing — containerized composer install, .env from .env.example, an app key — and imports it, committed mast.yml commands included. Whatever's still missing (Sail itself, node modules) shows up in Diagnostics with a one-click fix. URLs with embedded credentials are refused: effect history records every command verbatim, and a token must never land there.
Put an Open in Mast link in your repo's README and onboarding becomes one click:
mast://clone?url=git%40github.com%3Aacme%2Fshop.git
opens the dialog prefilled (mast://project/<name> jumps to an existing project). Links can only navigate or prefill — never clone, start, or stop anything by themselves — because any webpage can fire a registered URL scheme, and a link that acted on its own would be remote control of your machine. You still click the button.
Mast isn't limited to the desktop interface.
The mast CLI talks to the same engine as the desktop application and system tray.
mast status
mast start <project>
mast stop <project>
mast restart <project>
mast diagnose
mast history
mast agentsControl an individual service with:
mast start <project> --service <name>Projects can be matched by name or path fragment.
mast status --json prints the same picture as one JSON document — the engine snapshot the desktop app reads, camelCase keys and all — for scripts and desktop widgets such as the Omarchy bar plugin.
When the desktop application or daemon is running, the CLI connects to it over IPC.
Otherwise, the CLI starts its own embedded engine. If another Mast process already holds the ownership lock, that engine operates read-only.
Coding agents can use Mast as their window into your dev environment instead of shelling docker compose blind. Register the command below as an stdio MCP server in your agent's MCP settings:
mast mcpIt speaks the Model Context Protocol over stdio, talking to the same engine as the app. An agent gets mast_status (projects, services, health, ports), mast_logs, mast_laravel_log (parsed, stack traces included), mast_captures (a dead container's last words), mast_diagnose, mast_history, mast_wait, the lifecycle verbs, and mast_run_command — which runs your saved commands only, so the command list you curated stays the consent boundary.
mast_agents shows an agent what is already running — its own leftovers, another session's dev server, a worktree's stack holding the ports — and the server asks agents to check it before starting one and again before they finish. It only reads: stopping and removing happen in the app.
Mast sits on top of your existing Laravel Sail and Docker Compose workflow.
It doesn't create a proprietary environment that your projects need to be migrated into.
Three principles guide how Mast interacts with your development environment:
Operations performed by Mast correspond to the commands you would otherwise run yourself.
Mast asks Docker what's actually running rather than relying on cached application state.
Changes made outside Mast remain visible inside Mast.
When Mast changes project configuration, writes are validated rather than treated as arbitrary text manipulation.
The goal is simple:
Mast should make Sail easier to operate without making your projects dependent on Mast.
Mast is built with Rust, Tauri 2, and Vue 3.
A single Rust engine owns application state, and every client communicates with it through the same MastClient trait.
Desktop App ─┐
System Tray ─┼─ MastClient ── mast-engine ── Docker / Compose / Laravel
mast CLI ────┘ in-process, or over an IPC socket to the owning process
Whichever process holds the engine serves a Unix socket.
This allows the CLI to retain full control while the desktop application is running while still allowing either client to operate independently.
A headless mast-daemon binary serves the same socket without requiring the GUI.
Architecture decisions and their reasoning are documented in:
docs/adr/
Install the required system packages:
sudo apt update
sudo apt install \
build-essential \
curl \
libwebkit2gtk-4.1-dev \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-devInstall Rust and Vite+:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
curl -fsSL https://vite.plus | bashOpen a new terminal, then install the Tauri CLI:
cargo install tauri-cli --version '^2.0.0' --lockedClone Mast:
git clone <repository>
cd mastInstall dependencies:
vp installBuild the desktop application:
cargo tauri buildConfigured Linux bundle targets are:
.deb.rpm- AppImage
Bundles are written to:
target/release/bundle/
Build the optional CLI separately:
cargo build --release -p mast-cliThe resulting binary is:
target/release/mast
Build only the Debian bundle:
cargo tauri build --bundles debThe package is written to:
target/release/bundle/deb/Mast_<version>_<architecture>.deb
Install it with:
sudo apt install ./target/release/bundle/deb/Mast_<version>_<architecture>.debThe desktop bundle installs the GUI only.
The mast CLI can be distributed separately from:
target/release/mast
or bundled using externalBin in tauri.conf.json.
If the build fails with:
Can't detect any appindicator library
install the development package:
sudo apt install libayatana-appindicator3-devThe runtime library alone is not sufficient because Tauri locates AppIndicator using pkg-config.
Cross-architecture builds are not currently configured.
Build arm64 packages on an arm64 host or inside a matching environment with the same build dependencies installed.
Install dependencies after cloning or pulling changes:
vp installStart the desktop application from the repository root:
cargo tauri devTauri automatically starts the frontend development server (vp dev) on port 1420 and launches the desktop application.
If you need to run the frontend separately:
cd clients/desktop-vue
vp devDo not run vp dev from the repository root.
The root vite.config.ts configures the Vite+ workspace, while the application configuration lives in:
clients/desktop-vue/
If Tauri remains on:
Waiting for your frontend dev server
clear any stale Vite+ process holding port 1420:
pkill -f vite-plus-coreThen run:
cargo tauri devRun the Rust workspace tests:
cargo test --workspaceRun the frontend tests:
cd clients/desktop-vue
vp test runTests that require Docker skip cleanly when Docker is unavailable.
Fixtures under fixtures/ contain deliberate formatting traps used to prove byte-faithful editing.
Do not reformat them.
Issues, bug reports, and contributions are welcome.
Mast is particularly interested in feedback from developers using Laravel Sail on Linux and developers working across multiple local Laravel projects.
If Mast doesn't understand something about your existing Sail or Compose setup, open an issue with the relevant configuration and expected behaviour.











