Skip to content

feat(docker): single-command Docker stack for local development - #24

Merged
paulocastellano merged 9 commits into
trypostit:mainfrom
andrefrd:feat/docker-dev-environment
May 17, 2026
Merged

feat(docker): single-command Docker stack for local development#24
paulocastellano merged 9 commits into
trypostit:mainfrom
andrefrd:feat/docker-dev-environment

Conversation

@andrefrd

@andrefrd andrefrd commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a self-contained docker compose up --build workflow that brings the full Laravel + Vite + Reverb + Horizon + scheduler stack online inside one container, plus Postgres 16, Redis 7, and Mailpit. No host PHP / Node prerequisites.
  • Replaces the existing compose.yaml (a stale Sail file pointing at ./vendor/laravel/sail/runtimes/8.5/ — Sail isn't installed and the path doesn't exist) with a self-describing build that uses a new docker/Dockerfile.
  • The same docker/Dockerfile exposes a production target so self-hosters get a hardened image (composer --no-dev, npm production build, SSR build, OpCache locked) from the same source contributors use in dev.
  • Documents the workflow under "Get Started" in the README — common commands, override mechanism, troubleshooting, and the production build hint.

What changed

File Purpose
docker/Dockerfile Multi-stage: shared system-base (PHP 8.4-FPM Alpine + Postgres / Redis / intl / sockets / redis extensions) → composer-deps / composer-deps-prodwayfinder-genasset-builddev / production targets. UID/GID build args.
docker/{nginx,php.dev,php.prod,supervisord.dev,supervisord.prod}.{conf,ini} Sidecar configs. Dev opcache validates timestamps for hot-reload; prod is hardened. Supervisord runs fpm + nginx + reverb + horizon + scheduler (+ vite in dev).
docker/entrypoint.sh Idempotent: seeds .env from .env.docker.example, generates APP_KEY, waits for Postgres, runs migrations, links storage, generates Passport keys, regenerates Wayfinder helpers, and re-installs deps if anonymous volumes were wiped.
docker/.env.docker.example Docker-tuned env template (service-name hosts, sane defaults). Does not touch .env.example, so the existing Herd workflow is untouched.
docker/postgres-init.sh Creates the trypost_test database phpunit.xml expects, on first Postgres boot.
compose.yaml Self-contained dev stack. Bind-mounts source for hot reload; named volumes for vendor/ and node_modules/ so image-baked deps survive. Healthchecks gate app startup until Postgres / Redis are ready. Sets DB_HOST=pgsql, DB_USERNAME=postgres, REDIS_HOST=redis as process env so .env.testing's pre-existing CI defaults don't break in-container tests.
compose.override.yaml.example Per-developer customizations (UID/GID alignment, port conflicts, Xdebug, Selenium for Dusk).
.dockerignore Keeps the build context small and prevents secrets / dev artifacts from leaking into the image.
.gitignore Adds .env.testing.local as a safety net for any per-developer testing override.
README.md New "Run with Docker" section covering single-command boot, common commands, override workflow, troubleshooting (port conflicts, UID on Linux, first-boot expectations), and the production target hint.

Why these specific choices

  • Single container with supervisord instead of separate compose services for fpm / nginx / reverb / horizon / scheduler / vite, because the @laravel/vite-plugin-wayfinder plugin shells out to php artisan at runtime — splitting Vite into a Node-only container would break it. Co-locating mirrors the production supervisord layout and gives one mental model.
  • Postgres 16-alpine matches .env.ci. docker/postgres-init.sh covers the trypost_test database that phpunit.xml pins.
  • .env.docker.example separate from .env.example so Herd / composer dev users keep their flow unchanged. The entrypoint only seeds the Docker variant when TRYPOST_DOCKER_BOOTSTRAP=1 (set by compose).
  • UID/GID build args (default 1000) keep bind-mount writes owned by the host user. Linux contributors on non-1000 systems pass UID=$(id -u) GID=$(id -g).

Test plan

  • docker compose up --build from a fresh clone reaches healthy in ~3-4 min on first boot, seconds on subsequent boots.
  • curl -fsS http://localhost:8000/up → 200.
  • Homepage at http://localhost:8000 renders (302 → /login).
  • Vite dev server reachable at http://localhost:5173/@vite/client → 200.
  • Mailpit UI at http://localhost:8025 → 200.
  • docker compose exec app php artisan test --compact tests/Feature/Auth/PasswordResetTest.php → 5 passed (10 assertions).
  • docker compose exec app php artisan horizon:status → running.
  • docker compose exec app php artisan schedule:list → 4 scheduled commands.
  • docker compose down && docker compose up -d → fast restart, no re-migration thanks to migrate --graceful.
  • docker compose down -v && docker compose up --build → full reset works; composer install + npm ci re-run inside the container.
  • docker build --target production -t trypost:prod -f docker/Dockerfile . → production image builds cleanly without source-mount.
  • compose.yaml validated: docker compose config --quiet exits 0; docker buildx --check reports no warnings.

