Fixes the largest production noise cluster — **8 duplicate issues**, all
`UnexpectedValueException: The stream or file
"/app/storage/logs/laravel.log" could not be opened in append mode:
Failed to open stream: Permission denied`:
`PHP-LARAVEL-G`, `PHP-LARAVEL-Z`, `PHP-LARAVEL-12`, `PHP-LARAVEL-2P`,
`PHP-LARAVEL-2Q`, `PHP-LARAVEL-2R`, `PHP-LARAVEL-2S`, `PHP-LARAVEL-2T`.
## Root cause
`docker/entrypoint.sh` runs `migrate` / `config:cache` / etc. **as
root** before supervisor starts. With the default `umask 022`, the first
log line written during boot creates `laravel.log` as `root:root 0644`.
The persisted `whisper-storage` named volume
(`docker-compose.production.yml`) keeps that stale, non-group-writable
file across deploys — so the `www-data` php-fpm and queue workers can't
append to it and every code path that logs throws. 8 code paths → 8
Sentry issues.
## Fix
- **entrypoint**: `umask 0002`, pre-create `laravel.log` group-writable
before any artisan command, and normalize it to `0664` in the
post-artisan re-apply step.
- **config/logging.php**: `permission => 0664` on the `single` and
`daily` channels, so files Laravel creates itself (including daily
rotation) stay group-writable.
Net effect: whoever writes first (root at boot or www-data at runtime),
the file is owned `www-data` and group-writable `0664` — no more
`EACCES`.
## Tests
- `single` and `daily` channels expose `permission => 0664`.
- `bash -n docker/entrypoint.sh` passes.
## Follow-up
Once deployed and confirmed quiet, the 8 issues should be merged into
one canonical group in Sentry.
Fixes PHP-LARAVEL-G, PHP-LARAVEL-Z, PHP-LARAVEL-12, PHP-LARAVEL-2P,
PHP-LARAVEL-2Q, PHP-LARAVEL-2R, PHP-LARAVEL-2S, PHP-LARAVEL-2T.
## Problem
Sentry reporting permission-denied errors on `/open-banking/callback`:
> UnexpectedValueException: The stream or file
"/app/storage/logs/laravel.log" could not be opened in append mode:
Failed to open stream: Permission denied
4 issues, 14 events (PHP-LARAVEL-Z, 10, 11, 12).
## Root cause
Entrypoint runs as root. After the initial `chown www-data`, artisan
commands (`config:cache`, `route:cache`, `view:cache`, `event:cache`,
`migrate`) execute and can create/touch `storage/logs/laravel.log` as
root. Later, php-fpm (www-data) tries to append and fails.
Queue workers + inertia-ssr also run as root via supervisor — same
failure mode from worker side.
## Fix
- `docker/entrypoint.sh`: re-chown `/app/storage` and
`/app/bootstrap/cache` to www-data **after** artisan cache warm-up, just
before `exec supervisord`.
- `docker/supervisor/supervisord.conf`: add `user=www-data` to
`queue-worker`, `queue-worker-emails`, `inertia-ssr`. Nginx and php-fpm
master keep root (need port 80 / pool forking).
## Verification
Deploy and confirm Sentry issues stop firing on next
`/open-banking/callback` hits.
- Add production Docker setup for easy self-hosting with the CI-built
image
- Build multi-architecture Docker images (amd64 + arm64)
### Changes
Docker Production Setup
- Add docker-compose.production.yml for running the production image
locally
- Add docker/entrypoint.sh with automatic setup (migrations, caching,
storage, APP_KEY generation)
- Add HEALTHCHECK to Dockerfile for container health monitoring
- Add .env.production.example with documented production defaults
### CI/CD
- Add QEMU for multi-arch builds
- Build Docker images for both linux/amd64 and linux/arm64
### Coolify / WIP
- Add templates/coolify/whisper-money.yaml one-click template
- Includes MySQL service with health checks
- Auto-generates credentials using Coolify's SERVICE_* variables
### Documentation
- Update README with production Docker instructions
- Add environment variable reference table