Moving from Laravel Vapor to European infrastructure

How to move a Laravel app from Vapor to Scaleway and bunny.net, from vapor.yml to brewless.yml, with your variables, database, files and DNS.

In this post
  1. What does moving from Vapor to Brewless involve?
  2. 1. Write brewless.yml from vapor.yml
  3. 2. Check the plan
  4. 3. Variables and APP_KEY
  5. 4. Database data
  6. 5. Files
  7. 6. Deploy and test
  8. 7. Move the DNS
  9. Summary

Laravel Vapor made serverless Laravel normal: no servers, a container per request, and a vapor.yml that describes every environment. It also runs on AWS, an American company. When a client or your own legal team asks for European infrastructure, you need the same kind of setup somewhere else.

This guide moves a Vapor app to Scaleway (France) with bunny.net (Slovenia) in front, in your own accounts, deployed with Brewless. It follows the steps of the Vapor migration docs and explains the choices along the way.

What does moving from Vapor to Brewless involve?

Moving from Vapor means translating vapor.yml into brewless.yml, setting up a new database, queue and bucket at Scaleway, and then moving your variables, data, files and DNS. Brewless writes the configuration for you with brewless init --from-vapor; the data, files and DNS are steps you take yourself, at a moment you choose.

Nothing at Vapor or AWS is read or changed by Brewless. Your app keeps running on Vapor the whole time, and visitors only move when you move the domain.

Step Done by Command
Configuration Brewless brewless init --from-vapor
Check it Brewless brewless plan
Variables and secrets You brewless env:pull, brewless env:push
Database data You pg_dump, psql
Files You Any tool that speaks S3 to both buckets
First release Brewless brewless deploy production
DNS You, with Brewless brewless domain:add

You need a Scaleway account connected to your organisation and the command line client signed in, as in the quickstart.

1. Write brewless.yml from vapor.yml

In the root of the project, next to vapor.yml:

brewless init --from-vapor

init asks which application at Brewless this is, or makes a new one. Then it adds the environments of vapor.yml you pick, in the region you choose, and sets each one up the way it was at Vapor: a new database where it had one, a queue at Scaleway Queues unless it said queues: false, and a bucket where it had storage.

For a production environment with a database, storage, two domains and warm: 10, the result looks like this:

brewless.yml
organisation: acme
application: shop
framework: laravel
php: '8.4'

release:
  - 'migrate --force'

environments:
  production:
    kind: production
    region: fr-par
    web:
      memory: 1024
      min: 1
    release:
      memory: 512
    worker:
      timeout: 540
    database: shop-db
    queue: sqs
    storage: shop-files
    domains:
      - shop.example.eu
      - www.shop.example.eu

A few keys change on the way. These are the ones that surprise people:

vapor.yml brewless.yml Why
warm: 10 web.min: 1 A Lambda container takes one request at a time, a replica up to 50
concurrency: 120 web.max: 3 Worked out the same way
deploy release Artisan commands without php artisan in front
build build Steps Brewless already runs, such as composer install and npm run build, are left out
gateway-version – There is no API Gateway: visitors reach the container through bunny.net
octane – Brewless serves the app with FrankenPHP in classic mode

init prints every key it did not carry over as it was, once, in the terminal. The file itself stays clean. Commit brewless.yml: it holds no secrets.

Note

A cache in vapor.yml is not carried over by init --from-vapor yet. Add it to brewless.yml yourself; it becomes a Scaleway Managed Database for Redis. See brewless.yml.

2. Check the plan

brewless plan

This shows, per environment, what the file asks for next to what stands now, and changes nothing. A database or bucket that init made has a name of its own, after the application and the environment, so the plan shows both names side by side. Write the name that stands into brewless.yml.

3. Variables and APP_KEY

vapor.yml holds no variables, and Brewless never reads them from Vapor. The new environment already has the variables Brewless set: the database, the queue, the bucket and a new APP_KEY. Pull those into a file of their own, so they do not mix with anything from Vapor:

brewless env:pull production --file=.env.brewless-production

Then add what your app needs besides, copied from your Vapor environment: mail, payments, other services. Keep the DB_*, AWS_*, SQS_*, QUEUE_CONNECTION and FILESYSTEM_DISK lines Brewless wrote, because they point at the new database, queue and bucket.

Warning

