Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild

Vercel Laravel: Running Laravel on Vercel, and Where It Breaks

Sean

Platform Writer

Sep 30, 2026
11 min read

You can run Laravel on Vercel by adding an api/index.php that forwards to public/index.php and a vercel.json that loads the community vercel-php runtime, but the filesystem is read-only, there are no queue workers or scheduler, and the database must live somewhere else.

Vercel Laravel: Running Laravel on Vercel, and Where It Breaks

That trade is fine for a webhook receiver, an internal tool or a demo. It is a poor fit for a typical Laravel application with uploads, queues and scheduled jobs. Here is the working configuration, the reason each workaround exists, the list of things that will not work, and the container route that removes most of the pain.

Table of contents

Why Laravel on Vercel needs a workaround at all

Vercel runs code as serverless functions. Each request lands on a short-lived instance with a read-only deployment filesystem, a small writable temp directory and no process that stays alive between requests. Laravel was designed for the opposite: a long-lived PHP-FPM or Octane process on a server with a writable storage directory, a queue worker running in the background and cron firing the scheduler every minute.

Vercel does not ship a native PHP runtime, so the community filled the gap with vercel-php, a runtime that packages PHP and your Composer dependencies into a function. The current release listed in its README is [email protected], which targets PHP 8.5, with earlier versions for older PHP lines. It is a community project rather than a Vercel product, so pin the version and read the changelog before you upgrade.

Caleb Porzio’s write-up, Easy, Free, Serverless Laravel With Vercel, is the source most tutorials descend from. His own framing is honest: it is not a one-to-one replacement for something more robust, but it is useful for small, non-critical apps. That is the right expectation.

The three files: api/index.php, vercel.json and .vercelignore

Vercel expects function entry points inside an api directory. Laravel’s real entry point is public/index.php, so you add a tiny forwarder.

<?php
// api/index.php
// Forward Vercel requests to Laravel's normal front controller
require __DIR__ . '/../public/index.php';

The vercel.json file then declares the runtime, sends every path to that function and sets the environment that Laravel needs in a serverless world. The configuration below follows the pattern in the widely copied tutorial, updated to the current runtime version.

{
  "version": 2,
  "framework": null,
  "functions": {
    "api/index.php": { "runtime": "[email protected]", "maxDuration": 30 }
  },
  "routes": [
    { "src": "/(.*)", "dest": "/api/index.php" }
  ],
  "env": {
    "APP_ENV": "production",
    "APP_DEBUG": "false",
    "APP_URL": "https://your-project.vercel.app",
    "APP_CONFIG_CACHE": "/tmp/config.php",
    "APP_EVENTS_CACHE": "/tmp/events.php",
    "APP_PACKAGES_CACHE": "/tmp/packages.php",
    "APP_ROUTES_CACHE": "/tmp/routes.php",
    "APP_SERVICES_CACHE": "/tmp/services.php",
    "VIEW_COMPILED_PATH": "/tmp",
    "CACHE_DRIVER": "array",
    "LOG_CHANNEL": "stderr",
    "SESSION_DRIVER": "cookie"
  }
}

Add a .vercelignore containing /vendor so you do not upload your local dependencies; the runtime runs composer install itself. Put APP_KEY and other secrets in the project’s environment variables in the Vercel dashboard, never in vercel.json, which is committed. Then run vercel to deploy, or connect the GitHub repository and let pushes deploy.

The read-only filesystem and the /tmp workaround

This is where most first deployments fail. Laravel writes to disk constantly: compiled Blade views, the configuration and route caches, log files, the file cache, sessions and uploaded files. On Vercel the deployment directory is read-only, so every one of those writes throws an error or silently fails.

The environment block above is the workaround. Laravel lets you relocate its bootstrap caches with the APP_*_CACHE variables and the compiled views with VIEW_COMPILED_PATH, so you point them all at /tmp, the one writable location. Logs go to stderr so they appear in Vercel’s runtime logs, and the cache and session drivers switch to something that needs no disk.

  • Bootstrap caches and compiled views: /tmp, rebuilt whenever an instance starts
  • Logs: LOG_CHANNEL=stderr, then read them in the Vercel dashboard
  • Cache: array for a demo, or a Redis-compatible store or database for real use
  • Sessions: cookie driver, or a shared store, because instances do not share memory
  • Uploads: never store to local disk, use an object storage disk instead

Note what /tmp is: scratch space local to one instance, cleared when the instance is recycled, and invisible to other instances. Anything you write there is a cache, not data. A user uploads an avatar, the request hits instance A and writes to /tmp, and the next request hits instance B and finds nothing. Use a proper filesystem driver such as S3-compatible storage for anything that must last.

What does not work: workers, the scheduler and long requests

