Two containers: PHP and MySQL. Everything is configured in docker-compose.yml and docker/php/.
docker compose up -d --build
./dev composer install
docker compose restart php
./dev artisan migrate
App runs at http://localhost:8000.
docker compose up -d
The entrypoint detects vendor/ and starts serving automatically.
The entrypoint script checks for vendor/ on boot. If it exists, php artisan serve starts in the background. Either way, php-fpm runs as the main process so the container always stays up.
This means on first boot the container is running but not serving until you install dependencies and restart. After that it's fully automatic.
Dependencies aren't installed automatically to avoid modifying composer.lock without your say-so.
Shortcut for running commands in the PHP container. Handles the artisan case by prepending php.
./dev artisan migrate
./dev artisan make:model Foo -m
./dev composer install
./dev composer require foo/bar
./dev php -v
Make it executable after cloning: chmod +x dev.
The database name, user, and password are set in docker-compose.yml. Your .env needs DB_HOST=mysql (the service name, not localhost) and matching credentials.
Data lives in a named volume so it survives restarts. docker compose down -v wipes it.
Everything is scoped by directory name, so projects don't collide. The exception is host ports. Two projects can't both use 8000 simultaneously. Change the port mapping in docker-compose.yml if needed (e.g., "8001:8000").