← All posts

Deploying Modern Web Services for Your Tech Startup

After over a decade in big tech and then 3 years of running web services in a small startup - this is what I wish I knew if I were starting now for my use cases.

Build tools - Use Bazel

I had big tech experience with Bazel, so this was a natural fit for me. Importantly, it:

  • Is a polyglot build tool - allowing it to work across my monorepo of TypeScript, Kotlin, Golang and C++.
  • Supports remote builds and remote caches (not that I've needed this on a small codebase).
  • Works well with AI tools - e.g. "I want you to take this checked-in JSON file, and generate these TypeScript files from it with Bazel and have them used in foo" - and it one-shots it.

By using Bazel in 2026 - you get to go straight to bzlmod - which is a much nicer way to handle external dependencies than the old WORKSPACE files.

Remember to use bazelisk, which is a small wrapper around Bazel that allows you to specify which version of Bazel should be used in your codebase, and upgrade it with a normal commit.

Programming languages - Use Golang

I was used to using C++ for backend code in big tech. There were a lot of well-lit paths for it there, with great libraries, but I've found both the external ecosystem a bit less active, and just much harder to integrate with.

Specifically, I spent a decent amount of time fighting with rules_foreign_cc to hook these libraries into the Bazel system (think 30 minutes to a day per library). This includes figuring out how to include their transitive dependencies. With the Bazel Central Registry, more of this is made easier by helpful maintainers (e.g. OpenSSL).

I have found it much easier to work with Golang (think a couple minutes to integrate, assuming no C/C++ transitive dependencies) - given a more extensive standard library and the relative ease of adding dependencies - even when hooked in with bzlmod. Also, much faster compile times.

I do highly recommend, whatever system you are using, you spend time to make sure auto-complete works well - especially since the editors are not tuned to work with Bazel by default, e.g. instructions for Golang.

Orchestration - Use Managed Kubernetes

I know this is hotly debated on Hacker News - but I find Kubernetes to be a great way to run your services. While there is a lot you can learn, I find the simple case of "I want N replicas of my stateless server with a loadbalancer in-front" is very straight-forward as a Deployment. And when it's time for more complexity, you get to stay in the ecosystem.

This immediately gives you horizontal scalability, and you can treat VMs as cattle instead of pets.

Unless you've got a very strong need, I recommend just using a managed Kubernetes. Both Vultr and DigitalOcean have worked fine.

Deployment - Use Pulumi

Almost all Kubernetes tutorials are a series of imperative kubectl commands (create a namespace, add a deployment, ...), but other than temporary debugging, avoid this as much as possible.

Instead use systems like Terraform or Pulumi. They allow you to specify your intended state of production (e.g. create a managed k8s cluster, create a namespace, and then put in a deployment in it).

I've found Pulumi to be easier - since you can use standard programming languages. It can also be hooked into Bazel so you just run bazel run [deployment target] to deploy to production.

My favourite part is being able to do multi-provider changes in a single command, for instance - by specifying this declaratively you can create a:

  • CockroachDB Cloud instance
  • Managed Kubernetes cluster
  • cert-manager instance in the Kubernetes cluster that has access to your DNS server
  • TLS Certificate for your domain from cert-manager
  • Deployment in the Kubernetes cluster that takes the address of the CockroachDB cloud instance and path to the TLS certificate as command-line arguments
  • Loadbalancer in the Kubernetes cluster that points to the deployment
  • DNS records for your domain with the IP(s) assigned to the loadbalancer at creation

Other than the initial set-up of the domain name's nameservers to point to your DNS infrastructure, the rest is driven by Pulumi. That means you can create a dev instance of your stack (or even per dev!) very easily.

Database - Use CockroachDB

While SQLite, Postgres and MySQL can take you far - they are not made horizontally-scalable from the ground up.

I've really enjoyed using CockroachDB. It uses the Postgres wire format, is horizontally-scalable and while it doesn't support the entire Postgres dialect, it supports enough of it.

They even have a Cloud version that has free quota, so you can just point your k8s deployment at it. They did change their licenses recently, but as long as your annual revenue is <$10M / year, you can even run it on your own machines.

There are very cool features like REGIONAL BY ROW which allows a single table to have per-row geographic preferences. This allows reads to stay within a region, while writes still go to multiple regions (with most replication policies). Another is Global Tables which allows for data to be read within every region without consulting others, at the cost of much slower writes.

Observability - Use OpenTelemetry

To have traces, I've found OpenTelemetry to work well.

I've been using Honeycomb, but if you use OpenTelemetry libraries, then it's provider-agnostic.

Consulting

Need extra hands on your next build?

We take on select consulting engagements - architecture reviews, hands-on implementation, or unblocking a stuck project. Book a free intro call.

Book a free call