Engineering··8 min read

We shipped a SaaS from a single-tenant tool. Here's the diff.

Helmies started life as a single-tenant ops tool for one Finnish business. It served customers, projects, time, invoices and an AI agent — for exactly one company. Then we decided to open it.

The temptation was a rewrite. We didn't. Instead, we added tenant_id to every table, layered RLS over the schema, and made Tenant 0 the dogfood instance. Six weeks later we onboarded the first paying tenant. The full story is below.

Step one was the audit. We listed every table, every endpoint, every cron, every webhook. For each we tagged it as tenant-scoped or global. About 92% were tenant-scoped — the rest were the marketing site, billing infra, and the super-admin layer.

Step two was migrations. We wrote a single migration per resource that added tenant_id UUID NOT NULL DEFAULT 'tenant-0' and a default-deny RLS policy. We ran it on a staging branch first, watched the test suite catch the missing joins, and only merged when every test passed.

Step three was the agent. The agent had implicit access to everything; we added a scope object to every tool call and refused execution if the scope didn't match the calling user's tenant. We logged every refusal so we could see if any production path was relying on the implicit access. After two weeks of clean logs we shipped to all tenants.

Try it for 14 days.