This is the honest section that tutorials skip. Laravel’s background machinery assumes a process that stays up.

  • Queue workers: php artisan queue:work is a long-running process. There is nowhere to run it, so QUEUE_CONNECTION=sync executes jobs inside the web request, or you dispatch to an external service.
  • The scheduler: schedule:run expects cron every minute. Use an external trigger, such as Vercel Cron calling a protected HTTP route, that runs the due tasks.
  • Horizon and Reverb: both are long-lived daemons and cannot run in a function. Our post on Laravel Horizon explains what they need.
  • Long requests: functions have a maximum duration, so exports and imports that take minutes will be cut off.
  • Migrations: run them from your machine or a CI job, not at request time.

Sync queues are the trap. It looks like it works in testing because the job runs and the response arrives, but the user waits for the job, a failure becomes a 500 error, and there are no retries. Set QUEUE_CONNECTION to a real driver and run the consumer somewhere that can host it, or accept that this app has no background work.

Database connections deserve a mention too. Each function instance opens its own connection, and under load you can exhaust the database’s connection limit fast. Use a database that offers connection pooling, or keep concurrency low.

The database has to live somewhere else

There is no database on Vercel for a PHP function to talk to over the local network. Your MySQL or Postgres instance must run elsewhere and be reachable from the internet or through a supported private link. Point Laravel at it with DB_HOST, DB_DATABASE, DB_USERNAME and DB_PASSWORD in the project’s environment variables, and check that the database allows connections from Vercel’s dynamic address ranges.

That last part causes the most confusion. Functions do not have a fixed outbound IP by default, so an allowlist-only database will reject them. You either open the database more widely, protected by strong credentials and TLS, or move to a setup where the app and database share a network.

Latency matters as well. Run the database in the same region as the function, or every query pays a cross-region round trip, and a Laravel page that runs thirty queries will feel it. Eloquent’s N+1 habits, harmless on a local socket, become visible.

The container alternative, and when to skip serverless

Vercel has since added an official path that removes most of these restrictions. Its guide on deploying Laravel on Vercel with Docker runs Laravel on FrankenPHP inside a container-backed function, with a Dockerfile.vercel, a Caddyfile and a vercel.json that declares a container service. It is a better foundation than the community runtime, though the guide still says the container filesystem is not durable, queue workers and the in-process scheduler need other services, and state belongs in external stores.

The underlying idea is the more interesting one: Laravel is happiest as an ordinary long-running container. Build it with a Dockerfile, give it a persistent disk if you need one, run the web process and a queue worker as separate services, and use the platform’s cron or a scheduler process for schedule:run. Our guide to free Laravel hosting with Docker shows a Dockerfile that does it, and vercel alternatives covers the wider question of where else to host.

The decision rule I use: if the app is a stateless API or a small internal tool and you love Vercel’s preview workflow, the runtime or container function is fine. If it has uploads, queues, a scheduler, Horizon or a real database next to it, host it as a service.

How this fits the rest of the stack

If your Laravel app has queues and uploads, price it as a service before you contort it into functions. The RunxBuild hosting calculator shows the cost of a Docker service and a managed MySQL or Postgres database side by side. On RunxBuild a Laravel Dockerfile deploys from GitHub as a Docker service with build logs, environment variables, runtime logs, persistent storage attached to the service and rollback, and a MySQL or Postgres instance sits next to it. Read the Docker services docs or start from the dashboard.

Useful related references:

FAQ

Can you deploy Laravel to Vercel?

Yes. The classic route uses the community vercel-php runtime with an api/index.php forwarder and a vercel.json that routes every request to it. Vercel also documents a container route using FrankenPHP and Docker. Both need external services for databases, files and background work.

Why does Laravel fail on Vercel with a read-only file system error?

Laravel writes compiled views, caches, logs and sessions to disk, but Vercel’s deployment directory is read-only. Point the APP_*_CACHE and VIEW_COMPILED_PATH variables at /tmp, send logs to stderr, and use non-file cache and session drivers.

Can Laravel queues and the scheduler run on Vercel?

Not as long-running processes. Queue workers and schedule:run need a persistent process or cron. You can trigger scheduled work with an authenticated HTTP endpoint on Vercel Cron, and move real queue processing to a platform that can run a worker.

Where should I host the Laravel database if I use Vercel?

Use an external MySQL or Postgres service reachable over the internet, ideally in the same region as your functions. Configure it with environment variables, use TLS and strong credentials, and prefer a database with connection pooling to avoid exhausting connections.

Is Vercel a good host for a full Laravel app?

It suits small stateless apps, APIs and prototypes. A full application with uploads, queues, a scheduler and a database is easier on a platform that runs long-lived containers. Choose based on those needs, not on how nice the deploy previews are.

#vercel laravel#vercel-php#Laravel serverless#Laravel deployment#Laravel Docker