Deploying Modern Laravel 11 with Octane and PostgreSQL
Laravel 11 is the most polished PHP framework release to date, and Octane supercharges it by keeping the application in memory between requests, delivering 5 to 10x throughput improvements over traditional PHP-FPM. Together, they make Laravel viable for high-traffic applications that previously required Node.js or Go. But Octane's architecture (long-lived workers holding application state in memory) is fundamentally incompatible with serverless platforms, which spin processes down between requests. Deployxa's persistent containers are the natural home for Laravel Octane apps, and the zero-config engine handles the PHP runtime, Postgres connection, and Octane's RoadRunner or Swoole configuration automatically. Here is how to deploy a modern Laravel 11 app with Octane and PostgreSQL in minutes.
The direct answer is that Laravel Octane keeps the application bootstrap in memory and reuses it across requests, which eliminates the per-request overhead of bootstrapping the framework. This is great for performance, but it requires a persistent process, because the in-memory state must survive between requests. Serverless platforms, which spin processes down when idle, cannot provide this. Deployxa's persistent containers keep the Octane worker alive indefinitely, which means the performance benefits of Octane are fully realized.
Why Laravel Octane Needs Persistent Containers
Three properties of Octane make persistent containers essential:
1. In-memory application state
Octane keeps the Laravel application bootstrap (service provider registration, container bindings, configuration loading) in memory between requests. On the first request, the application bootstraps (which takes 50 to 200ms). On subsequent requests, the bootstrap is reused, so request handling is much faster (5 to 20ms). This performance gain depends on the process staying alive, because if the process is killed, the in-memory bootstrap is lost, and the next request has to bootstrap again.
2. Long-lived workers
Octane uses a worker process model (via RoadRunner or Swoole). Workers are started once and kept alive, handling requests as they come in. This is different from traditional PHP-FPM, which spawns a new worker per request. The worker model is more efficient, but it requires a persistent process, because workers must stay alive to handle the next request.
3. Stateful features
Octane supports stateful features like static caches, in-memory session storage, and persistent database connections. These features assume the process stays alive, because the state lives in the process's memory. If the process is killed, the state is lost, and the next request starts fresh.
Serverless platforms, which spin processes down when idle and spawn new ones for each request, cannot provide any of these properties. A Laravel Octane app on a serverless platform either does not work at all (because the platform does not support long-lived workers), or works with degraded performance (because the in-memory state is lost on every cold start).
The Specific Numbers
Concrete benchmark numbers illustrate the difference. A typical Laravel 11 app with Eloquent ORM, a Postgres connection, and a Redis cache, running on a 512MB container:
- PHP-FPM (traditional): 50-200 requests per second, average response time 80-250ms
- Octane with RoadRunner: 500-2000 requests per second, average response time 5-30ms
- Octane with Swoole: 600-2500 requests per second, average response time 4-25ms
The Octane numbers assume a warm worker (in-memory bootstrap). On a cold start (worker just spawned), the first request takes 100-200ms, then subsequent requests are fast. On a persistent container, the cold start happens once per deploy, then all subsequent requests are warm. On a serverless platform, every request after an idle period is a cold start, which eliminates the Octane advantage.
How Deployxa Handles Laravel Octane
Deployxa's ingestion service detects Laravel from your composer.json and detects Octane from the laravel/octane dependency. It configures the build and start commands automatically:
- Build command: composer install --no-dev --optimize-autoloader && php artisan config:cache && php artisan route:cache && php artisan view:cache
- Start command: php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=$PORT
- Runtime: PHP 8.3 with the necessary extensions (mbstring, pdo, pdo_pgsql, redis, etc.)
The start command uses RoadRunner by default, which is the recommended Octane server for production. RoadRunner is a Go-based application server that manages PHP workers efficiently, with built-in HTTP handling, static file serving, and health checks. Deployxa's Traefik v3 reverse proxy routes traffic to RoadRunner, which distributes it to the PHP worker pool.
RoadRunner vs Swoole: Why RoadRunner by Default
Deployxa defaults to RoadRunner for several reasons. First, RoadRunner is a standalone Go binary that manages PHP workers, which means it does not require a PHP extension (unlike Swoole, which requires the swoole extension compiled into PHP). This makes the build simpler and more portable. Second, RoadRunner's worker management is more predictable: it spawns a fixed pool of workers (configurable via .rr.yaml), and each worker handles requests sequentially. Swoole uses a coroutine model, which is faster but harder to reason about, especially for code that was not written with coroutines in mind. Third, RoadRunner's static file serving is built-in, which means Traefik can route static asset requests directly to RoadRunner without a separate Nginx. Swoole requires a separate static file server.
If your app specifically requires Swoole (e.g., you are using Laravel Octane's Swoole-specific features like WebSocket support via Swoole's HTTP server), you can override the start command in the Deployxa dashboard to use --server=swoole. The ingestion service will detect the ext-swoole requirement in composer.json and install the Swoole extension.
Step-by-Step: Deploying a Laravel 11 Octane App
Here is the exact workflow for a typical Laravel 11 app with Octane and PostgreSQL.
Step 1: Create your Laravel app
If you do not have one yet, create a new Laravel 11 app:
composer create-project laravel/laravel my-app
cd my-app
composer require laravel/octane spiral/roadrunner
php artisan octane:installStep 2: Configure the database
In your .env file, set the database connection to Postgres:
DB_CONNECTION=pgsql
DB_HOST=your-postgres-host
DB_PORT=5432
DB_DATABASE=your-database
DB_USERNAME=your-username
DB_PASSWORD=your-passwordStep 3: Push to GitHub
git init
git add .
git commit -m "laravel 11 with octane"
git remote add origin https://github.com/yourname/my-app.git
git push -u origin mainStep 4: Connect to Deployxa
In the Deployxa dashboard, connect your repository. Deployxa auto-detects Laravel and Octane:
[ingest] Detected PHP project
[ingest] Framework: laravel
[ingest] Runtime: php 8.3
[ingest] Octane detected, using RoadRunner server
[ingest] Build command: composer install --no-dev --optimize-autoloader && php artisan config:cache && php artisan route:cache && php artisan view:cache
[ingest] Start command: php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=$PORTStep 5: Configure environment variables
In the Deployxa dashboard, add your production environment variables:
- APP_KEY (generate with php artisan key:generate --show)
- APP_ENV=production
- APP_DEBUG=false
- DB_CONNECTION=pgsql
- DB_HOST=your-postgres-host
- DB_PORT=5432
- DB_DATABASE=your-database
- DB_USERNAME=your-username
- DB_PASSWORD=your-password
The pre-flight scanner will warn you about any that are clearly required but missing.
Step 6: Deploy
Click Deploy. The build runs composer install, caches the config, routes, and views, and starts the Octane server with RoadRunner. The container is live within 60 to 90 seconds.
Step 7: Run migrations
After the first deployment, run your database migrations:
php artisan migrate --forceYou can do this via the Deployxa CLI or by connecting the migration command to the deployment process.
Step 8: Add a custom domain
In the Deployxa dashboard, add a custom domain. SSL is provisioned automatically via Let's Encrypt.
Step 9: Verify with deployxa doctor
Run deployxa doctor to verify health. The 14-point readiness engine checks SSL, DNS, environment variables, health endpoints, and container status.
Common Pitfalls
Four pitfalls appear in Laravel Octane deployments. First, memory leaks from static state. Octane keeps the application in memory, which means static variables, singletons, and in-memory caches persist between requests. If your code accumulates data in a static variable (e.g., a query log), it will grow indefinitely and OOM-kill the worker. Use Octane::tick() to periodically clear caches, or avoid static state entirely. Second, leaked Eloquent builders. Eloquent query builders hold references to the database connection, and if they are stored in a static variable, the connection is never released. Use unset() or scope your queries to method-local variables. Third, Octane-incompatible packages. Some packages assume a fresh bootstrap per request (e.g., packages that register route bindings at boot time). Check the package's Octane compatibility before installing. The Laravel Octane documentation maintains a list of known-incompatible packages. Fourth, missing PHP extensions. The default PHP 8.3 image includes the common extensions (mbstring, pdo, pdo_pgsql, redis, gd, bcmath, xml, ctype, json, tokenizer), but if your app requires a less-common extension (e.g., imagick, intl, soap), add it to your composer.json as ext-imagick etc., and the ingestion service will install it.
Troubleshooting: Common Laravel Octane Errors
Below are common errors and their interpretations.
Error: RoadRunner server failed to start: address already in useAnother process is using the assigned port. This should not happen on Deployxa (each container gets its own port), but if you overrode the start command with a hardcoded port, change it to $PORT.
Error: Class "PDO" not foundThe pdo and pdo_pgsql PHP extensions are not installed. Add ext-pdo and ext-pdo_pgsql to your composer.json requirements.
Error: SQLSTATE[08006] [7] could not connect to server: Connection refusedThe Postgres database is unreachable. Check DB_HOST, DB_PORT, and that the database server is running.
Error: RoadRunner: worker terminated due to memory limitThe worker exceeded the memory limit. Either fix the memory leak (see Common Pitfalls), or increase the container's memory in the Deployxa dashboard.
Error: Octane: Request timeout (30s)A request took longer than 30 seconds. RoadRunner's default request timeout is 30s. Increase it in .rr.yaml with http.pool.request_timeout: 60s.
Performance: Octane vs Traditional PHP-FPM
The performance difference between Octane and traditional PHP-FPM is significant. A typical Laravel app on PHP-FPM handles 50 to 200 requests per second on a single container. The same app on Octane handles 500 to 2000 requests per second, a 5 to 10x improvement. The exact numbers depend on the app's complexity, database queries, and external API calls, but the order-of-magnitude improvement is consistent.
This performance gain comes from eliminating the per-request bootstrap overhead. On PHP-FPM, every request bootstraps the Laravel application (load config, register service providers, boot the container), which takes 50 to 200ms. On Octane, the bootstrap happens once when the worker starts, and subsequent requests reuse it, so request handling is much faster.
The trade-off is that Octane requires careful handling of stateful features. Static variables, singletons, and in-memory caches persist between requests, which can cause bugs if you assume they are reset per request. Laravel's documentation covers this in detail, and the Octane-specific testing tools help catch these issues.
Benchmarking Your Own App
To benchmark your Laravel app on Deployxa, use wrk or ab against your Deployxa URL:
# Using wrk
wrk -t4 -c100 -d30s https://my-app.deployxa.app/
# Using ab
ab -n 10000 -c 100 https://my-app.deployxa.app/Run the same benchmark against the PHP-FPM version (temporarily disable Octane by overriding the start command to php artisan serve --host=0.0.0.0 --port=$PORT) and compare. Typical results show Octane at 5-10x the throughput of PHP-FPM for the same app.
When to Use Octane vs Traditional PHP-FPM
Octane is the right choice for high-traffic Laravel apps where performance matters. For low-traffic apps or apps with simple request handling, traditional PHP-FPM is fine and simpler. The decision depends on your traffic patterns and performance requirements.
For AI-generated Laravel apps, Octane is a good default, because the LLM typically generates a clean, modern Laravel 11 app that is compatible with Octane. The performance gain is free, and the stateful-feature pitfalls are manageable with proper testing.
When Octane Is Not the Right Choice
Octane is not the right choice when: your app is low-traffic (under 100 requests per minute), and the bootstrap overhead is negligible; your app uses many Octane-incompatible packages (the cost of replacing them exceeds the performance gain); your team is unfamiliar with Octane's stateful-feature pitfalls and you ship bugs because of leaked state; or your app is a long-running console application (Octane is for HTTP requests, not Artisan commands). For these cases, traditional PHP-FPM via php artisan serve or a PHP-FPM container is simpler and sufficient.
The Pricing Reality: Persistent Containers for Laravel
Deployxa's persistent containers are priced by provisioned resources, not by request. For a Laravel Octane app, this means you pay for the container whether it is handling 1 request or 1000. The free tier (3 apps, 512MB RAM) is enough to run a small Laravel app with light traffic. The paid tier starts at $9 per month for 15 apps, with predictable pricing.
Compare this to traditional PHP hosting, which often charges per request or per visitor, and the value of predictable pricing becomes clear. For high-traffic Laravel apps, the combination of Octane's performance and Deployxa's persistent containers is hard to beat.
Cost Comparison: Laravel Hosting Options
| Provider | Pricing model | Cost for a small Laravel app (1000 req/day) | Cost for a high-traffic app (100k req/day) |
|---|---|---|---|
| Deployxa Free | Flat | $0 | $0 (within free tier limits) |
| Deployxa Paid | Flat | $9/mo | $9/mo |
| Laravel Forge + DigitalOcean | Flat (per server) | $12/mo ($9 Forge + $6 DO droplet) | $24-48/mo (larger droplet) |
| Vapor (Laravel serverless on AWS) | Per request + AWS usage | $20-40/mo (Vapor plan + Lambda + RDS) | $100-300/mo (heavy Lambda usage) |
| Heroku | Per dyno | $7-25/mo | $50-200/mo (multiple dynos) |
| Shared PHP hosting (cPanel) | Flat | $5-15/mo | Not viable (shared hosting cannot handle the traffic) |
For a vibe coder running a single small Laravel app, Deployxa Free or Paid is the cheapest option. For a high-traffic production app, Deployxa Paid at $9/month flat is competitive with the cheapest alternatives, and significantly cheaper than serverless (Vapor) at scale.
Conclusion: Octane Needs a Persistent Home
Laravel Octane is a powerful performance tool, but it requires persistent containers to deliver its benefits. Serverless platforms cannot provide the long-lived workers and in-memory state that Octane depends on. Deployxa's persistent containers, combined with the zero-config engine that handles PHP, RoadRunner, and Postgres automatically, make Laravel Octane deployment as simple as pushing to Git.
Ready to deploy your Laravel Octane app? Drag your project to Deployxa Drop for an instant live preview, or install the CLI with npm i -g @deployxa/cli and deploy from your terminal. For more on persistent containers, see Deployxa vs Vercel and explore our free developer tools to speed up your workflow.