systemsdk/docker-apache-php-laravel
A ready-to-use Docker environment for Laravel development, combining Apache and PHP with sensible defaults. Spin up a consistent local stack, simplify onboarding, and keep Laravel projects containerized across teams and machines. Ideal for reproducible development and testing.
This is not a Composer dependency — it's a Docker environment template you clone, fork, or copy files from. Nothing in your composer.json changes, so "installing" means pulling the stack files into (or alongside) your Laravel project.
docker group so you don't need sudo, which would create root-owned volumes and bite you later.docker-compose.yml, a Dockerfile or docker/ directory with Apache vhost + PHP ini overrides, an .env/.env.example pair, and often a Makefile or helper scripts. Read docker-compose.yml first — it tells you the service names (app/web, db, phpmyadmin, etc.), ports, volumes, and build args.src/-style directory). The Apache DocumentRoot should resolve to public/..env.example to .env, set APP_URL, APP_KEY (generate it), and — critically — the DB host must be the service name from Compose (e.g. db), not 127.0.0.1. Set DB_PASSWORD/MYSQL_ROOT_PASSWORD consistently between the Laravel and MySQL sections.docker compose up -d --build. First build is slow; it compiles PHP extensions (pdo_mysql, mbstring, xdebug, etc.) and Apache.http://localhost (or whatever APP_PORT is mapped to) and expect your Laravel welcome page. If you see the Apache default page, the document root or bind mount is wrong; if you see a DB connection error, the DB_HOST or init ordering is wrong.Treat the container as your runtime, not your editor. Mount the app as a bind volume for live reload, but run everything — artisan, composer, phpunit, npm — inside the container so PHP versions and extensions match:
docker compose exec app php artisan migrate --seed
docker compose exec app composer require package/name
docker compose run --rm app php artisan tinker # one-off, disposable
Use the Makefile (or add one) if it exists. Most day-to-day friction disappears with aliases like make up, make sh, make artisan, make test. If the repo doesn't ship one, write a two-line wrapper; it's the highest-leverage thing you can do in the first hour.
Layer Compose overrides instead of editing the base file. Put your local tweaks (ports, Xdebug, extra mounts) in docker-compose.override.yml and commit only what the team needs. Keep secrets in an untracked .env, and keep the example's keys stable so onboarding instructions don't rot.
Xdebug is on-demand, not always-on. For step debugging, enable it via an env flag or a dedicated override, set xdebug.client_host=host.docker.internal (Docker Desktop) or your host IP on Linux, and point your IDE at port 9003. Turn it back off — Xdebug's overhead on every request is the single biggest cause of "why is my app so slow" complaints.
Extend the stack by adding services, not rebuilding the image. If you need Redis, MailHog/Mailpit, or Meilisearch, add them to your override file and set REDIS_HOST=redis, MAIL_HOST=mailpit in .env. Don't bake them into the PHP image.
Pin the template. Since this is a copy, not a dependency, record the upstream commit/tag you based your setup on (a line in the README is enough). Upgrading later means diffing against that tag, not composer update.
CI uses the same image. For tests, docker compose run --rm app php artisan test keeps parity with local, but consider building a slimmer image without Xdebug for CI to shave minutes. If the image installs Node, use it to build assets and commit public/build to your deploy artifact.
storage/ and bootstrap/cache/ are unwritable because the container runs as www-data while the bind mount is owned by your host UID. Fix with a UID/GID build arg or an entrypoint chown, not a chmod 777. The latter works and is the wrong answer you'll reach for at 11pm.storage/logs/laravel.log, bootstrap/cache/*.php, and possibly vendor/, those land back on your host through the bind mount. Add them to .gitignore early and be careful about git add -A.DB_HOST is a service name, and the DB isn't ready when app is. Add a healthcheck to MySQL and depends_on: condition: service_healthy (or a retry loop in an entrypoint). Otherwise php artisan migrate fails intermittently on first boot only."8080:80", "3307:3306") and update APP_URL / any GUI client accordingly. The MySQL internal port stays 3306.vendor/ and node_modules/ in anonymous volumes so they aren't synced. On Docker Desktop, try :cached/:delegated or Mutagen if you're desperate..env or config/*.php, run php artisan config:clear (or optimize:clear) inside the container. A stale bootstrap/cache/config.php is the #1 source of "my env var isn't picked up."APP_URL mismatches break everything. If the asset URL or redirect host doesn't match what the browser sees, you get mixed-content errors, broken Vite/Mix assets, and session cookie failures. Set APP_URL to the exact scheme/host/port you browse..ini change. docker compose up -d won't pick up a new Apache vhost or PHP extension; use --build, and --no-cache if you suspect layer caching is hiding a change.docker/ config dir, php.ini drop-ins (for upload_max_filesize, memory_limit), and the entrypoint script for post-startup commands (migrate, queue:work). Fork these rather than patching files inside a running container, which vanishes on rebuild.php.ini values (display_errors, error_reporting) are safe for public traffic.entrypoint.sh may arrive with CRLF and fail with exec format error. Fix with .gitattributes (* text=auto plus a CRLF rule for *.sh) and chmod +x the script.How can I help you explore Laravel packages today?