DevOps
-
AI Code Reviewers Won't Save You
Dropping an AI reviewer into your pull request pipeline is just a band-aid. Tools like CodeRabbit or Greptile are great for catching syntax errors or basic anti-patterns, but they can’t assess architectural intent or domain-specific business logic. They’re spell-checkers for code. Useful, sure. But nobody ever said “our codebase is solid because we run spell check.”
AI doesn’t change your engineering baseline. It just accelerates it. If your foundational guardrails are weak, agentic tools will help your team generate technical debt at unprecedented speeds. So the real question isn’t “how do we review AI code?” It’s “how do we build systems that prevent slop from ever reaching production?”
Shift Left, Hard
When engineers use agents to scaffold a new Go service or spin up a SvelteKit frontend, they’re inevitably pulling in generated dependencies or utilizing unfamiliar libraries. Models hallucinate packages. They suggest insecure patterns with total confidence.
Your CI pipeline needs to be ruthless before a human ever looks at the code. Aggressive SAST and SCA should automatically block PRs that introduce vulnerable dependencies or hardcoded secrets. If the agent generates slop, the pipeline rejects it instantly. No discussion.
Make the Agents Write the Tests
Agents are incredibly eager to generate feature code, but humans are historically lazy about writing the tests for it. The influx of AI-generated code means human reviewers can’t possibly step through every logic branch manually.
So flip the script. Use the agentic tools to build the guardrails themselves. Mandate that any generated feature code must be accompanied by generated, human-verified unit tests. If an agent writes a sprawling TypeScript function, the build should fail if the test coverage doesn’t meet a strict threshold. You’re already using AI to write the code. Use it to prove the code works, too.
Context Boundaries Matter
Bloated AI output often happens because the model is given too much context or allowed to generate too much at once. Heavyweight IDEs with aggressive multi-file auto-completion can easily create cascading messes across a codebase.
Define strict architectural boundaries and API contracts upfront. Agents should be tasked with solving small, well-defined, modular problems. “Write a function that parses this specific JSON schema” is a good prompt. “Build the backend” is not. The tighter the scope, the less room for generated nonsense.
Observability Is Your Safety Net
You can’t catch all generated slop at the PR level. Some of it only reveals itself under load. An agent might write a technically correct query that causes an N+1 database issue, or introduce a subtle memory leak that passes all unit tests.
Your ultimate safety net is what happens at runtime. You need an airtight observability stack to trust the velocity AI brings. Logs, distributed tracing, metrics, all feeding into dashboards your team actually watches. When generated code hits staging, you need the immediate telemetry to spot performance regressions before they reach production.
Redefine the Human Review
Because AI makes the “typing” part of coding trivial, the human code review needs to fundamentally shift. Reviewers should no longer be looking for missing semicolons. They should be asking: “Does this component fit our architecture?” and “Did the agent over-engineer this solution?”
Train your senior engineers to review for intent and systemic impact. That’s the stuff AI genuinely can’t do yet. Leave the syntax checking to the robots.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
What Is a Runbook and Why Should You Care?
If you’ve ever been woken up at 3 AM by a pager and stared at your screen trying to remember how the database failover works, you already know why runbooks matter. You just might not have had one yet.
A runbook is a step-by-step guide for handling a specific operational scenario. Database goes down? There’s a runbook for that. Failed deployment needs a rollback? Runbook. Routine certificate rotation? You get the idea. They range from simple markdown files to fully automated scripts where a human only needs to click “approve.”
That’s the idea anyways. The impact of having good ones versus not having CAN be massive.
Why They Matter
When something breaks in production, your brain is not at its best. Adrenaline kicks in, Slack is blowing up, and suddenly you can’t remember if you’re supposed to restart the service first or check the connection pool. A runbook takes the thinking out of the equation. You follow the steps. You restore the service. You go back to sleep.
This directly lowers your Mean Time To Recovery (MTTR). Instead of spending twenty minutes in a group call debating what to try next, you open the runbook and start executing.
Runbooks also solve the consistency problem. If five different engineers respond to the same alert five different ways, you’re rolling the dice every time. One of those approaches might cause a secondary outage. A runbook ensures everyone follows the same diagnostic and remediation path, which means fewer surprises.
And then there’s the tribal knowledge issue. Every team has that one senior engineer who knows exactly how to fix the weird thing that happens once a quarter. What happens when they’re on vacation? Or they leave the company? A runbook gets that knowledge out of their head and into a document the whole team can use.
It also makes onboarding way faster. New engineers can start handling on-call rotations with confidence instead of hoping nothing breaks on their watch.
Treat Them Like Code
This is the part a lot of teams get wrong. Runbooks shouldn’t live in a random Confluence page that hasn’t been updated since 2023. They should live in version control. Sometimes they’re kept in the repo with the code. Other times they’re kept separate. It’s up to you. It’s up to the team on where to put it.
If a developer changes how a service authenticates or connects to a database, the associated runbook needs to be updated in the same pull request. An outdated runbook is worse than no runbook at all. It sends engineers down the wrong path during an outage, which burns time and trust.
Share Early, Share Often
A runbook sitting in someone’s private folder is doing exactly nothing for your team.
Start during the draft phase. Have someone who didn’t write the runbook try to follow it. If they get confused or stuck, the runbook needs work. This is the cheapest way to find gaps.
When a new service is heading to production, the runbook should be part of the readiness review. I’d argue a service shouldn’t go live without one. And after an incident, if the runbook was wrong or didn’t exist, creating or fixing it should be a mandatory action item from the post-mortem.
One more thing. Practice them. Run game days where the team actually walks through runbooks before a real emergency happens. The worst time to discover your runbook has a missing step is when production is on fire.
So Here We Are
Runbooks aren’t glamorous. Nobody’s giving a conference talk about the beautiful runbook they wrote last quarter. But they’re the difference between a calm, methodical incident response and a panicked Slack thread full of guesses. Write them, version them, share them, and practice them. Your future self will thank you.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
What Temporal Actually Does (And Why You'd Want It)
Building a multi-step process across microservices usually goes something like this. You wire up a message queue, add retry logic, build a state machine backed by a Postgres
statuscolumn, throw in some cron jobs, and pray. It sounds complicated because it is.Temporal is an open-source “durable execution” system that replaces all of that duct tape with a single, opinionated framework. Lets break it down.
Workflows and Activities
Temporal splits your application into two concepts:
- Workflows are your business logic, written in standard code (Go, Python, TypeScript) using a Temporal SDK. They must be deterministic. They define the order of operations, branching, loops, and error handling.
- Activities are the actual tasks your services perform. HTTP requests, database writes, external API calls. Activities are where the non-deterministic, real-world work happens.
When a workflow runs, it executes on your own worker services. Every time it schedules an activity, starts a timer, or completes a step, the Temporal Server records that event internally. If the worker crashes, another worker picks it up, replays the workflow’s event history to the exact point of failure, and resumes. No data loss. No half-finished state.
All of all the things that you would have to build yourself simplified Into A framework that handles it for you.
What It Replaces
Without something like Temporal, teams generally land in one of two camps:
- Choreography (event-driven): Services emit and listen to events through a message broker like Kafka or RabbitMQ. Highly decoupled, sure. But in practice it turns into a pinball machine. There’s no single place to understand the flow of a business transaction. Debugging becomes detective work across dozens of services and topics.
- Ad-hoc orchestration: You build a custom state machine with a database, message queues, background workers, and cron jobs. Then you write a ton of boilerplate for retries, dead-letter queues, and idempotency. Every team ends up building a slightly different version of this, and none of them are great.
Temporal gives you the reliability of a custom state machine without making you build and maintain one.
Why It’s Worth Looking At
A few things stand out:
- Durable sleep. A workflow can execute
sleep(30_DAYS). Temporal suspends the execution, frees the worker’s resources, and wakes it back up a month later exactly where it left off. Hard to do with a cron job. - Built-in resiliency. Exponential backoffs, timeouts, and retry policies are configured on the activity invocation. You’re not writing custom
whileloops andtry/catchblocks to handle network jitter. - Centralized observability. Instead of piecing together distributed traces or searching through logs to figure out why step 4 of 7 failed, the Temporal UI shows the exact execution state of every workflow. Inputs, outputs, errors, all in one place.
- Code over configuration. Unlike AWS Step Functions or YAML-heavy tools like Airflow, you write workflows in a real programming language. You can unit test them, store them in version control, and run them through your normal CI/CD pipeline.
That last point is worth reading and thinking through again. If your orchestration logic lives in code, it gets all the benefits code gets. Reviews, tests, refactoring, IDE support. Visual workflow builders look great in demos, but they don’t scale the way code does.
Should You Use It?
Temporal isn’t free in terms of operational complexity. You’re running the Temporal Server (or paying for Temporal Cloud), and your team needs to understand the replay model and determinism constraints. It’s not something you bolt on to a simple CRUD app.
But if you’re managing distributed transactions with queues, cron jobs, and hand-rolled state machines, Temporal is worth a serious look. It takes the hardest parts of that problem and makes them someone else’s. Durability, retries, observability. All handled.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
pgvector vs Pinecone: You Probably Don't Need a Separate Vector Database
Every time someone starts building a RAG pipeline, the same question will come up: do I need a “real” vector database like Pinecone, or can I just use pgvector with the Postgres I already have?
I can imagine teams agonizing over this decision for weeks. So maybe this will save you some time?
The Case for Staying Put
If you already have a PostgreSQL instance in your stack, adding
pgvectoris almost always the right first move.You manage one stateful service instead of two. Your existing backup strategy, monitoring, and security all stay the same. Your vector embeddings live next to your metadata, so you get ACID compliance and standard SQL joins. No syncing between two data stores. No eventual consistency headaches.
Performance? From what I found, for datasets under a few million vectors,
pgvectorwith HNSW indexes is fast. Really fast. It satisfies the latency requirements of most applications without breaking a sweat.And you’re not paying for another SaaS subscription…
When Pinecone Actually Makes Sense
Pinecone is a purpose-built vector database designed for high-dimensional data at massive scale. It’s serverless and fully managed.
If you’re dealing with hundreds of millions or billions of vectors, a specialized engine handles memory and disk I/O for similarity searches more efficiently than Postgres can. Pinecone also gives you native namespace support, metadata filtering optimized for vector search, and live index updates that are faster than re-indexing a large Postgres table.
Those are real advantages. At a certain scale.
The Decision Is Simpler Than You Think
Stay with Postgres + pgvector if:
- You want to minimize infra sprawl and moving parts
- Your vector dataset is under 5 to 10 million records
- You rely on relational joins between vectors and other business data
- You have existing observability and DBA expertise for Postgres
Consider Pinecone if:
- Your Postgres instance needs massive, expensive vertical scaling just to keep the vector index in memory
- You don’t want to tune HNSW parameters,
mmapsettings, or vacuuming schedules for large vector tables - You need sub-millisecond similarity search at a scale where Postgres starts to struggle
That is what I would use to make that decision.
Most teams are probably nowhere near the scale where Pinecone becomes necessary. They have a few hundred thousand vectors, maybe a million or two. Postgres handles that without flinching. Adding a separate managed vector database at that point is just adding operational complexity for no measurable benefit.
The trap is thinking you need to “plan ahead” for scale you don’t have yet. You can always migrate later if you actually hit the ceiling. Moving from pgvector to Pinecone is a well-documented path. But moving from two services back to one because you overengineered your stack? That’s a conversation nobody wants to have.
Start with what you have. Add complexity when the numbers force you to, not when a vendor’s marketing page makes you nervous.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
How Kong Actually Works in Kubernetes
At some point with microservices in Kubernetes, basic Ingress routing stops being enough. Kong is interesting router that I would like to try in the future.
It’s an API Gateway built on top of NGINX and OpenResty. It operates at the infrastructure layer, managing the actual HTTP traffic flowing into your cluster. Drop it into a Kubernetes environment and it acts as an Ingress Controller. It does that job really well.
The Ingress Controller Problem
We should review what an ingress controller is. In case you’re familiar, unfamiliar with its job in Kubernetes. An
Ingressresource is just a set of routing rules. “Send traffic forapi.example.com/v1to theuser-servicepod.” Kubernetes doesn’t actually route traffic itself. It needs a controller to read those rules and move the packets.The Kong Ingress Controller (KIC) runs as a pod inside your cluster. It watches the Kubernetes API server for changes to Ingress resources, Services, and Endpoints. When someone deploys a new app and creates an Ingress rule, KIC picks it up, translates the Kubernetes config into Kong’s native format, and reloads the proxy. No manual intervention.
How Traffic Actually Flows
When external traffic hits your cluster, the path looks like this:
- External Load Balancer forwards traffic to the Kong proxy pods
- Kong evaluates the incoming request against its routing table (headers, paths, hostnames)
- Plugins execute before routing, handling cross-cutting concerns at the edge instead of inside your application code
- Upstream routing sends traffic directly to Pod IPs, bypassing
kube-proxyfor better performance
That plugin step is where Kong really earns its keep. Rate limiting, API key auth, mTLS, request transformation. All of that happens at the gateway layer so your services don’t have to think about it.
CRDs Make It Actually Useful
Standard Kubernetes Ingress is pretty limited. Host-based routing, path-based routing, and that’s about it. Kong extends this with Custom Resource Definitions:
- KongPlugin lets you attach behaviors to routes or services. Deploy a manifest to enforce rate limits, require API keys, or add mTLS to a specific endpoint.
- KongConsumer manages user identities and credentials directly in Kubernetes, so you can tie routing rules or rate limits to specific clients.
This means your API gateway configuration lives right alongside your application manifests. Version controlled, reviewable, deployable through your normal CI/CD pipeline.
Skip the Database
Kong used to require PostgreSQL or Cassandra to store its routing config. In modern Kubernetes deployments, you almost always run it in DB-less mode instead.
Why? Kubernetes already has
etcdas its source of truth for cluster state. Running a second database just for the API gateway adds overhead and failure modes you don’t need. In DB-less mode, Kong stores its configuration entirely in memory. The Ingress Controller reads state from Kubernetes and pushes updates to the proxy dynamically.This is one of those decisions that sounds minor but changes everything about how you operate Kong. No database backups to worry about. No schema migrations. Your gateway config is just Kubernetes manifests managed through GitOps.
Observability at the Edge
Sitting at the edge of the cluster, Kong is perfectly positioned to capture metrics, logs, and traces. With the right plugins, it exports traffic data (latency, status codes, request volumes) directly into whatever observability stack you’re running.
You get visibility across your entire microservice architecture without instrumenting every individual service.
Kong isn’t the only Ingress controller out there, but the combination of plugin architecture, DB-less mode, and CRD-based configuration makes it a solid choice if you need more than basic routing. If you’re already running Kubernetes and find yourself writing the same auth and rate-limiting logic across multiple services, moving that to the gateway layer is worth your time.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
Agentic Development Trends: What's Changed in Early 2026
I’ve been following the agentic development space around Claude Code and similar tools and the last couple months have been interesting. Here’s what I’m seeing as we move through March and April 2026.
From Solo Agents to Coordinated Teams
The biggest shift is that more people are moving away from trying to build one agent that does everything. Instead, we’re seeing coordinated teams of specialized agents managed by an orchestrator, often running tasks in parallel. I think this is the more proper use of these systems, and it’s great to see the community arriving here.
If you’re curious about the different levels of working with agentic software development, I created an agentic maturity model on GitHub that goes into more detail on this progression.
Long-Running Autonomous Workflows
Early on, agents handled what were essentially one-shot tasks. Now in 2026, agents can be configured to work for days at a time, requiring only strategic oversight at key decision points. Doesn’t that sound fun? You’re still the bottleneck, but at least now you’re a strategic bottleneck.
Graph-Based Orchestration
Frameworks like LangGraph and AutoGen are converging on graph-based state management to handle the complex logic of multi-agent workflows. I think this makes sense when you consider the branching and conditional logic of real-world tasks could map naturally to graphs.
MCP Is Everywhere
MCP (Model Context Protocol) has become the industry standard for tool integration. All vendors fully support it, and there’s no sign of slowing down. Every week there are new MCP servers popping up for connecting agents to different services and tools.
Unified Agentic Stacks
The developer tooling is becoming more consistent. Cursor is becoming more like Claude Code, and Codex is becoming more like Claude Code. Maybe you see a pattern there… might tell you something about who’s setting the pace.
What is also noteable, people are experimenting with using different tools for different parts of the workflow. You might use Cursor to build the interface, Claude Code for the reasoning and main logic, and Codex for specific isolated tasks. Mix and match based on strengths.
Scheduled Agents and Routines
Claude Code recently released routines or scheduled or trigger-based automations that can run 24/7 on cloud infrastructure without needing your laptop. Microsoft with GitHub Copilot are working on similar capabilities? Cursor had something like this a while back too.
Security Gets Serious
Two things happening here. First, people are getting better at leveraging agents for security reviews and monitoring. Tasks that previously required highly specialized InfoSec expertise. You no longer need to be a hacker to find vulnerabilities; you can let your AI try to hack you.
However, the same capabilities that harden defenses can also be used for offensive attacks. We’re seeing a major push for security-first architecture as a requirement for all new applications, specifically to defend against the rise of agentic offensive attacks. Red team and blue team are both getting AI-pilled.
FinOps: Watching the Bill
Last on the list is financial operations. Inference costs now account for over half of AI cloud spending according to recent estimates. Organizations are prioritizing frameworks that offer explicit cost monitoring and cost-per-task alerts. Getting granular about how much you’re spending to solve specific problems and optimizing at the task level. I think that’s pretty interesting and something we’ll see a lot more tooling around.
The common thread across all of these trends is maturity. We’re past the “wow, an AI wrote code” phase and into “how do we make this reliable, secure, and cost-effective at scale.” That’s a good place to be.
-
What Companies Are Actually Paying for Application Security
In the Application Security Testing (AST) market, Static Application Security Testing (SAST) and Software Composition Analysis (SCA) represent the two most critical pillars of preventative cyber defense.
So as a part of that, we should talk about the thing that people normally can’t or don’t talk about and that is cost. Vendors like to hide their pricing behind “contact sales” buttons, and buyers end up negotiating based on hard to find information.
So here’s an unofficial look at what companies are actually paying, pulled from a Deep Research report provided by Gemini. At the very end there is a list of resoruces where you can learn more about these subjects. However it is important to mention, there are not a lot of viable options for the home/hobby market.
What the Market Looks Like
Vendor Average Mid-Market / SMB Spend (Annual) Average Large Enterprise Spend (Annual) Economic Dynamics and Negotiation Factors Snyk ~$47,428 ~$222,516 Costs scale rapidly with developer headcount. Highly susceptible to volume discounting. Total cost includes separate quoting for onboarding and services. Black Duck (Coverity) $60,000 – $120,000 (50-100 devs) $150,000 – $300,000+ (150+ devs) Full platform deployments (SAST + SCA) often range from $300k to $600k+. Volume discounts and custom enterprise agreements are typical. Premium support adds 20-30%. Checkmarx $35,000 – $75,000 $100,000 – $250,000+ Pricing is considered complex. Hidden costs include mandatory professional services, premium support, and infrastructure overhead, adding 15-35% to year-one totals. Veracode $40,000 – $80,000 $100,000 – $250,000+ Application-based pricing feels predictable until microservice architectures cause application counts to explode. Discounts are heavily available for SAST+DAST+SCA bundles. SonarQube $30,000 – $50,000 (up to 5M LOC) $80,000 – $180,000 (5M - 20M+ LOC) Highly predictable LOC model. However, self-managed deployments incur separate infrastructure and administrative overhead costs not reflected in the software license. HCL AppScan $50,000+ $100,000 – $500,000+ Unified platform pricing for large deployments can easily exceed $1M. Implementations often require months of setup and heavy professional service fees. Official Licensing Models and Published Structures
Vendor / Platform Primary Pricing Metric Published Entry-Level / Standard Tier Pricing Enterprise Pricing Status Key Inclusions & Pricing Caveats Snyk Per Contributing Developer Team Tier: ~$52–$98 per developer/month ($624–$1,176/year). Custom / Unpublished Includes Snyk Code (SAST) and Open Source (SCA). Enterprise plans drop per-seat costs at high volume but require minimum seat counts. SonarQube Lines of Code (LOC) Analyzed Developer Edition: ~$15,000 for 1M LOC. Smaller tiers available (e.g., ~$2,500 for 100k LOC). Annual Pricing; Talk to Sales Prices scale strictly by the largest branch of private projects. Enterprise Edition adds legacy languages. Advanced Security is an add-on. GitHub Advanced Security Per Active Committer $19/user/month (Secrets) + $30/user/month (Code) = $49/user/month. Custom / Add-on to Enterprise ($21/user base) GHAS is strictly an add-on to the GitHub Enterprise plan. Tied directly to commit activity within a 30-day window. Mend.io Per Contributing Developer AppSec Platform: Up to $1,000 per developer/year. Included in upper bound limit Includes SAST, SCA, Renovate, and AI Inventory. No limits on LOC, scans, or applications. AI Premium is an extra $300/dev. Checkmarx Custom (Historically Per App or Node) Team Plans: ~$1,188/year base. Enterprise base starts ~$6,850/year. Custom / Unpublished Highly modular pricing based on developer count, module selection (SAST, SCA, DAST), and deployment model. Veracode Per Application or Per Scan Basic plans start at ~$15,000/year for up to 100 applications. Custom / Unpublished Pricing heavily depends on application count, scan frequency, and support levels. SCA alone starts around $12,000/year. Black Duck (Coverity) Per Team Member / Custom Coverity SAST: $800–$1,500 per team member annually. Custom / Unpublished Pricing scales with user access. Often bundled. Perpetual licenses with 18-22% annual maintenance fees exist for legacy deployments. Contrast Security Custom (GiB hour / usage) Essential tier: $119/mo. Advanced: $359/mo. Enterprise base ~$6,850/yr. Custom / Unpublished Pricing varies by package (AST vs. Contrast One managed service) and workload throughput. HCL AppScan Per Scan / Enterprise License SaaS: ~$313 per scan (min 5 scans). Basic Codesweep: $29.99/scan. Custom / Unpublished Enterprise suite pricing is highly customized, often requiring significant upfront capital expenditure. Feature Comparison
Feature / Capability Snyk Veracode Black Duck Checkmarx Mend.io GitHub (GHAS) SonarQube Endor Labs Primary Strength Developer Adoption & Speed Enterprise Governance & Low FPs License Compliance & Deep SAST Unified ASPM & Repo Scanning Automated Remediation Native Ecosystem Integration Code Quality & Baseline Security Noise Reduction & Reachability Reachability Analysis Basic No No No Advanced No No Full-Stack (95% reduction) Automated AI Fixes Yes (DeepCode) Yes (Proprietary Data) No Yes (Limited IDE) Yes Yes (Copilot) Yes (CodeFix) Yes (Without upgrades) Compilation Required No Yes (Binary) Yes (Coverity) No No No No No Broad Language Support High (14+) Very High (100+) High (22+) High (35+) Very High (200+) Moderate High (40) Moderate License Compliance Moderate Moderate Enterprise-Grade Moderate Enterprise-Grade Basic Basic Moderate Learning
-
Is There Something Better Than JSON?
Have you ever looked at a JSON file and thought, “There has to be something better than this”? I have.
JSON has served us well. It works with everything, and it’s human readable. It’s a decent default, don’t get me wrong, but the more you use it, you’ll find its limitations to be quite painful. So before we answer the question of whether there’s anything better, we should describe what’s actually wrong with JSON.
The Problems with JSON
First, there’s no type system. No datetimes, no real integers, no structs, no unions, no tuples. If you need types, and you almost always do, you’re on your own.
Second, JSON is simple, which sounds like a feature until you try to store anything complicated in it. You end up inventing your own schema, and the schema tooling out there (JSON Schema, etc.) gets verbose fast. Because the spec is so loose, validation can be inconsistent across implementations.
There’s more: fields can be reordered, you have to receive the entire document before you can start verifying it, and there are no comments. You can’t leave a note for the next person explaining why a config value is set a certain way. That’s a real problem for anything that lives in version control.
The Machine-Readable Alternatives
Now, there are plenty of binary serialization formats that solve some of these issues. Protobuf, Cap’n Proto, CBOR, MessagePack, BSON. They’re all interesting and have their place. But they’re machine readable, not human readable. You can’t just open one up in your editor and make sense of it. So let’s set those aside.
The question I’m more interested in is: is there something better than JSON that you can still read and edit as a text file?
It turns out there are two solid options.
Dhall
Dhall is a programmable configuration language. Think of it as JSON with all the things you wish JSON had: functions, types, and imports. You can convert JSON to Dhall and back, and it’s just a text file you can open in any editor. The name comes from a character in an old video game, and the language itself is interesting enough that it’s worth your time to explore.
CUE
CUE stands for Configure, Unify, and Execute. It’s similar to Dhall in that it fills the gaps JSON leaves behind, like types, validation, and constraints, while staying human readable. Where CUE really pulls ahead is in its feature set. You can import Protobuf definitions, generate JSON Schema, validate existing configs, and a lot more. In terms of raw capabilities, CUE has more going on than Dhall.
JSON isn’t going anywhere. But if you’re looking for something interesting to explore, check out both of these. They make great fun little side projects.
-
Multi-Repos Are Underrated
If you’re considering a monorepo, I’d like you to, stop, and reconsider. Monorepos cause more problems than they solve, and I think multi-repos deserve way more love than they get.
The “Shared Libraries” Argument
The pitch usually goes something like this: “If we put everything in a monorepo, we can have shared libraries across multiple applications.” Okay, sure. But let’s talk about what’s actually happening here.
This is almost always closed-source, internal code. You don’t have a public package registry to lean on. And maybe your org hasn’t approved a private package hosting service. So the monorepo becomes the path of least resistance, not because it’s the best solution, but because nobody wants to fight for the budget to host private packages.
But, actually, private package hosting for most languages doesn’t cost a lot. You can host private packages in GCP pretty easily, but there are several affordable options. However it can depend somewhat on the language.
Monorepos often exist because nobody fought for the right infrastructure, not because it was the right call.
Coupling Will Eat You Alive
Probably the biggest problem with monorepos is coupling. You can very easily introduce tightly coupled dependencies across several applications. Now you can’t update your libraries safely because two completely different applications are using the same one, and nobody wants to touch it.
You know that feeling within a single application where there’s tightly coupled code without proper abstractions? Congratulations, now you have that problem across several different applications.
This is why we have packages with versions. Would you make breaking changes to an API and not version it?
Are we not engineers dedicated to a craft? Version your packages. Version your APIs.
Let the applications that pull in dependencies manage their own upgrades. If something worked on version 1.2 and breaks on 1.3, either fix your application or stay on the old version. That’s the whole point of versioning.
CI/CD Becomes a Nightmare
Monorepos make your CI/CD pipelines absolutely terrible to work on. Not only does it make things harder for everyone on the team to work with their applications day-to-day, but now your build and deploy pipelines are a tangled mess.
There are going to be undocumented parts of the monorepo tooling, like little hidden landmines waiting to kneecap you when you least expect it.
What About NX?
Yes, I’ve used NX. I don’t want to get into and re-traumatize myself, but chances are most of your team secretly hates it. I’ll use it if I’m forced to. But if it’s my decision? No-thX.
A Multi-Repo Example
From my own work: api2spec has fixture repos for Hono, Express, chi, gin, Fastify and many more all in a separate repositories.
They test the same tool against different frameworks across many different programming languages. Putting them in a monorepo would’ve complicated things significantly. Instead we have separate repos under the same GitHub org with consistent naming convention. Simple not Stupid.
For The Love of All that Is Holy Do Yourself A Favor and stick with Multi-Repos
Multi-repos give you clear boundaries, independent versioning, simpler CI/CD, and teams that can move without stepping on each other.
Yes, the overhead of managing separate repositories is real, but it’s a manageable and with good hygiene, the much preferred path over a never ending battle with your own tooling.
The monorepo pitch sounds great in a meeting. The reality is coupling, pipeline complexity, and a team that’s afraid to merge.
-
I switched to mise for version management a month ago. No regrets. No more
brew upgradebreaking Python. Built-in task runner replaced some of projects that were using Makefiles.Still juggling nvm + pyenv + rbenv?