The tools we reach for on client projects and our own products, and the reasoning behind each choice.
People ask what we use. The honest answer is “whatever fits the client”, but there is a default stack we reach for when nothing forces a different choice, and it is worth writing down. Every item here has earned its place on real projects. Where we changed our minds, we say so.
Principles before tools
Three rules shape the list. First, boring beats novel for anything that has to run unattended. Second, the client’s team must be able to operate it after we leave, which rules out anything that needs a specialist on call. Third, we prefer tools that are easy to remove. Lock-in is a cost we would rather not bill you for later.
Data: SQL first
Most of the value in a data project comes from getting the data right, and most of that is SQL. We model transformations in dbt, because it turns a folder of queries into something testable and documented. For warehouses we have shipped on BigQuery, Snowflake and plain PostgreSQL. For a company under a few hundred gigabytes, Postgres with sensible indexes is often the right answer, and we say so even when a warehouse would look more impressive.
For orchestration, Airflow remains the default when a client already runs it. For greenfield projects we increasingly use Dagster, whose asset model matches how we think about pipelines. Either way, every scheduled job has an owner, an alert and a retry policy.
Modelling: gradient boosting, then everything else
For tabular problems, which is most business problems, LightGBM and XGBoost are where we start and often where we finish. They train fast, handle messy features gracefully and explain themselves through SHAP. PyTorch comes out for sequences, images and anything where the structure of the data matters more than the columns.
For forecasting we use the Nixtla libraries for classical and machine learning baselines, and only reach for deep models when backtests justify them. They rarely do on business data, and a good baseline with careful features wins more often than the papers suggest.
Experiment tracking runs on MLflow. It is not the most elegant tool, but it is the one every client can host and every engineer already knows.
Generative AI: models are a commodity, evaluation is not
We use commercial models from OpenAI and Anthropic when the data can leave the client’s infrastructure, and open-weight models hosted in EU regions when it cannot. The model choice matters less than people expect. Retrieval quality, prompt design and evaluation matter more.
For agents and multi-step flows we use LangGraph, mainly because its explicit state machine is easier to reason about and debug than the alternatives. Retrieval runs on pgvector inside Postgres for most projects; a dedicated vector database is rarely justified below tens of millions of chunks.
The piece we insist on is an evaluation suite: a set of real questions with reference answers, scored automatically on every change. Without it, every prompt tweak is a guess.
Serving: FastAPI and containers
Models reach users through FastAPI services in Docker containers, deployed on whichever cloud the client runs. Azure and Google Cloud are most common among our clients; we have shipped on AWS and on-premises too. We keep services small, stateless and observable, with structured logs and a health endpoint from the first commit.
Observability: Grafana for everything
Model metrics, input drift, latency and cost all land in Grafana, usually backed by Prometheus or the client’s existing monitoring. One dashboard per model, with the same four panels every time: volume, accuracy or quality, drift and errors. Consistency means anyone can read any dashboard.
Our own products: Astro on the front
For product front-ends and sites, including this one, we build with Astro. It ships almost no JavaScript by default, renders fast on cheap hosting, and lets us add interactivity only where a page needs it. Tailwind handles styling. Cloudflare Pages handles hosting and edge functions. The whole setup costs less per month than a coffee, and that matters when you run several small products.
What we stopped using
Notebooks as the unit of delivery. They are excellent for exploration and terrible for production, so code moves into packages with tests before anything ships. Kubernetes for small workloads; a container on a managed service is enough for most models and far easier to operate. And “AI platforms” that promise to do everything: every one we have evaluated did one thing well and four things badly, and the client paid for all five.
The point
None of this is exotic, and that is deliberate. The stack is chosen so that a client’s engineering team can run it, extend it and, if they ever want to, replace it. Good tooling is the part of our work you should be able to forget about.