We Moved Our Company Off €200–400/Month of SaaS and Onto a €10/Month Self-Hosted Stack
The line item that never appeared in one place
Between €200 and €400 a month. That is what our business software cost before we cancelled it. Not one invoice: the total of a CRM, a task tracker, a wiki, team chat, password management, automation, analytics and a few smaller tools, each billed per seat, each on its own plan, for two to five people.
The range is deliberate. It is what happens when vendor pricing depends on the plan tier you actually need and the number of seats you buy. At entry tiers the same bundle prices out at roughly a third of what it costs at the tier we needed. We will show the arithmetic later, because the arithmetic is the point.
No single invoice showed the total. The number lived across several accounts and plans, and it grew quietly every time someone new needed access to something. GPO is a software and hardware development company in Tallinn. We are not a cost-cutting shop; we sell engineering. But this bill was the one number in the business that stayed abstract until we sat down and added it up.
Then we replaced it. 18 services, of which 12 are used by people and 6 keep the rest running, on two small servers, with a third that only stores backups. About €10 a month in hosting for all three. One week to build the services, one day to deploy, a few days to move the data and onboard the team.
This is the story of that switch, the numbers behind it and the catch nobody puts in a landing page.
Four things that pushed us
The bill was annoying. Annoyance does not move a small company to rebuild its stack. Four specific things did.
Seat creep: every new person multiplies
Per-seat pricing sounds fair until you price the next three hires. Adding three more people to our stack, at November 2026 list prices, would have added roughly €100–120 a month across HubSpot Starter, Jira, Confluence, Slack Pro and 1Password. That is before any plan change. Adding them to our self-hosted stack costs €0.
Then there is the user type the model does not have: an AI agent that reads the CRM, answers questions and writes records. Per-seat software assumes every user is a person who signs in and clicks. Agents are neither. In our CRM, Claude via MCP and n8n are first-class users, and an extra agent seat has no price because there is no seat.
Contractors, interns and agents all land on the same bill in SaaS. In our stack they land on the same server.
Tier cliffs: the same seats, ten times the price
The second push was tier pricing. HubSpot Sales Hub Starter lists at $10 per seat per month when billed monthly. Professional lists at $100 per seat per month, plus a $1,500 onboarding fee. Same company, same number of people, ten times the per-seat price for features that are not ten times more useful to a small team. All figures here are vendor list prices, November 2026.
That gap is why we quote our old bill as a range. We know the total. We do not have a clean single plan to point at, and pretending otherwise would be dishonest. The variance is one vendor’s tier, and that is the whole problem with SaaS pricing in one sentence.
Data you can export but not really take
Export is not ownership. Every tool we paid for offered an export button, and every export gave us a snapshot of records, not a working system. The CRM export from 13 September 2026 arrived as the truth about our pipeline, and the truth was messier than the dashboard suggested: contacts never touched, mailboxes still connected, form submissions nobody had read.
When the data sits in someone else’s product, the export is always a copy made on their terms. When it sits in Postgres on our server, it is a database we can query, join, clean and back up ourselves.
Customisation as someone else’s roadmap
We run a CRM, not a generic pipeline demo. Custom fields, objects and pipeline changes are how a CRM fits a real company. In SaaS, those are feature requests. In our fork of Twenty, they are a commit, and the AGPLv3 licence means we can make that commit without asking.
One honest note here: the CRM still has roadmap items. Audit logging, invoicing, an e-Äriregister lookup and IMAP sync are things we are building, not things we claim are live today. Owning the code path does not make a feature exist. It just removes the permission step between wanting it and building it.
What we replaced
The complete stack is 18 services on two servers: Vault on its own machine, everything else on the main one. A third server only stores backups. The six decisions that mattered most:
| Cancelled | Running now | Status |
|---|---|---|
| HubSpot CRM | GPO CRM, our fork of Twenty (AGPLv3) | Deployed; we own the code and the data |
| Jira | Kaneo 2.49.0 | Deployed |
| Confluence | Wiki.js 3.3.0 | Deployed |
| Slack | Matrix Synapse 1.164.2 + Element 1.12.33 | Deployed |
| 1Password | Vaultwarden 1.37.5, used with the official Bitwarden apps | Deployed |
| PostHog Cloud | Self-hosted PostHog | Done — on our own server |
The rest of the stack: n8n 3.2.0 for automation, RustFS 1.0.4 for S3-compatible file storage, FeatBit 6.1.1 for feature flags and wg-easy 15.5.0 for WireGuard VPN. Website forms and scheduling moved off HubSpot embeds to our own form handling and SMTP delivery; that switch went live on 31 October 2026.
PostHog is different in one detail: it was usage-based rather than per-seat, so it is not one of the five paid accounts. It now runs on a self-hosted instance on our own server. The per-seat accounts are cancelled.
The build: one week, one day, a few days
The honest timeline is short. One week to build the services, one day to deploy them, a few days to move the data and onboard the team. The evidence is in the repository: the infra repo was initialized on 2 November 2026 and had roughly 60 commits by 11 November. The CRM fork was imported on 28 October, and the website dropped the HubSpot integration on 31 October.
That timeline hides the parts that took thought. Four of them are worth naming, because they are the parts anyone repeating this will hit.
Secrets. Every service needs credentials, and the
lazy version is a .env file next to the compose file. We
banned that. Ansible generates each secret once into Vault; every
service gets its own Vault AppRole and policy; a Vault Agent renders the
secrets into a memory-only directory under /run/ for that
service, and the container mounts only its own directory, read-only.
Nothing secret sits in the repository, and one compromised service does
not hand over the rest. Vault itself runs on its own server, apart from the services it protects. This was the largest design decision in the build.
Backups. restic, encrypted, daily. Retention is 7 daily, 4 weekly, 12 monthly, to the server disk and to a separate backup server that runs our own MinIO. The targets are an RPO of 24 hours and an RTO of 4 hours. The weekly verification is automated: it reads back 20% of the data and, when a snapshot contains the Postgres dump, restores it into a throwaway Postgres container with no network and counts the databases and tables. Results go to Matrix. What is still on the list is a full restore drill on a temporary server. Weekly verification is not the same thing as a disaster-recovery rehearsal, and we say so.
Email delivery. This is the part everyone underestimates. The website forms now deliver through our own SMTP path instead of a vendor embed. Deliverability is a project in its own right: reputation, authentication, monitoring and the patience to see where messages land. If we were starting again, we would treat email as a workstream from day one rather than as a checkbox at the end.
Adoption. Element and the official Bitwarden apps felt familiar enough that people kept their habits. The CRM needed more care, because a tool the team will not open is not cheaper, it is just unpaid. Migration is only finished when the last person stops keeping a private spreadsheet as a backup.
One more thing belongs here. The whole stack is Ansible, with one deploy command per service, and the README is written so that an AI agent can rebuild it from scratch. That was a deliberate design constraint, not an afterthought. It forces the documentation to be executable instead of decorative.
The migration in numbers
The HubSpot export from 13 September 2026 is the most honest audit we have ever run on our own sales operation. These are the numbers we moved and the numbers we found:
- 1,371 contacts and 572 companies.
- 14 deals, none of them closed.
- 585 notes and 282 meetings of CRM history.
- 10 mailboxes connected, of which 3 were active.
- 445 cleanup candidates (test addresses, spam, service senders) and 103 GPO Home Messenger chats.
- 54 contacts with no company link.
- 508 contacts still marked NEW: never contacted.
- 16 form submissions from drones.gpo-tech.com sitting in a mail Trash, where nobody saw them.
- 4 concrete demo or partnership requests that had gone unanswered.
The invoice was not only for software. It was also for storing a mess that nobody had time to clean. Moving the data forced the audit, and the audit found money we had already lost: unanswered requests, untouched contacts, leads that arrived and died in a mailbox.
That number, 508, is the one that stayed with us. It is what a CRM looks like when nobody owns the process.
The bill today
Hosting is about €10 a month for all three servers, or roughly €120 a year. The old bill was €2,400–4,800 a year. The difference is €190–390 a month in cash, or about €2,300–4,700 a year.
The one-time cost was one engineer-week. If you price that week as an assumption, five days at €25–45 per hour, it is €1,000–1,800 of engineering time. That is our assumption, not our invoice, and it is the number to argue with if you are building the same case for your company. Against a cash saving of €190–390 a month, payback is roughly 3 to 9 months. That range exists because both the one-time cost and the monthly saving are ranges, and we would rather show both ends than pick the flattering one.
For context, the vendor arithmetic that produced our old bill: at list prices from November 2026, the tools we used, for three seats at entry tiers, come to about $143 a month, or roughly €130. With the HubSpot tier we actually needed, the same three seats come to about $383 a month, or roughly €350. That spread is the honest range for a company our size, and it is also why the number never felt stable.
None of this was ever a single invoice. The €200–400 is a total across accounts. The €10 is the hosting total. The full arithmetic, including the 3-seat and 6-seat cases, is in the cost breakdown article that follows this one.
Numbers at a glance
- SaaS bill before: €200–400/month, or €2,400–4,800/year
- Hosting now: ~€10/month, or ~€120/year
- Saving: €190–390/month, or ~€2,300–4,700/year
- Stack: 18 services on two servers, plus one for backups (12 people use, 6 keep it running)
- Build: one week. Deploy: one day. Migrate and onboard: a few days
- One-time build: ~one engineer-week, or €1,000–1,800 as a stated assumption
- Payback: ~3–9 months on that assumption
- Migrated: 1,371 contacts, 572 companies, 585 notes, 282 meetings
- Found during migration: 508 never-contacted contacts, 16 lost form submissions, 4 unanswered demo requests
- Users: 2–5 people, plus AI agents at €0 per seat
What got better beyond the bill
Cost was the reason to start. It stopped being the most interesting part within a week.
No per-seat pricing, including for agents. Claude via MCP and n8n are first-class users of our CRM. Every extra human or agent seat costs €0. That changes how you use a system: you can give an agent access to a pipeline without opening a purchase order.
The data is ours. 1,371 contacts and 585 notes moved from HubSpot into Postgres on a server we control, in the EU. No processor sits between us and our pipeline.
We own the code path. The CRM is our AGPLv3 fork of Twenty. Custom fields, objects and pipelines are a commit, not a feature request. This is also where the roadmap lives: audit log, invoicing, e-Äriregister lookup and IMAP sync are being built, not claimed.
Fewer third-party scripts on the website. We removed the HubSpot integration from gpo-tech.com on 31 October 2026. Lead forms now deliver by our own SMTP. That is one less vendor on a public page, and one less script between a visitor and a form.
Fixed, predictable cost. Three small servers at a fixed monthly price, no usage-based invoices and no seat creep. Storage and traffic are bounded by fixed quotas, which means the bill does not change when the team grows.
Rebuildable and documented. The whole stack is an Ansible repository, one deploy command per service, documented so an AI agent can redeploy from scratch. That same stack can be handed to a client.
Boring security is included. Vault-managed secrets delivered to processes in memory, a Postgres role and database per service, no published database ports, encrypted daily backups, fail2ban with a honeypot, and weekly vulnerability scans.
A cleaner CRM. The migration audit found the 508 never-contacted contacts, the 16 lost form submissions and the 4 unanswered demo requests. Self-hosting started with cleaning house instead of paying to store the mess. That is not a technical win. It is a sales win that came bundled with a technical change.
What it costs besides money
Self-hosting is cheaper in euros and more expensive in attention. Both halves are true, and the second half is where most optimistic case studies go quiet.
Ops hours. Our working assumption is two to four hours a month for routine upkeep, and more in the weeks after a version bump. That is an estimate, not a measurement, and it belongs in any honest comparison. At an assumed €25–45 per hour, that is €600–2,160 a year, which is real money even if it never appears on an invoice.
The main server is a single point of failure. Vault runs on its own server and backups go to a third, but every application still runs on one machine. If it dies, the tools are down until we restore them. The inventory is service-per-host, so any service can move to its own machine by changing one line; Vault was the first to move. Anyone who tells you a setup like this has no availability risk is selling something.
Upgrade discipline. Pinned image versions, upgrades after a backup, a rollback path for each service. Doing this monthly is boring. Skipping it for a quarter turns boring into an incident.
You are the platform team. There is no vendor support line and no managed uptime SLA. Alerts go into Matrix, emergency notifications go to Telegram, and the README recommends an external monitor for the pieces that matter most. The project’s own monitoring is deliberately thin, and it is a fair criticism.
Email is a project. The website forms are on our own SMTP path now, and deliverability needs ongoing attention. If email is not something you want to own, buy it.
Backups must be tested. We verify weekly and restore the Postgres dump into a throwaway container. The full drill on a temporary server is still pending. A backup you have never restored is a hypothesis, not a backup.
None of this removes the saving. It prices it correctly.
When we would tell you to stay on SaaS
This is the section most self-hosting posts skip. We do not think every company should do this, and a few cases are clear enough to name.
Your team has nobody to own infrastructure. Paying a vendor is cheaper than paying attention you do not have. If no one on the team wants to own backups, upgrades and secrets, the stack will work until it doesn’t.
Your bill is small. A three-person team paying for two tools might spend less in a year than a week of engineering time. Run the arithmetic before the migration, not after.
Compliance and procurement need a vendor’s paperwork. In regulated environments, part of what you buy is the vendor’s certifications and contracts. Self-hosting moves that work to you, and it does not get cheaper because the software is open source.
The open-source alternative is immature. Some categories still have no good self-hosted answer for a small team. Buy those tools and keep the rest.
Email deliverability is not a project you want. Use a provider for outbound email unless you are prepared to own reputation, authentication and monitoring.
You cannot test a restore. A self-hosted stack without tested backups is a liability, not a saving.
Self-hosting is not a discount. It is a trade of money for attention. That trade has been good for us because building and running systems is what we do for a living. For another company it may be a bad deal, and we would rather say so than pretend the switch costs nothing.
If you want this stack
If you want this stack, or want to know what your version of it would look like before you build it, talk to us. We are GPO, a software and hardware development company in Tallinn. We run this stack ourselves, and we build and hand over systems like it for clients. The first conversation is about your bill, your data and your team’s appetite for owning it, not about a product. If the honest answer is that self-hosting is wrong for your team, we will say so before you build it — the cheapest stack is the one you can defend.