# An EU-sovereignty checklist for a Laravel SaaS

One page to hand to a client or DPO, with every part of a Laravel SaaS that holds or sees data, what to check for each and how to note the exceptions.

## In short

- A Laravel SaaS is only as sovereign as the least sovereign service that holds or sees its data.
- For every part of the stack, write down three facts - where the data is stored, which company runs it and in which country that company is headquartered.
- Builds, secrets, the deploy tool and error tracking are the parts teams forget, and each can see more data than the database.
- A checklist with honest exceptions is more useful to a DPO than a claim of full sovereignty that does not hold.

Sooner or later a client, a tender or a data protection officer asks for it in writing: *which parties see our data, where, and under which law?* A sentence like "hosted in the EU" does not answer that. A table does.

This post is that table, for a typical Laravel SaaS. Copy it, fill it in for your app, and hand it over. It builds on [How to host a Laravel app EU-sovereign](/blog/eu-sovereign-laravel-hosting), which explains the setup; this one is about writing it down.

## What does an EU-sovereignty checklist need to show?

For every part of your app that holds or sees data, it needs three facts: where the data is stored, which company runs that part, and in which country that company is headquartered. Add the exceptions you know about. That is enough for a DPO to judge the setup, and it is honest about the parts you cannot control.

The reason ownership matters, and not just location, is the US CLOUD Act: a US provider can be required to hand over data it controls, wherever that data is stored. [The CLOUD Act explained](/blog/us-cloud-act-laravel) covers that in detail.

## How to fill it in

Start from the code, not from memory:

```bash
# Every outside service your app talks to
grep -rhoE "https?://[a-z0-9.-]+" config/ app/ | sort -u
grep -E "^[A-Z_]+_(KEY|SECRET|TOKEN|DSN|URL)=" .env.example
composer show --direct
```

Every SDK in `composer.json`, every host in `config/services.php` and every DSN in `.env.example` is a candidate row. Then add what does not show up in code: where builds run, where secrets are stored, and the tools your team uses.

## The checklist

| # | Part | What it sees | Where is it stored? | Who runs it? | Company HQ | Contract with |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | Runtime (web, worker, scheduler) | Every request and response | | | | |
| 2 | Database | Everything | | | | |
| 3 | File storage | Uploads, exports, invoices | | | | |
| 4 | Queue | Job payloads | | | | |
| 5 | Cache and sessions | Session data, cached queries | | | | |
| 6 | Secrets | `APP_KEY`, passwords, API keys | | | | |
| 7 | Builds | Source code, build-time variables | | | | |
| 8 | CDN, DNS, firewall | Every visitor's request and IP address | | | | |
| 9 | Deploy tool | Code, secrets, keys to your cloud | | | | |
| 10 | Transactional mail | Names, addresses, message content | | | | |
| 11 | Payments | Names, payment details | | | | |
| 12 | Error tracking | Stack traces, request data, user IDs | | | | |
| 13 | Logs | Whatever the app logs | | | | |
| 14 | Analytics | Visitor behaviour, IP addresses | | | | |
| 15 | Fonts, scripts from CDNs | Every visitor's IP address | | | | |
| 16 | Support tools (chat, helpdesk) | Customer conversations | | | | |
| 17 | Team tools (source hosting, CI, chat, docs) | Code, credentials, sometimes data | | | | |

Leave nothing blank. "Not used" is a valid answer; an empty cell is not.

## Notes per row

**1–5. Runtime, database, files, queue, cache.** Keep them at one provider, in one region, so data does not cross between companies on its way from the web container to the database. On serverless, sessions and cache belong in the database or a cache service, since the container's disk does not outlive it.

**6. Secrets.** Ask where the values are stored and who can read them. A deploy tool that keeps a copy of your `.env` is a party that holds your database password.

**7. Builds.** A build sees your full source code and often build-time secrets. Hosted CI and hosted build servers count as a party here, with their own owner and location.

**8. CDN, DNS, firewall.** The edge sees every request before your app does, including IP addresses and often cookies. Check where the edge locations are, whether they can be limited to Europe, and where DNS is answered.

**9. Deploy tool.** It holds keys to your cloud. Check whether it sits in the request path, whether it stores secret values and logs, and what happens to your app if you stop using it.

