Why building a self-hosted SaaS is a headache (and how we make it easier)

In the 90s, we flew in technicians to install Oracle databases in server basements. Today, Supabase spins up a backend, in seconds, for free.
Over the past 30 years, softare has gotten faster, cheaper and easier in almost every way.
Some engineers might miss 24-month cycles of tranquil coding, but nobody wants to do code reviews over email or contort software to run on a 10 year-old server rack your eighth-biggest customer is still using.
As an open source SaaS startup, we need to be able to do both: Ship quickly while also offering a self-hosted version.
This makes shipping updates harder because customer instances are a black box. We have no way to know what jobs a customer has running. This is especially difficult because no two customers use Lago the same way. One customer might aggregate usage data in real-time while another does so monthly.
Even low-risk fixes like renaming a database column can break a billing run when a job is currently using that data.
But as a startup, we need to ship quickly and frequently. In this piece, I want to explain how we balance the two: Shipping quickly without neglecting self-hosted customers.
Before the details, let’s answer a valid question: Why go through the pain of offering self-hosting?
C’mon, it’s 2025. Why even offer self-hosting?
Just about everything has moved to the cloud. If you shut down AWS, Azure and GCP, you’d shut down the software business.
This has made everything available everywhere, obviated in-house software maintenance, etc. But it also comes with trade-offs:
- You can’t extend/integrate cloud software beyond what APIs allow you to do.
- If a cloud vendor has a security issue, you now have a security issue.
- If a vendor fails/gets sunset by an acquirer, their software disappears.
All of these are fine for much of B2B SaaS. Take a tool like Miro. It doesn’t require deep integrations to be useful. You’re not storing critical data there. If Miro vanishes over night, it’s an inconvenience.
The opposite is true for infrastructure like Lago. A billing system (especially for complex pricing) is always customized and deeply integrated. The data is sensitive and breaches have serious consequences. If your billing system vanishes over night, it’s a catastrophe.
This is why enterprise-level customers often want to self-host Lago. And that’s not just marketing talk: 42% of IT leaders have moved workloads off of the cloud. Basecamp’s Cloud Exit is a tech saga.
Self-hosting might not be worth the trouble for an early-stage startup. For enterprises, it’s often the only way.
At some point, resources aren’t the constraint anymore. Security and control are.
So there you have it: Self-hosting is making a resurgence and billing is the perfect product category for it.
But that doesn’t mean it’s always easy.
Building a self-hosted product is harder
We recently added organization_id
to every table. This simplified queries because you no longer had to take a detour around the customer object. A simple fix that sped up Lago.
We looked at data, made sure no jobs are currently running and released the update. Boom, Lago is more efficient, everyone’s smiling, hugging, dancing in a circle and leadership plans the IPO. No cloud user would ever notice.
Like most cloud-hosted SaaS companies, we release like that multiple times a day. But with self-hosted customers, we have zero visibility. We can’t know when a migration is complete and we can ship another update.
Something as simple as adding organization_id
to every table can create issues. On the cloud version, we can confirm every user has migrated to the new way of storing data. For self-hosted customers, upgrading to a new release could mean they upgrade during a migration, which breaks data integrity (which would be a massive issue with billing).
And the organization_id
example is an easy one. This gets compounded even more when lookups are complex and involve multiple tables. Applying a tax to an invoice requires looking at multiple tables:
- Customers (to retrieve their location)
- Plans (to retrieve what prices to apply)
- Usage (to calculate the final charge)
- Taxes (to retrieve their tax rate)
This is just to determine one tax rate for one invoice. And when a customer emits millions of invoices a year, you really don’t want to break the system because you renamed a column.
Whatever you can ship quickly in the cloud becomes a headache when customers self-host. Renaming/removing a job queue, adding or removing a database column, removing an abandoned feature nobody’s using.
Any of these “hygiene” updates can torpedo a customer’s business. But we also don’t want to disadvantage our self-hosted customers.
How we release to avoid breaking customer instances
When customers manually self-serve updates, it means they control which version they’re on. That means they can decide to upgrade from a year-old build from 2 years ago to the most current version—which would probably be catastrophic. Migrations or queued jobs would become a minefield.
Why we still do bridge versions in 2025
I guess this is where I admit I’m aging and tell you that our startup, founded in 2021, has copied an engineering practice from the cutting-edge technology firm Cisco.
That practice is called a bridge version. No matter what version you’re on, you can upgrade to the bridge version and nothing will break. In our organization_id
example, this would mean we keep both the old data schema and the new one alive for a version.
That way, the old data is migrated correctly before upgrading to the latest version, which removes the old way of doing things and enables all of the new features.
If this sounds like a big pain compared to multiple releases a day, full infrastructure control and perfect data visibility—yup, it is.
And because it’s so complex, we abide by a simple rule:
Build for the worst case
There’s a Murphy’s Law aspect to software engineering: If a user can do it, they will. It’s not our customer’s job to understand the details of migrating billing data. We’re building Lago so companies don’t ever have to think about billing.
This means we build Lago in a way that even a 7th-grader equipped with nothing but Cold Brew and ChatGPT might eventually operate each instance. This won’t happen in practice, obviously. But it’s good to keep in mind that not everyone is obsessed with billing.
To most people, our product is like the plumbing of a house—you only think about it when it doesn’t work.
Will bridge versions keep working? It’s hard to say.
What happens when bridge versions stop working?
Billing might be extremely complex, but our product is still relatively simple. We’re not WordPress, where a jungle of plugins means the same update needs to work as well for a simple blog as they do for someone running a membership site.
Customers do build integrations with their own internal tools or extend Lago to work with niche tools we haven’t integrated with yet. But nobody has modified Lago to the point where the data schema is incompatible with the versions we release.
That point arrives for every open source project. At that point, additional engineering work is inevitable to maintain customized instances and keep them working as new updates come out.
That will likely mean more manual work for migrations to new versions, either via services from our side or dedicated engineers on the customer side.
ERPs like SAP have entire cottage industries and teams of developers for customization and maintenance. Shopify’s and WordPress’s enterprise customers probably aren’t using the product out of the box.
Focus on building, not billing
Whether you choose premium or host the open-source version, you'll never worry about billing again.
Lago Premium
The optimal solution for teams with control and flexibility.

Lago Open Source
The optimal solution for small projects.
