Brewless Brewless Docs

Running your application

Databases per tenant

Let your running application ask Brewless for a Serverless SQL Database per tenant of its own, signed with a key only the two hold.

On this page

An application that gives every customer of its own a database of its own has to make that database while it runs, when a customer signs up. On a Serverless SQL Database at Scaleway, CREATE DATABASE over SQL is never allowed: each database is a resource of its own, with its own address, made through the Scaleway API. Your application has no key for that API. So it asks Brewless, and Brewless makes the database in your Scaleway project.

In brewless.yml

brewless.yml
environments:
  production:
    database: shop-main
    databases:
      prefix: shop-

The environment's own database has to be a Serverless SQL Database: a Managed Database is refused. The prefix starts with a lowercase letter, has lowercase letters, digits and dashes, ends in a dash and is at most 32 long. A dash and not an underscore: Scaleway takes only letters, digits and dashes in the name of a Serverless SQL Database, and the database inside has the same name. Every database the application asks for has a name that starts with it.

The next deploy gives the application what it needs to ask (see below). The variables go next to the environment's own variables, so this needs those kept through Brewless (push them once with brewless env:push).

databases: false takes the prefix away: the application can ask for nothing more. The databases it made stay at Scaleway and stay recorded. Another prefix is refused while databases of the one that stands are recorded, because they would no longer be reachable by name.

What the application gets

Every container of the environment gets three variables, the one that serves visitors too, because an application may make a customer's database in a web request:

Variable What it is
BREWLESS_API_URL The address of your organisation at Brewless, such as https://acme.brewless.eu
BREWLESS_ENVIRONMENT The id of the environment
BREWLESS_DATABASES_KEY A random key of 32 bytes, base64, made once per environment

The key is kept in your Secret Manager like the other keys of the environment. Brewless keeps it too, encrypted, to check the signature of a request. It is never shown or logged.

A new database signs in exactly like the environment's own: with DB_USERNAME and DB_PASSWORD, the identity Brewless made for the environment, which may use every Serverless SQL Database of the project. Only the host differs. Making the tables in it is your application's work; Brewless never connects to it.

With Laravel, brewless/laravel 0.7.1 or later (0.7.0 refuses a name with a dash) reads the three variables and signs for you:

app/Actions/CreateTenantDatabase.php
use Brewless\Laravel\Databases\DatabaseClient;

$database = resolve(DatabaseClient::class)->create('shop-'.$tenant->id);
$database = resolve(DatabaseClient::class)->waitUntilReady($database->name);

// $database->host and $database->port, with DB_USERNAME and DB_PASSWORD

The requests

Each request carries three headers:

Header Value
X-Brewless-Environment BREWLESS_ENVIRONMENT
X-Brewless-Timestamp The time in whole seconds since 1970
X-Brewless-Signature The HMAC-SHA256 below, in hexadecimal

The signature is over four lines: the method, the path, the time and the SHA-256 of the body, with the key from BREWLESS_DATABASES_KEY as it stands in the variable (not decoded from base64):

hash_hmac('sha256', $method."\n".$path."\n".$timestamp."\n".hash('sha256', $body), $key);

The path starts with /api/; the body is empty for every request below. A request more than five minutes off, with a wrong signature, or for an environment without databases is answered 401 with the same sentence, whatever was wrong.

Request Answer
PUT /api/runtime/databases/{name} Makes the database when it does not exist. 202 while it is being made, 200 once it is there
GET /api/runtime/databases/{name} Where it stands, 200
DELETE /api/runtime/databases/{name} Removes it at Scaleway now, with everything in it. 202

PUT and GET answer with {"name", "status", "host", "port"}. The status is creating, ready, deleting or error; host and port are null until Scaleway names them. DELETE answers with {"name", "status": "deleting"}. Asking for the same name again makes nothing twice.

Limits

  • The name has lowercase letters, digits and dashes, at most 63, and starts with the prefix. An underscore is refused with 422 before Scaleway is asked, since Scaleway would refuse it. In SQL, quote the name ("shop-1"): PostgreSQL reads a bare dash as a minus. When Scaleway refuses a name for good, Brewless keeps no record of it, so the name and the prefix stay free. It is never the name of the environment's own database, and a database of that name that Brewless did not make for this environment is never taken over: that is refused with 422.
  • GET and DELETE only reach databases Brewless made through this route for this environment; anything else is 404. The environment's own database can never be removed this way.
  • A new database scales between the same vCPUs as the environment's own database, as it was set up.
  • Each environment may make 120 requests a minute. When Scaleway cannot be reached or refuses, the answer is 503: ask again.
  • Every database made and removed is in the audit trail, with the name and the environment. Each is a resource of the environment, so the console lists it and an export names it.