**10–14. Mail, payments, errors, logs, analytics.** These often carry the most personal data per request. Error trackers in particular receive request bodies, headers and user IDs by default. See [European alternatives for the services a Laravel app calls](/blog/european-alternatives-laravel-services).

**15. Fonts and scripts.** Each external font or script sends the visitor's IP address to whoever serves it. Self-host them through Vite and the row is answered.

**16–17. Support and team tools.** These are about your organisation, not the app's runtime. List them separately so the reader can see the difference, but list them.

> [!WARNING]
> Do not mark a row as sovereign because the data centre is in the EU. The "Company HQ" column is the one that answers the CLOUD Act question.

## An example, filled in

This is the hosting part for a Laravel app deployed with Brewless, as described in the [Brewless documentation](https://docs.brewless.eu/getting-started/how-it-works):

| Part | Where is it stored? | Who runs it? | Company HQ | Contract with |
| --- | --- | --- | --- | --- |
| Runtime | Scaleway region you choose (Paris, Amsterdam, Warsaw or Milan) | Scaleway Serverless Containers | France | You |
| Database | Same region | Scaleway | France | You |
| File storage | Same region | Scaleway Object Storage | France | You |
| Queue | Same region | Scaleway Queues | France | You |
| Secrets | Your own Scaleway Secret Manager | Scaleway | France | You |
| Builds | Inside your own Scaleway project | Scaleway | France | You |
| CDN, DNS, firewall | bunny.net; edge locations can be limited to Europe | bunny.net | Slovenia | You |
| Deploy tool | Control plane at Scaleway in Paris | Brewless (Coding Agency) | The Netherlands | You |

And the notes that belong with it:

- Brewless is not in the path of visitor traffic: requests go from bunny.net straight to your containers.
- Brewless keeps the names and revisions of your environment variables, not their values. Logs are read live from Scaleway and not stored by Brewless.
- bunny.net answers DNS lookups from its worldwide network and uses a few US-based tools for its own support and email, which see account details, not your visitors' traffic.
- Queues are not available in Warsaw or Milan yet, and file storage is not yet in Milan, so check the [regions](https://docs.brewless.eu/reference/regions) before you pick one.

Rows 10 to 17 depend on your app and your team, and Brewless does not answer them.

> [!TIP]
> Put the filled-in checklist in your repository, next to `brewless.yml`. A pull request that adds a new SDK is then the natural moment to add a row.

## What to hand over

A one-page document with:

1. The filled-in table.
2. The exceptions, each in one sentence: what data, which provider, which country, and why you accept it.
3. The data processing agreements you hold, and with whom. [GDPR and your hosting stack](/blog/gdpr-hosting-stack-laravel) explains which ones those are.
4. The date, and who is responsible for keeping it current.

A DPO can work with a short list of known exceptions. What they cannot work with is a claim of full sovereignty that turns out not to hold the first time someone looks at `config/services.php`.

## Summary

- List every part that holds or sees data, from runtime to team tools.
- For each, write down location, operator, company headquarters and who holds the contract.
- Build the list from the code: SDKs, hosts and DSNs.
- Write the exceptions down instead of leaving them out.
- Keep it in the repository and update it with every new outside service.

## Frequently asked questions

### What should an EU-sovereignty checklist for a Laravel app cover?

Every part that holds or sees data - the runtime, database, files, queue, secrets, builds, CDN and DNS, the deploy tool, and every outside service the app calls, such as mail, payments, error tracking and analytics. It should also cover the team's own tools, because they see code, data and credentials.

### Who should fill in the checklist?

The developers who know the stack fill in the facts, and the client or their data protection officer judges them. Developers know which services the app calls; the DPO decides which exceptions are acceptable.

### Is an EU region of an American cloud enough to tick the runtime box?

No. The data is in the EU, but the provider is subject to US law, including the CLOUD Act. The checklist asks for both the location and the owner of each provider.

### How often should the checklist be updated?

Whenever a dependency that talks to the outside world is added or replaced, and at least once a year. A new SDK in composer.json can add a new party that sees your data.

### Does Brewless fill in parts of the checklist?

It answers the runtime, database, files, queue, secrets, builds and CDN rows with Scaleway (France) and bunny.net (Slovenia) in your own accounts. Brewless itself is a Dutch control plane that is not in the visitor traffic path and does not store secret values. The services your app calls remain your choice.

Source: https://test.brewless.eu/blog/eu-sovereignty-checklist-laravel-saas
