May 29, 2025malcolm-matalka

How we're beating $359M in funding with two people and OCaml

Terrateam is a two-person startup, built in OCaml, competing with HashiCorp. We've bootstrapped it, open-sourced it, and stayed lean. Somehow, we are consistently evaluated next to our highly funded competition by potential customers. And sometimes we win.

Punching above our weight

As co-founder of Terrateam, there are many things we have accomplished that I am proud of, but as CTO, I am especially proud of how much we've accomplished given our meager resources. Our goal is to develop the most flexible and expressive infrastructure management tooling in the world. For most of Terrateam's life, it has been a two person company. Myself, as CTO, doing almost all of the software development and my co-founder doing operations, both business and technical. We decided in the beginning that Terrateam would be a bootstrapped company. It would be a lean company. It would be built with our own money, time, and energy.

Our competition is, by and large, very well funded. HashiCorp is probably the best known company in this space, having received $359m in funding, and now being part of IBM, it is a gorilla. env0 ($41m) and Spacelift ($22m) have each received tens of millions of dollars in funding as well. There are many, smaller players out there, with single digit millions in funding.

Yet we routinely are evaluated alongside them with potential customers. Sometimes we win the contract, sometimes not. But we are at the table despite putting in the equivalent of a few hundred thousand dollars in salary. Are we beating our competition in actual dollar figures? No. But are we beating our competition in terms of building a sustainable company? We think: absolutely yes.

How are we able to accomplish so much with so little? I have thought about that a lot and the truth is, I don't really know. The best I can do is explain what I believe makes us unique and maybe there is a clue in there.

Co-founder trust

To begin with, my co-founder and I have been close friends for over 20 years. We trust each other. We can have difficult discussions without fear of that creative tension turning into personal tension. A good example of this was deciding to make Terrateam open source. I thought this was a bad idea for various reasons that, looking back, had nothing to do with reality. We had some very heated discussions about it. But it never got personal. It took a bit but we found a way to have a constructive conversation about it and now we're open source.

Being such longtime friends also means our values are very aligned. We don't want to build a billion dollar company. We want to build a sustainable company that we will be with for the rest of our lives. I think everyone starting a company has been told to never get into business with friends and my experience has been the opposite.

Fair and honest pricing

As people we have always valued being fair and honest and a small company is a reflection of its founders, so Terrateam is fair and honest. This is probably most apparent to the outside world in our pricing. Like any startup, we've changed it several times during the life of the company but its almost always been slight variations on the same theme. Going back to being a bootstrapped company, we feel we can charge a price that reflects the actual problem we are solving. We want to solve the problem of infrastructure management and we want to do it very well. But infrastructure management is a commodity.

None of us in this space are doing it fundamentally different from each other. None of us are revolutionizing DevOps. It doesn't need a revolution. It needs to let you solve your problems and get out of your way. The extremes in pricing are out of control. We saved a customer $400,000 per year. Their existing provider wasn't doing anything more for them than us but their provider just wanted to maximize the deal size. We are a company, we need to pay the bills, but we also want to create more value than we extract.

Software development philosophy

I've been passionate about software development for most of my life. Not just writing software but understanding how to write software. I see it as a craft that I am constantly improving. A big influence for me has been SQLite. SQLite is extremely high quality software, quite complex, and maintained by only a few people. What are the choices one has to make to maximize the size and complexity of a piece of software while minimizing the number of people needed to maintain it? Part of the founding story of Terrateam is testing those decisions against reality.

Almost all of Terrateam has been written by one person. Not only that, it's been written in a relatively esoteric language: OCaml. Not only that, it uses almost no existing frameworks, most everything is implemented from scratch. And the product does a lot. The feature list is extensive.

The philosophy of software development in Terrateam is focused around one thing: semantics. What do things mean? Whenever we develop a feature, make a change, fix a bug, we think deeply about the semantics of the change. By understanding the semantics, you can create a cohesive and robust piece of software. A significant benefit to focusing on semantics is that you avoid implementing features that prevent you from implementing other features in the future. Or you at least know it will be difficult.

It all sounds very abstract but the end result is not only software that is very flexible and expressive but a majority of feature requests and bug reports can be fixed in only a few days with only a few lines of code.

For a concrete example, consider our "tree builder" functionality. By default, on a pull request, Terrateam queries the VCS provider for what files have changed in the pull request. But if you manage your infrastructure through some sort of templating mechanism, the actual underlying Terraform code may never exist in the repository. It may be a configuration file that, on the fly, gets expanded out into the actual code via code generation. Terrateam needs to know which Terraform files have changed in order to run the correct workspaces. Tree builder solves this problem by allowing the user to configure a program to run that will generate what the file tree would look like if the code were checked into the repository. Creating a "virtual repository" tree. Implementing this feature took around half a day to write and a day and a half to test.

This could be implemented quickly because we created our own workflow engine that has well defined semantics around what work is executed on the Terrateam server and what work is executed on the customer's compute. It can jump between them with little work from the developer. The effort to support tree builder was mostly a matter of adding the code to run the configured program in the customer's compute and then continue on with the rest of the workflow.

This mindset gives us a lot of optionality. We don't know, nor do we want to predict, how infrastructure management will evolve. The Terrateam software has very few assumptions built into it. While we have focused on Terraform and Tofu, we support Terragrunt, CDKTF, and even Pulumi. With our custom engines support, you can use Terrateam to orchestrate any software you want. But the codebase isn't layer upon layer of abstraction and confusion, like a lot of Java code is often criticized as (especially in the early days). It is well-defined components that compose to build a more powerful tool.

This approach also has made Terrateam quite robust. This is important to us because a lot of our customers want to self-host, either with the open source edition or the enterprise edition. It's one thing to be able to run and operate your own software in the environment of your choosing but it's a totally different world when supporting a setup in another environment. Being able to build a mental model of how Terrateam works is essential in supporting those users. Often times we can infer what's going on given the description of the symptoms because we know how Terrateam should work.

I talked about these topics on the SE Radio podcast.

Focused scope

Finally, Terrateam has managed to maintain a tight scope. We only work on GitHub. We focused on a pull request based UX. We have a minimal UI. All of this is to direct our energy on making a powerful and expressive engine. The foundation of Terrateam is solid. Now we are able to expand our scope.

We get there with OCaml

I am not a big believer in tech stacks making a significant difference. I think that the tech stack that you and your team is both most proficient in and most excited, day in and day out, to use is the best tech stack you can choose. I do think there are some attributes to OCaml that make it a good choice, but for the most part any language with a good type system would probably meet the requirements as well. Specifically, OCaml allows expressing a lot with very little code. I think it is similar to Haskell, in that way, however not as extreme. Even I struggle to understand some Haskell code. OCaml is more "practical" in that sense. Being able to pack a lot into very little but stay readable, in my opinion, is a great strength of OCaml.

Secondly, leaning on the OCaml type system means you can do very aggressive refactorings successfully. In a startup, this can be a big advantage as you might have to change quite a bit as your situation changes. With OCaml, you can effectively make the change you need, and keep on fixing compiler errors until it succeeds, and you're done. In reality, it's more subtle than that, but not by much. It does depend on using the type system and being disciplined with it.

An example of this is our Typed_sql module. Typed_sql is not a DSL for writing SQL. Instead it is a DSL for providing type annotations to SQL, where the statement is represented as a string. Typed_sql does not try to verify that the type annotations you add are correct, it depends on you to not make a mistake. This turns out to be totally fine. The observation is that syntax and type bugs in SQL statements show themselves fairly quickly. Write a test that runs the SQL and you know if you have a syntax error or the types aren't what you expect. But what's harder to enforce is that every call site of that SQL statement is both passing in and receiving the correct types. Typed_sql solves that problem. You can refactor database code aggressively, update the type annotations, and then let the compiler lead you to each call site which needs to be fixed.

An example of an annotated SQL statement. Typed_sql does do a little bit of processing in that it transforms variables like $installation_ids to a numeric prepared statement variable, like $1.

let select_installations () =
  Pgsql_io.Typed_sql.(
    sql
    //
    (* id *)
    Ret.bigint
    //
    (* login *)
    Ret.text
    /^ "select id, login from github_installations where id = ANY($installation_ids)"
    /% Var.(array (bigint "installation_ids")))

And a usage of that SQL expression. Note that the function passed in to ~f must match the types of the Ret annotations and the values passed in after ~f must match the types Var annotations.

Pgsql_io.Prepared_stmt.fetch
  db
  (Sql.select_installations ())
  ~f:(fun id name -> { id; name })
  (CCList.map (fun { id; _ } -> id) installations)

Vision

Terrateam's vision is to create the most flexible and expressive infrastructure management tooling in the world.

We have put a lot of thought into what success looks like to us and how to deliver that vision. Infrastructure management is a commodity. It is important. But still a commodity. Our limited resources is also our strength. We can build a product that solves the exact problem people have and then gets out of the way.

That is why I truly believe we are able to compete. The reasons above are how we get there but at the end of the day we understand our customers' pain points and we competently solve them, and we do it without being needy or self-important.

With our strong foundation, we have expanded the team and that means we are expanding our scope. We are investing more complete UI and orchestrating more infrastructure tooling. Slow and steady wins the race.