Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Sign up now for $10 in credit on a new Laravel Cloud account.

Idea

Can a Laravel app keep its SQLite database in a Cloud bucket?

· Ed Grosvenor

We have already made SQLite survive Laravel Cloud's ephemeral filesystem once. Cloud wipes that filesystem on every deploy and reboot. In the Campfire Rust experiment, we used Litestream, an open-source tool that replicates SQLite databases to object storage. It continuously replicated the application's SQLite database to the bucket attached to its environment. It then restored the database before the application started after a deploy or reboot.

That experiment was not a Laravel application. Campfire ships as one binary and owns its whole process tree, which made Litestream unusually easy to put in charge. The next experiment is whether the same shape can work for a small Laravel app that we already know will never need more than one instance.

Why try it with Laravel

For a small app, the managed database can be the biggest line on the Cloud bill. Campfire's Rust deployment costs about $7 a month for one flex-512mb instance, with its bucket costing essentially nothing at chat volumes. That figure assumes the instance never sleeps. Cloud can hibernate an idle instance, and one that hibernates costs less. The Rails deployment costs about $33 a month with Postgres, Valkey, a worker, and Reverb. That difference comes mostly from removing managed resources, not from Rust serving requests faster.

The exact saving for a Laravel app is still unknown, and it will depend on what else the app needs. But if the database is the expensive part, SQLite backed by a bucket could change whether Cloud makes economic sense at all. Our guide to finding where a Laravel Cloud bill is actually going is the place to check that premise before replacing anything.

It could also remove a practical barrier to moving from Forge. A Laravel app already using SQLite would otherwise need to move its data to MySQL or Postgres before it could move to Cloud. That can mean an export, an import, SQL dialect differences, and a longer cutover.

Litestream might be the migration path instead. Replicate the live database from Forge into the future Cloud environment's bucket while the site is still running, deploy the Cloud app so it restores that replica, then switch DNS. There would still be a final cutover window, but no database conversion. That is a hypothesis for this experiment, and we have not proved it.

What worked for Campfire

Cloud wipes the local filesystem on deploys and reboots. Campfire treats its local database in /tmp as scratch space and the bucket replica as the durable copy.

Attaching a bucket to the environment makes Cloud inject AWS_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ENDPOINT_URL, and AWS_REGION as environment variables. Nothing is configured by hand. Attaching the bucket is the configuration.

The public boot script takes it from there.

  1. It writes litestream.yml from those injected variables without writing the credential values into the file.
  2. It runs litestream restore -if-db-not-exists -if-replica-exists. If a replica exists and the restore fails, the application cannot start with an empty database and overwrite the good copy.
  3. It runs exec litestream replicate -exec "<app server>". Litestream becomes the parent process, supervises the application, and streams SQLite's write-ahead log to the bucket.

The database survived redeploys. Hibernation does not trigger a restore. Cloud freezes the existing process and thaws it later, rather than starting it again. Only a deploy or reboot takes the restore path.

The rules we cannot bend

This is not a way to make SQLite horizontally scalable. The experiment only makes sense for an app that meets four hard requirements:

  1. Never more than one instance. Set the maximum replicas, the number of copies of the app Cloud may run at once, to one. Hibernation can stay on. A sleeping instance is still one instance, and Cloud freezes it and thaws it later rather than restarting it. Even a full restart would be safe, because the boot step restores from the bucket before anything else runs.
  2. Everything that touches the database runs on that instance. Any other instance has its own filesystem, and so its own SQLite file. It would read stale or empty data and write to a file nobody replicates. That rules out queue workers, because Cloud runs queued jobs on separate compute rather than on the app instance. The queue driver makes no difference. What matters is where the job runs. A candidate app does all of its work within the request, using the sync driver, or Laravel's defer() for anything that can wait until after the response.
  3. WAL mode is required. SQLite must use its write-ahead log, and nothing else can checkpoint that log behind Litestream's back.
  4. The restore comes first. Litestream must finish restoring before anything opens the database, including Laravel migrations.

If an app might outgrow one instance, or needs real background workers, it should use a managed database. The premise here is an app for which neither will ever be necessary.

What is most likely to trip us up

We have not run this on Laravel yet. The five items below are guesses, and the experiment exists to test them.

  1. Where does Litestream run? Campfire is one binary and owns its process tree. A Laravel app runs under PHP-FPM or Octane, and Cloud owns how those processes start. Litestream probably cannot be the parent process, so it would need to run as a long-lived process on the same instance. We do not yet know whether Cloud allows that.
  2. The restore has to beat the migrations. Cloud runs deploy commands during each deployment, before the new version takes traffic. If Cloud runs deploy commands such as php artisan migrate in a container other than the one that will serve traffic, the migration would open a throwaway empty database. Restore and migration may both have to move into instance startup, in that order and behind a lock.
  3. Zero-downtime deploys are a problem of their own. Cloud may start the new instance before stopping the old one. During that overlap, two processes could write to the same replica path. The new instance may also have restored a snapshot that does not include the old instance's final writes. We did not measure this on Campfire either. It has to be measured before this can be called safe.
  4. Then there is the scheduler. If Cloud runs scheduled commands on the app instance, they are fine. If it runs them anywhere else, they hit the same split-database problem as queued jobs. And while the app hibernates, its scheduled tasks may not fire at all. Either a candidate app has no scheduled tasks, or the experiment has to find out.
  5. One part of this may really be easy. Laravel's SQLite connection already accepts journal_mode, busy_timeout, and synchronous in config/database.php. Forcing WAL mode should not need a framework hack.

What the easy version could become

If the experiment works, the Laravel-facing part should be small. A package could install a pinned, checksummed Litestream binary at build time, generate its configuration from Cloud's injected variables, perform a guarded restore, and then migrate and start replication. It could check replication lag and refuse to boot when the environment has more than one replica or SQLite is not in WAL mode.

The goal would be that attaching a bucket is the entire setup. To make that a supported path rather than a clever boot script, we would ask Laravel for a first-class sidecar process on the application instance, a documented restore-migrate-boot order, and a single-writer deployment mode that stops the old instance before starting the new one.

How we will decide whether it works

The experiment needs a throwaway Laravel app on Cloud rather than another round of reading documentation. We will use it to answer five questions:

  1. Where do deploy commands actually run relative to the web instance and its filesystem?
  2. Can Litestream run as a long-lived sidecar on that same instance?
  3. Do the old and new instances overlap during a deploy? We will write continuously through the deploy, then count the rows and look for anything missing.
  4. Where do scheduled commands run, and do they fire on time while the app is hibernating?
  5. Can a live SQLite database move from Forge to Cloud through Litestream with no export, import, or dialect conversion?

The result is viable only if deploys and restarts lose no writes, attaching a bucket is the only manual setup, and the bill is meaningfully lower than the same app with a managed database. Anything less stays an interesting experiment rather than a deployment pattern we should recommend.

Support the Lab

The Lab runs on our supporters

Every idea here gets tried for real: deployed, broken, fixed and written up. Right now that is one person doing the work between everything else, and supporters are what buy the time. As support grows, it will also pay freelancers to run experiments of their own, so more of these ideas get tested, sooner.

Our supporters

  • Sputnik Intelligence

    Spain

    We transcribe the podcasts and read the newsletters, and make them searchable. Set up alerts, or hand it all to your AI agent over MCP or API.

    Supporter Visit
    Visit Sputnik Intelligence