Out of scope

  • Production deploy guides (Swarm / Kubernetes / Forge). The production target is offered, but orchestration is left to self-hosters.
  • Selenium / Dusk service: omitted from compose.yaml to keep first-boot small. compose.override.yaml.example shows how to add it.
  • CI changes: .env.ci and .github/workflows/ are unchanged.

🤖 Generated with Claude Code

Comment thread docker/.env.docker.example Outdated
Comment thread docker/.env.docker.example Outdated
Comment thread docker/php.dev.ini Outdated
Comment thread docker/php.prod.ini Outdated
Comment thread README.md Outdated
@andrefrd
andrefrd force-pushed the feat/docker-dev-environment branch 4 times, most recently from 8fd34ab to 254fc9f Compare May 12, 2026 23:17
andrefrd added 8 commits May 12, 2026 23:44
Introduces a self-contained Docker build under docker/ that boots the
full Laravel + Vite + Reverb + Horizon + scheduler stack inside a
single container.

The Dockerfile exposes two targets sharing a common system-base layer
(PHP 8.4-FPM Alpine + Postgres/Redis/intl/sockets/redis extensions):

  - dev: bind-mount source at runtime, runs Vite via supervisord, hot
    reloads PHP via opcache.validate_timestamps=1, UID/GID build args
    align container writes with the host user.

  - production: ships the prebuilt application — composer --no-dev,
    npm run build + build:ssr, wayfinder TS pre-generated, OpCache
    hardened, fixed UID 1000.

Sidecar configs (nginx, php.{dev,prod}.ini, supervisord.{dev,prod}.conf,
entrypoint, postgres-init for the test DB) live next to the Dockerfile
so the build context is self-describing. The entrypoint is idempotent
and handles APP_KEY generation, migrations, storage:link, Passport
keys, Wayfinder regen, and dependency reinstall on every boot.
The previous compose.yaml referenced ./vendor/laravel/sail/runtimes/8.5
which was never wired up (sail isn't installed and the runtime path
doesn't exist), so docker compose up failed out of the box. It also
lacked Reverb, queue workers, and the scheduler.

The new compose.yaml:

  - Builds the dev target of docker/Dockerfile so a single
    `docker compose up --build` brings up the entire stack with no host
    PHP or Node prerequisites.
  - Bind-mounts the project root for hot-reload, with named volumes for
    vendor/ and node_modules/ so image-baked deps survive the bind.
  - Pins Postgres to 16-alpine (matches CI) and Redis to 7-alpine.
  - Healthchecks gate `app` startup until pgsql and redis are ready.
  - Sets DB_HOST=pgsql, DB_USERNAME=postgres, REDIS_HOST=redis as
    process env so .env.testing's pre-existing CI defaults
    (DB_HOST=127.0.0.1, DB_USERNAME=root) don't break tests run from
    inside the container.

compose.override.yaml.example documents per-developer customizations
(UID/GID alignment on Linux, port conflicts, Xdebug, Selenium) without
forcing a specific workflow. .env.testing.local is added to .gitignore
as a safety net for any future per-developer testing override.
Adds a "Run with Docker" section between "Get Started" and
"Contributing" covering: single-command boot, common commands,
compose.override.yaml.example, troubleshooting (port conflicts,
UID/GID on Linux, first-boot expectations), and the production image
build hint for self-hosters.
Two adjustments from the PR review.

Stripe placeholders (STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET,
and the eight price IDs) only matter when SELF_HOSTED=false. The
Docker stack defaults to SELF_HOSTED=true, so keeping the block in
.env.docker.example just adds noise for first-time contributors.

upload_max_filesize and post_max_size go from 100M to 1G in both
php.dev.ini and php.prod.ini to accept full-length video uploads.
Bumping nginx client_max_body_size to match — otherwise the proxy
would cut the request off before PHP ever sees it.
User-facing documentation lives in trypostit/trypost-docs. Per
maintainer feedback, the Docker setup walkthrough belongs there
rather than inline in this repo's README, which is meant to stay
focused on product positioning. Compose stack and docker/ assets
stay in place.
- Add libwebp-dev + libavif-dev and --with-webp --with-avif to GD
  configure (fixes imagewebp() undefined at runtime)
- Merge wayfinder-gen into asset-build stage: PHP + Node together
  so @laravel/vite-plugin-wayfinder can invoke artisan during build
- Pass VITE_* as build args and ENV so they bake into the bundle
- Stub APP_URL=http://localhost so artisan boot doesn't crash on null
- mkdir -p storage/framework/{cache/data,sessions,views} storage/logs
  bootstrap/cache before artisan runs (dirs stripped by .dockerignore)
- Production stage: rm vendor + bootstrap/cache/*.php before swapping
  to prod-only deps (prevents missing-provider errors from dev cache)
- Switch production chown from app:app to www-data:www-data to match
  Alpine php-fpm default pool user
Add /app/ (Pusher protocol) and /apps/ (REST API) location blocks
with Upgrade/Connection headers so browsers can connect to the
in-container Reverb server via the same port 80/443 endpoint.
@andrefrd
andrefrd force-pushed the feat/docker-dev-environment branch from 254fc9f to ef501dd Compare May 13, 2026 02:45
@paulocastellano
paulocastellano merged commit bdf5a8a into trypostit:main May 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants