Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Docker Nginx Php Laravel Laravel Package

systemsdk/docker-nginx-php-laravel

A ready-to-use Docker environment for Laravel development, bundling Nginx, PHP-FPM, MySQL, Redis, and common tooling. Spin up isolated, reproducible stacks with configurable PHP versions and simple Docker Compose commands for local development and CI.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a Composer package — you do not composer require it, there is no service provider, and nothing lands in vendor/. It is a Docker Compose stack (Nginx + PHP-FPM + MySQL + Redis + MailHog, plus queue/cron workers) that you place next to or around your Laravel codebase. Treat it as project infrastructure you own and edit, not a dependency you upgrade.

Minimal path to a running app:

git clone <your-app-or-this-template> && cd <project>
cp .env.example .env          # compose-level variables, not Laravel's .env
# set COMPOSE_PROJECT_NAME, PHP_VERSION, host ports, APP paths
make build      # or: docker compose build
make up         # or: docker compose up -d
docker compose ps

Then, inside the PHP container:

docker compose exec php-fpm composer install
docker compose exec php-fpm php artisan key:generate
docker compose exec php-fpm php artisan migrate --seed

Hit http://localhost:<NGINX_PORT> and confirm public/index.php renders.

Where to look first, in this order:

  1. Makefile — this is the real user interface of the stack. Read every target before typing raw docker compose commands; you will find the intended build/up/ssh/migrate/test entry points and the exact container names.
  2. .env (compose level) — PHP version, ports, project name, mount paths, Xdebug toggle. This is interpolated into docker-compose.yml, so nothing here takes effect without a recreate.
  3. docker-compose.yml — service names, volumes, healthchecks, depends_on. Note which services mount your source directory and which don't.
  4. docker/ — the PHP Dockerfile, php.ini, Nginx vhost, supervisor programs (worker + scheduler), cron. All customization lives here and survives a rebuild.

First use case: get one authenticated request through Nginx into PHP-FPM into MySQL and back — login, or php artisan tinker reading a seeded record — before touching queues, cron, or Xdebug. That isolates volume-mount and permission problems from application problems.

Implementation Patterns

Mount the code, build the runtime. Application source should be a bind mount so edits are live; vendor/, node_modules/, and storage/ internals are better as named volumes on macOS/Windows to avoid the filesystem penalty and uid/gid fights. When you need a new PHP extension, change the Dockerfile and rebuild — never apt-get install into a running container, because the next make build silently reverts it and the bug reappears only for whoever rebuilt.

Never run php artisan on the host. The host PHP version, extensions, and vendor/ platform requirements will diverge from the container. Standardize on a wrapper — a make artisan target, a dc shell function, or docker compose exec php-fpm php artisan. Enforce it in code review; host-side artisan is the number-one source of "works on my machine" in this setup.

Use exec for interaction, run --rm for one-shots. exec joins a running container and shares its state; run --rm php-fpm php artisan migrate spins up a throwaway container for CI or deploy tasks. Mixing them up is how you end up with migrations run against a stale, half-provisioned environment.

Multiple PHP versions. Build-arg-driven PHP_VERSION means you can validate a Laravel upgrade by rebuilding the same stack on the next PHP minor without touching application code. Give each variant its own COMPOSE_PROJECT_NAME and non-default host ports so two stacks can coexist.

Parallel projects. Because this stack owns fixed ports (80/3306/6379/8025), two checkouts collide out of the box. Always set COMPOSE_PROJECT_NAME, NGINX_PORT, MYSQL_PORT, REDIS_PORT, MAILHOG_PORT per project in .env. This is the single highest-value five minutes of setup.

Workers and scheduler as first-class services. Queue workers belong in a dedicated container under supervisor, with its own queue name so a backed-up queue cannot starve the default. The scheduler container should run schedule:work (or a supervisor-managed schedule:run loop) — not a host crontab, which will diverge on every machine. After deploying or pulling code, restart workers explicitly; long-lived processes keep old code in memory.

CI parity. Run tests through the same image the developers use:

docker compose run --rm php-fpm vendor/bin/phpunit
docker compose run --rm php-fpm vendor/bin/phpstan analyse

This makes "passes locally, fails in CI" almost entirely disappear, because the failure is now about code, not runtime.

MailHog for everything non-production. MAIL_HOST=mailhog, MAIL_PORT=1025. Any mail your app sends lands in the web UI on the MailHog port. If you use the log driver locally, you lose the rendering check and discover broken templates in staging.

Gotchas and Tips

  • config:cache inside the container is the classic footgun. Cached config is written by the container user and contains whatever .env was present at build time. Clear it (php artisan config:clear) when switching between local/staging values, and never build the cache during image build.
  • env() outside config/ returns null once config is cached. Only reference env() inside config/*.php. If a value is fine in tinker but null in a request, this is the cause.
  • Permission errors on storage/ and bootstrap/cache mean the container UID/GID doesn't match yours. Fix it properly with a build arg (UID/GID) plus chown in the entrypoint, rather than chmod -R 777, which hides the problem and creates new ones.
  • .env edits don't hot-reload. Compose interpolates variables at container creation; after changing ports or versions, run docker compose up -d --force-recreate (or make down && make up). "I changed the port and it still uses the old one" is always this.
  • Nginx 502 means PHP-FPM isn't listening or the fastcgi_pass host doesn't match the service name. 404 on every route except / means the try_files rule or root in the vhost is wrong. Blank page with 200 usually means a stale public/index.php mount path.
  • Upload limits are set in two places. Nginx client_max_body_size and PHP upload_max_filesize/post_max_size must both be raised, or large uploads fail differently depending on payload size.
  • Xdebug is a tax, not a default. Keep XDEBUG_MODE=off for normal work; enable debug only when stepping through, and coverage only for the coverage run — coverage mode can multiply test time by an order of magnitude. On Linux host networking, set the client host to your host-gateway address rather than relying on localhost.
  • Workers cache code and config. docker compose exec php-fpm php artisan queue:restart after any deploy where code isn't a live mount. Under long-running workers, a stale worker can process jobs against the previous version of a class.
  • Only one scheduler. If you scale the scheduler container or run it on more than one host, tasks without onOneServer() will double-fire. For local dev, one instance is fine; document the invariant.
  • docker compose down -v destroys the MySQL named volume. Make it a deliberate act, and seed data via migrate --seed or a committed dump so a reset is cheap.
  • MySQL readiness. depends_on alone doesn't wait for the database to accept connections; keep the healthcheck and the condition: service_healthy dependency in the compose file, or migrations will intermittently fail on a cold start.
  • Node assets. Build npm run build inside a Node container or dedicated stage, not on the host, so lockfile and Node version stay aligned with what CI uses.
  • This is a template, not a dependency. There is no composer update path for the stack itself. Track upstream, keep your local diffs confined to docker/ and the compose file, and document each deviation in a short DOCKER.md — otherwise a future merge with upstream is guesswork. Reconcile your fork against upstream releases (e.g. v5.1.0) on a regular cadence, review the compose/Dockerfile diff, and cherry-pick rather than pull.
  • Don't ship this image to production unmodified. It includes dev tooling, Xdebug, and host-mounted code. Reuse the Dockerfile patterns in a leaner production build with --no-dev --optimize-autoloader, baked-in code, and no bind mounts.
  • Compose-level .env and Laravel's .env are different files. Confusing them produces configuration that looks correct in one place and has no effect in the other. Name them distinctly in documentation, and never commit Laravel's .env — commit .env.example, and keep the compose .env.example in sync with any variable you add.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource