Deployment
Production build, caching, the route cache trap, queue workers, and web server setup.
Build#
composer install --no-dev --optimize-autoloader
yarn install --frozen-lockfile
yarn build
php artisan migrate --force
php artisan storage:link
php artisan config:cache
php artisan view:cache
php artisan event:cache
The route cache#
php artisan route:cache
This is safe — public routes use an invokable controller, not closures, so they cache correctly. The risk is staleness, not failure.
Once routes are cached, the routes the Hub generates at boot are frozen. If an
administrator then changes a URL in route_map:
- the old URL keeps returning
200 - the new URL returns
404 - nothing is logged
Cogeze handles this: every operation that changes routing structure bumps the route map's
version counter and deletes Laravel's route cache file. The next request re-registers
routes — slightly slower, but correct. Run route:cache again on your next deploy.
If you script deploys, do not restore a route cache file from a build artifact after an administrator has edited URLs.
Optimisation summary#
| Command | Safe to cache | Notes |
|---|---|---|
config:cache |
Yes | Re-run after any .env change. |
view:cache |
Yes | |
event:cache |
Yes | |
route:cache |
Yes, with the caveat above | Cleared automatically when URLs change. |
optimize |
Yes | Runs the above together. |
Queue worker#
php artisan queue:work --tries=3 --max-time=3600
Run it under a supervisor (systemd, supervisord, or your platform's process manager).
--max-time lets the process exit periodically so a deploy picks up new code.
Scheduler#
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1
Required for token pruning and any plugin that schedules work.
Web server#
Point the document root at public/. Standard Laravel configuration applies.
server {
listen 80;
server_name example.com;
root /var/www/cogeze/public;
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Note the catch-all: the Hub serves pages at arbitrary paths, including single-segment URLs
such as /about. try_files … /index.php is what lets that work.
Production checklist#
| Setting | Value | Why |
|---|---|---|
APP_DEBUG |
false |
With debug on, a failing optional need returns 500 instead of degrading. |
APP_ENV |
production |
|
CACHE_STORE |
redis |
The route map and page needs are read on every request. |
SESSION_DRIVER |
redis or database |
|
SANCTUM_STATEFUL_DOMAINS |
the production host | Otherwise admin login returns 419. |
storage/ and bootstrap/cache/ |
writable | |
| Queue worker | running | Otherwise queued mail and media jobs never run. |
Verifying a deploy#
php artisan route:list --path=api/admin | head # admin API is up
curl -o /dev/null -w '%{http_code}\n' https://example.com/ # storefront
curl -o /dev/null -w '%{http_code}\n' https://example.com/nope # themed 404
curl -o /dev/null -w '%{http_code}\n' https://example.com/api/admin/plugins # 401, not 500
The last one is worth checking explicitly. A guest request to an admin API endpoint must
return 401 regardless of the Accept header — a 500 there means the authentication
layer is misconfigured and is masking an auth failure as a server error.
No storefront installed#
If no plugin claims /, the core serves a minimal page with HTTP 503. That is deliberate:
missing configuration is not content, and a 200 would tell search engines the empty page
is the site.