18 August 2026 · Euskolabs
Building a scalable SaaS should not take months. In euskolabs we deliver MVPs SaaS multitenant in less than 14 days using an optimized stack: Support for database and auth, FastAPI for backend, Stripe for payments and Docker for deployment. This is the technical guide to how we do it.
The multi-tenant key is in the database design. We use Row Level Security (RLS) from PostgreSQL via Supabase to isolate data from each tenant without the need for manual filters in each query.
-- Tabla de tenants (organizaciones/clientes)
CREATE TABLE tenants (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
plan TEXT DEFAULT 'free',
stripe_customer_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Tabla de usuarios vinculada a tenants
CREATE TABLE users (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
email TEXT UNIQUE NOT NULL,
role TEXT DEFAULT 'member',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Tabla de datos del SaaS (ejemplo: proyectos)
CREATE TABLE projects (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
status TEXT DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Row Level Security: cada usuario solo ve datos de su tenant
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY "tenant_isolation" ON projects
FOR ALL USING (tenant_id = (
SELECT tenant_id FROM users
WHERE id = auth.uid()
));
With RLS enabled, any query you run Supabase automatically filters by the authenticated user's tenant. There's no way a tenant can access data from another, not even by mistake.
FastAPI automatically generates OpenAPI documentation, valids types with Pydantic and supports native async. The backend structure:
app/
├── main.py # Entry point + routers
├── deps.py # Dependencias (auth, db)
├── models.py # Modelos Pydantic
├── routers/
│ ├── tenants.py # CRUD tenants
│ ├── projects.py # CRUD proyectos
│ ├── auth.py # Login/registro via Supabase
│ └── billing.py # Webhooks Stripe
└── supabase.py # Cliente Supabase
from fastapi import Depends, HTTPException
from supabase import create_client
async def get_current_user(token: str = Depends(oauth2_scheme)):
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
user = supabase.auth.get_user(token)
if not user:
raise HTTPException(401, "No autorizado")
return user
For the front we use Next.js with SSR for SEO and automatic code splits. Integration with Stripe includes:
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
event = stripe.Webhook.construct_event(
payload, sig, ENDPOINT_SECRET
)
if event["type"] == "invoice.paid":
tenant_id = event["data"]["object"]["metadata"]["tenant_id"]
activate_tenant(tenant_id)
elif event["type"] == "invoice.payment_failed":
tenant_id = event["data"]["object"]["metadata"]["tenant_id"]
suspend_tenant(tenant_id)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
services:
api:
build: .
ports: ["8000:8000"]
env_file: .env
restart: always
caddy:
image: caddy:2
ports: ["443:443", "80:80"]
volumes: ["./Caddyfile:/etc/caddy/Caddyfile"]
Caddy manages SSL automatically with Let's Encrypt. The SaaS is live in production with HTTPS.
With this stack we deliver SaaS multitenant functional in 14 days:
You want to launch your SaaS? Request a diagnosis in euskolabs. com.