Laravel
What Zenavo detects
A repository with composer.json (or index.php) builds with zbpack as PHP, and laravel/framework in composer.json marks it as Laravel. The image is the official php:<version>-fpm image with nginx in front, listening on port 8080:
| Step | What happens |
|---|---|
| PHP version | ZBPACK_PHP_VERSION, else the Laravel Sail runtime in docker-compose.yml, else require.php in composer.json; 8 when none is set |
| Extensions | apcu, bcmath, gd, intl, mysqli, opcache, pcntl, pdo_mysql, sysvsem and zip, plus every ext-* in composer.json (add "ext-pdo_pgsql": "*" for PostgreSQL) |
| Install | composer install --optimize-autoloader --classmap-authoritative --no-dev, and npm install when there is a package.json (Node.js 22) |
| Optimize | php artisan optimize, config:cache, event:cache, route:cache, view:cache, then npm run build if package.json has a build script |
| Serve | nginx serves public/ and passes PHP to php-fpm |
Settings for Laravel
The optimize step runs during the build, and builds do not get the service's variables (Build Plan). config:cache would cache a configuration without your database and APP_KEY, and Laravel then ignores the variables at runtime. Turn the build-time optimization off and run it when the service starts instead. Under Settings → Build → zbpack options:
sh
ZBPACK_PHP_OPTIMIZE=false
ZBPACK_BUILD_COMMAND=npm run build
ZBPACK_START_COMMAND=php artisan migrate --force && php artisan optimize && _startupZBPACK_BUILD_COMMANDbuilds the Vite assets, which the optimize step would otherwise have done. Leave it out if the app has no front-end build._startupstarts nginx and php-fpm; it must come last.php artisan optimizecaches the configuration, routes, events and views with the service's variables, and migrations run before the app starts serving, because the build cannot reach the database.
Variables
On the service's Variable tab:
sh
APP_KEY=base64:…
APP_ENV=production
APP_DEBUG=false
APP_URL=${ZENAVO_WEB_URL}
LOG_CHANNEL=stderr
DB_CONNECTION=pgsql
DB_URL=${POSTGRESQL_CONNECTION_STRING}- Create
APP_KEYonce withphp artisan key:generate --showon your computer, and keep it: changing it logs everyone out and makes encrypted data unreadable. LOG_CHANNEL=stderrsends Laravel's logs to Runtime Logs instead of files in the container, which are lost on every deploy.APP_URLuses the domain of the port namedweb; use the name shown on the Networking tab.- For MySQL, use
DB_CONNECTION=mysqlandDB_URL=${MYSQL_CONNECTION_STRING}. Laravel readsDB_URLfrom version 11; older versions useDATABASE_URL. - Redis for cache, sessions and queues:
REDIS_URL=${REDIS_CONNECTION_STRING},CACHE_STORE=redis,SESSION_DRIVER=redis.
Deploy
- Add PostgreSQL (or MySQL) to the project (Databases).
- Add Service → GitHub, pick the repository and branch, add the variables above and click Next.
- Check that the preview shows php, then click Deploy. Set the zbpack options above under Settings → Build and redeploy.
- Generate a domain or bind your own on the Networking tab.
Files and queues
- Uploads in
storage/appare lost on every deploy. Store them in S3-compatible storage (FILESYSTEM_DISK=s3with theAWS_*variables). - Queue workers and the scheduler run as separate services from the same repository: add the repository again with the start command
php artisan queue:work(orphp artisan schedule:work) inZBPACK_START_COMMAND, and no HTTP port.
Common problems
| Symptom | Cause |
|---|---|
No application encryption key has been specified | APP_KEY is missing, or the configuration was cached during the build. Set APP_KEY and ZBPACK_PHP_OPTIMIZE=false. |
The app uses SQLite or localhost instead of the database | The configuration was cached during the build, without the variables. Set ZBPACK_PHP_OPTIMIZE=false and run php artisan optimize at start. |
could not find driver | The PDO extension is missing. Add "ext-pdo_pgsql": "*" to require in composer.json. |
| CSS and JavaScript return 404 | The Vite build did not run. Set ZBPACK_BUILD_COMMAND=npm run build. |
Links and redirects use http:// | Laravel does not trust the proxy in front of it. Trust it in bootstrap/app.php with $middleware->trustProxies(at: '*'). |