Skip to content

marko/dev-server

Start your full development environment with a single command. The Dev Server package provides dev:up, dev:down, and dev:status CLI commands that orchestrate your PHP built-in server, Docker Compose services, and frontend build tools together. It auto-detects your project’s Docker Compose file and package manager, so zero configuration is required for most projects. All services are managed as background processes tracked via a PID file, letting you stop everything cleanly with dev:down.

Terminal window
composer require marko/dev-server
Terminal window
marko dev:up
# alias: marko up

This starts all detected services:

  • PHP server --- always started at http://localhost:8000 (serving public/)
  • Docker --- started if a compose.yaml / docker-compose.yml file is found
  • Frontend --- started if package.json has a dev script (uses bun, pnpm, yarn, or npm)

By default dev:up runs in the foreground. Press Ctrl+C to stop all services.

Terminal window
marko dev:up --detach

Starts services in the background. Use dev:status and dev:down to manage them.

Terminal window
marko dev:status

Shows the name, PID, status (running/stopped), port, and start time for each managed process.

Terminal window
marko dev:down
# alias: marko down

Stops all processes started by dev:up --detach.

Terminal window
marko dev:up --port=8080

Overrides the configured port for the PHP built-in server.

Publish or create config/dev.php in your application:

config/dev.php
<?php
declare(strict_types=1);
return [
'port' => 8000,
'detach' => false,
'docker' => true,
'frontend' => true,
'processes' => [],
];
KeyTypeDefaultDescription
portint8000Port for the PHP built-in server
detachboolfalseRun services in background by default
dockertrue|string|falsetrueAuto-detect Docker (true), custom command (string), or disable (false)
frontendtrue|string|falsetrueAuto-detect frontend (true), custom command (string), or disable (false)
processesarray<string, string>[]Named custom processes to run alongside the dev environment

The docker and frontend keys accept three forms:

// Auto-detect (default): scan for compose file / package.json
'docker' => true,
// Custom command: run exactly this
'docker' => 'docker compose -f infrastructure/compose.yaml up -d',
// Disabled: skip entirely
'docker' => false,

Use the processes key to run additional named processes alongside the standard services:

'processes' => [
'tailwind' => './tailwindcss -i src/css/app.css -o public/css/app.css --watch',
'queue' => 'marko queue:work',
],

Each process is managed by ProcessManager --- output is prefixed with the process name (e.g. [tailwind]), and processes are tracked in the PID file when running in detached mode.

Flags passed to dev:up take precedence over config file values:

FlagDescription
--port=NOverride the server port
--detachRun in background (detached mode)
use Marko\Core\Attributes\Command;
use Marko\Core\Command\Input;
use Marko\Core\Command\Output;
#[Command(name: 'dev:up', description: 'Start the development environment', aliases: ['up'])]
public function execute(Input $input, Output $output): int;
use Marko\Core\Attributes\Command;
use Marko\Core\Command\Input;
use Marko\Core\Command\Output;
#[Command(name: 'dev:down', description: 'Stop the development environment', aliases: ['down'])]
public function execute(Input $input, Output $output): int;
use Marko\Core\Attributes\Command;
use Marko\Core\Command\Input;
use Marko\Core\Command\Output;
#[Command(name: 'dev:status', description: 'Show development environment status')]
public function execute(Input $input, Output $output): int;

Scans the project root for Docker Compose files (compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml) and returns the appropriate up/down commands.

use Marko\DevServer\Detection\DockerDetector;
$dockerDetector = new DockerDetector(projectRoot: '/path/to/project');
/** @return array{upCommand: string, downCommand: string}|null */
$dockerDetector->detect();

Checks for a package.json with a dev script and auto-detects the package manager by looking for lock files (bun, pnpm, yarn, npm --- in that order).

use Marko\DevServer\Detection\FrontendDetector;
$frontendDetector = new FrontendDetector(projectRoot: '/path/to/project');
$frontendDetector->detect(); // e.g. 'bun run dev', or null

Manages named background processes with prefixed output streaming and signal handling for graceful shutdown.

use Marko\DevServer\Process\ProcessManager;
/** @throws DevServerException */
$processManager->start(string $name, string $command): int;
$processManager->stop(string $name): void;
$processManager->stopAll(): void;
$processManager->getPid(string $name): ?int;
$processManager->getPids(): array; // array<string, int>
$processManager->isRunning(string $name): bool;
$processManager->runForeground(): void;

Persists process entries to .marko/dev.json for tracking detached processes across commands.

use Marko\DevServer\Process\PidFile;
use Marko\DevServer\Process\ProcessEntry;
/** @param array<ProcessEntry> $entries */
$pidFile->write(array $entries): void;
/** @return array<ProcessEntry> */
$pidFile->read(): array;
$pidFile->clear(): void;
$pidFile->isRunning(int $pid): bool;
use Marko\DevServer\Process\ProcessEntry;
readonly class ProcessEntry
{
public function __construct(
public string $name,
public int $pid,
public string $command,
public int $port,
public string $startedAt,
) {}
}

Extends MarkoException with contextual error messages and suggestions:

  • processFailedToStart(string $name, string $command) --- thrown when a process fails to start. Suggests checking the command and running marko dev:status.
  • portInUse(int $port) --- thrown when the PHP server port is already in use. Suggests using --port=XXXX to pick a different port.