Built a multi-tenant boilerplate for running n8n across many client instances (Siennabase)

Hey everyone!

Sharing something I built because I kept hitting the same wall and I saw a lot of
you asking about it in here: how to run n8n for multiple clients without the setup
eating you alive.

The problem I kept having

Every new client meant the same 2-3 days of manual work: spin up n8n on a VPS,
configure Postgres, wrestle with SSL, isolate their credentials from everyone else’s,
document it all again. The automation work is what clients pay for — the infra is
what quietly kills your margin.

What I built

Siennabase — a self-hosted boilerplate that turns that into one command:

add-client acme → isolated Postgres schema + dedicated n8n container + subdomain
with automatic HTTPS, in ~30 seconds.

The stack:

  • Docker Compose: Caddy (reverse proxy + auto HTTPS), Postgres, Qdrant, n8n, Grafana
  • Postgres schema-per-tenant (one DB, isolated schema per client — real isolation
    without running 20 databases)
  • Caddy wildcard subdomains (client.yourdomain.com, no DNS edits per client)
  • A TypeScript CLI: add-client, list-clients, remove-client, status, and
    backup-client / restore-client per tenant
  • 3 ready-to-import n8n workflow templates: RAG chatbot (Qdrant + Claude),
    lead scoring, and invoice/document processing with Claude vision

A couple of things I learned the hard way (sharing so you don’t repeat them)

  • N8N_ENCRYPTION_KEY has to stay consistent and backed up. If it changes, every
    client’s stored credentials become unrecoverable. Generate once, guard it, never
    regenerate.
  • Pin your n8n version. :latest means clients created at different times drift onto
    different versions, and an update can silently break workflows.

Where it’s at

It’s a commercial boilerplate (one-time purchase, you host it yourself, ShipFast-style
— not a SaaS). Landing’s at siennabase.dev. Full transparency: it’s early, just
launched, still validating with real agencies.

I’d genuinely love feedback from people who run n8n for clients:

  • Does the instance-per-client approach match how you’d do it?
  • What would you NEED in something like this before trusting it with real clients?

Happy to go deep on any of the architecture decisions. That’s the part I enjoy most.

1 Like

This is a strong angle for agencies. The part that stands out is not only the one-command client setup, but the fact you are treating version pinning, credential isolation, and backups as first-class parts of the client instance lifecycle.

One thing I would add early is a maintenance receipt per client instance. For example:

  1. Which n8n version the client is pinned to.
  2. Which encryption key backup exists and when it was last verified.
  3. Last successful backup and last restore test.
  4. Which critical workflows have health checks.
  5. Open issues or recent failed runs for that client.
  6. Last change or redeploy made to that client instance.

The add-client flow solves the setup margin problem. The next margin leak for agencies is usually month 3 or 6, when nobody remembers which client is on which version, which backups were tested, or which workflow failed quietly last week.

This is very close to the problem space I am working on with Maintain Flow: not replacing the n8n instance layer, but keeping the post-launch maintenance record clean across clients.

1 Like

This is a genuinely great breakdown — the “month 3-6 margin leak” is exactly right
and honestly not something I’ve had think off. The setup is the
obvious pain, but you’re right that the quiet drift (who’s on what version, which
backup was actually tested) is where it gets expensive later.

The maintenance receipt idea maps closely to where I want the per-client status view
to go. Right now status is basic — version, container health — but that list you
wrote is a solid target for it.

Interesting that Maintain Flow lives in that post-launch space — sounds like we’re
looking at the same problem from opposite ends (I’m the instance/provisioning layer,
you’re the ongoing maintenance record). Curious how you’re approaching it. There
might be more overlap or complementarity than competition there.

Yes, exactly. I would probably split that future status view into two layers: provisioning state and maintenance state.

Provisioning state is things like container health, n8n version, Postgres/schema status, and whether the instance exists. Maintenance state is the slower drift you mentioned: last verified backup, last restore test, critical workflow checks, open issues, last redeploy/change, and whether the client has seen a recent report.

The useful boundary is that provisioning tells you “the client instance is up.” Maintenance tells you “we can still explain and support this client instance three months from now.”

If you build that per-client status view, I would consider making the maintenance receipt exportable or webhookable later. That would let a tool like Maintain Flow consume the operating record without needing to own the instance/provisioning layer.

1 Like

The provisioning vs maintenance split is a really clean way to frame it — I’m
going to steal that mental model, it clarifies what the status view should even
be trying to answer. “Is it up” vs “can we still support this in three months”
is exactly the right distinction.

On making the record exportable/webhookable — makes total sense as a direction,
and I like the idea of the operating record being consumable rather than locked
in. I’m going to keep that in mind when I get to building the per-client status
properly. Right now I’m honestly still at the stage of validating whether people
will pay for the provisioning layer at all, so I’m holding off on locking any
architecture decisions until I’ve talked to more agencies actually living this.

But genuinely useful thread — if it turns out there’s a clean complementarity
there, worth revisiting. What stage is Maintain Flow at?

The per-client schema isolation + dedicated container is the right foundation. One thing that comes up as tenant count grows: webhook URL routing. When all client workflows share the same n8n instance, embedding a tenant slug in the webhook path (e.g. /webhook/{{client-slug}}/...) and validating it as the first node in every workflow prevents cross-tenant data from landing in the wrong place. Worth baking that pattern into the boilerplate from the start - it’s painful to retrofit once you have 10+ clients all sending to the same base URL.

1 Like

This is a great callout. You’re right that it’s the kind of thing that’s trivial to bake in early and miserable to retrofit later. Even with instance-per-client, validating the tenant slug as the first node is a clean safety net. Adding it to the template convention — thanks for flagging it before it became a problem.