Replace the new APP_KEY with the one from Vapor when your app has encrypted data, encrypted casts or sessions you want to keep. Laravel can only read those with the key they were written with.

Push the file and remove it:

brewless env:push production --file=.env.brewless-production
rm .env.brewless-production

The values go into Scaleway Secret Manager in your own project. Brewless keeps the names and revisions, never the values. Plain values such as APP_LOCALE can go under variables in brewless.yml instead; anything that looks like a secret is refused there.

4. Database data

The new database is empty. A Serverless SQL Database speaks PostgreSQL, so from a machine that can reach both databases:

pg_dump --no-owner --no-privileges "postgresql://user:password@old-host:5432/shop" > shop.sql
psql "postgresql://$DB_USERNAME:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_DATABASE?sslmode=require" < shop.sql

The address and credentials of the new database are in the file brewless env:pull gave you. Do this before the first deploy, so the release commands find your tables.

A MySQL database at Vapor needs a MySQL database to move to, such as a Managed Database you name when you set the environment up. Brewless does not convert data between the two.

Tip

Do a first copy to test with, and a second one just before you switch the domain. Put the Vapor app in maintenance mode for that second copy, so no writes get lost in between.

5. Files

The files in your S3 bucket at AWS are not moved either. Scaleway Object Storage speaks the S3 API, so any tool that can talk S3 to two endpoints can copy them from the old bucket to the new one. Your app reads them through the same s3 disk as before; only the AWS_* variables changed.

6. Deploy and test

brewless deploy production

This builds the current commit inside your own Scaleway project, runs the release commands and puts the release live on the environment's own address, such as acme-shop-production.b-cdn.net, and on its test address. Your domain still points at Vapor, so you can try everything here first.

The deploy names the domains from brewless.yml that the environment does not answer on yet, and asks whether to add them. Answer no until you are ready to move, because adding one points its DNS at Brewless when the zone is in your accounts.

7. Move the DNS

When the release on Scaleway does what it should, add each domain:

brewless domain:add production shop.example.eu
brewless domain:add production www.shop.example.eu

With the zone at Bunny DNS or Scaleway DNS in your connected accounts, Brewless makes the record. With DNS anywhere else, it prints the CNAME you make yourself. Adding a domain never overwrites a record somebody else made: when an old address record is in the way, it stops and names it. The certificate follows once the record points at the edge.

Then set APP_URL to the new domain, which domain:add offers to do, and deploy once more so the containers get it. Moving your domain covers every case.

Once visitors arrive on Scaleway, what you had at Vapor can go. Brewless never removes anything there, so that is a step you take at AWS yourself, when you are sure.

Summary

  1. brewless init --from-vapor, then brewless plan.
  2. Pull the new variables, add your own, keep your old APP_KEY if you have encrypted data, and push.
  3. Dump and load the database, copy the files.
  4. Deploy and test on the environment's own address.
  5. Add the domains, set APP_URL, deploy again.

The whole key-by-key comparison is in the Vapor guide. Why you might want European infrastructure in the first place is in How to host a Laravel app EU-sovereign.

Frequently asked questions

Does brewless init change anything at Vapor or AWS?

No. It reads vapor.yml from your project and writes brewless.yml next to it. Your Vapor environments keep running until you move your domain, and Brewless never removes anything there.

Is my data moved from the Vapor database automatically?

No. The database Brewless sets up is empty. You dump the old database with pg_dump and load it into the new one with psql, before the first deploy, so the release commands find your tables.

What happens to Vapor's warm setting?

It becomes web.min, the number of replicas that keep running. A Lambda container takes one request at a time and a Brewless replica up to 50, so warm 10 becomes a minimum of 1. A running replica is paid for all the time.

Can I move a MySQL database from Vapor?

Only to another MySQL database, such as a Managed Database you name when you set the environment up. Brewless does not convert data between MySQL and PostgreSQL. The serverless database it sets up by default speaks PostgreSQL.

Should I reuse my APP_KEY from Vapor?

Yes, when your app has encrypted data, encrypted casts or sessions you want to keep. Laravel reads those only with the key they were written with. Without such data, the new APP_KEY Brewless generated works as well.

All posts Read this post as markdown

Try it on your own app

Brewless deploys your Laravel app to Scaleway and bunny.net, in your own accounts. We open access to Laravel teams in small batches.