How to reduce your AWS NAT Gateway bill
Cost optimization strategies for AWS networking infrastructure, covering NAT Gateway alternatives and best practices for reducing cloud networking expenses.
I'm a software developer, consultant, entrepreneur, CTO, & co-founder of Compoze Labs.
I've been building software for 15 years, building teams for 10, and running an engineering organization for 5.
I started building software in the JVM world, and will always have a soft spot for Java. I quickly moved up the stack into the Cloud world, AWS will always be my favorite, but I have built and designed a lot of workloads in Azure and GCP. I'm obsessed with developer experience, and in 2025 I became officially AI-pilled.
You can find my writing and videos that have influenced my view of the software world as well as work that I think is worth sharing.
CONTENT
The AWS Well Architected framework is more than a set of best practices; it's one of AWS' best Marketing initiatives. It's trained thousands of engineers and architects on the best way to spend the most money in AWS. And while the values are sound, it requires a practical approach to build the right thing, for the right price.
Cost optimization strategies for AWS networking infrastructure, covering NAT Gateway alternatives and best practices for reducing cloud networking expenses.
A deep dive into the hidden costs of AWS NAT Gateway when using Fargate and ECR, and how to avoid unexpected networking charges.
Best practices for securing your AWS infrastructure through proper VPC network segmentation and isolation strategies.
A practical guide to publishing custom CloudWatch metrics from your Node.js applications using TypeScript and the AWS SDK.
Automating SSL/TLS certificate provisioning and DNS validation using AWS Certificate Manager and Terraform.
Practical guide to building a CLI tool for automated S3 bucket cleanup during infrastructure teardown using Go and Cobra.
Implementing custom email confirmations for AWS Cognito user signups using Lambda triggers and Terraform configuration.
Creating Amazon Machine Images with encrypted root devices using Packer—a repeatable solution for meeting PCI, HIPAA, and FISMA compliance requirements.
Quick, practical tips and tricks for everyday software development—from testing patterns to build tooling.
Exploring domain modeling patterns and understanding the trade-offs between anemic and rich domain models for testable code.
Understanding why assertEquals fails on objects and how to properly override equals() and hashCode() for reliable test assertions.
Leveraging NPM's pre and post hooks for custom scripts to automate workflows like building before deployment.
Comparing native apps, cross-platform frameworks, and web apps to help businesses choose the right approach based on performance, cost, and user experience trade-offs.
Foundation for building testable HTTP endpoints in NestJS with proper structure and test coverage from the start.
Implementing secure authentication and authorization patterns in NestJS applications with modern best practices.
Best practices for managing environment variables in NestJS cloud applications with type safety and validation.
Implementing comprehensive observability in NestJS applications with logging, metrics, and tracing for cloud-native monitoring.
Automating API documentation and client code generation in NestJS to accelerate development and reduce manual errors.
Building robust error handling and exception management patterns in NestJS applications with proper testing strategies.
Setting up consistent local development environments using Dev Containers for NestJS cloud applications.
Building and testing agentic AI systems with LangGraph. From unit testing individual nodes to experiment-driven evaluation with LangSmith—practical approaches to shipping reliable AI agents.
Hard-won insights from using AI coding agents in real-world production environments—what works, what doesn't, and how to get the most out of them.
Practical strategies for unit testing LangGraph agents, covering how to isolate and test individual nodes in your agentic systems.
Moving beyond traditional testing to experiment-driven evaluation of LLM-based systems using LangSmith's pytest integration.
Strategies for bridging legacy systems with modern applications—from abstraction layers and system modernization to eCommerce integration and headless architecture.
How abstraction layers serve as a strategic bridge between legacy systems and modern applications, enabling rapid digital transformation without complete system overhauls.
A practical roadmap for updating outdated technology through refactoring, replatforming, or replacing—treating modernization as a strategic, phased initiative.
Eliminating the gap between your eCommerce platform and ERP system through phased integration, from order flow automation to real-time inventory synchronization.
Understanding headless commerce architecture—decoupling frontend from backend to enable faster development, better omnichannel experiences, and future-proof systems.
Sometimes I just want to build something because it's awesome. My home lab is where I experiment with hardware, open-source software, and custom applications without worrying about whether it's the right choice.
A journey through building a hybrid compute cluster with Docker Swarm, Raspberry Pi 5s, and custom applications—making architectural decisions simply because they're awesome.
RECOMMENDATIONS
These are the articles, talks, and resources that have shaped the values I hold on building software.

This talk solidified my practical approach to TDD. Ian Cooper clarifies what Kent Beck actually meant: test behaviors, not implementation details. Every engineer that cares about building quality code should watch this.

Martin Fowler's classic article explaining the two fundamental approaches to TDD. While he doesn't firmly take a stance, it made me staunchly anti-mocks. Spoiler: I'm a classical TDDist.

This article inspired me to start Compoze Labs. I think a company's values should be in conflict with one another.

Blake Smith nails what code review is actually for: keeping the team mentally aligned, not nitpicking style. His hierarchy of needs for code review (team alignment > correctness > design discussion > style) has shaped how I approach PRs. The advice on framing feedback as questions is gold.

Fowler explains how useful terms lose their meaning as they spread—'agile' becoming meaningless, 'refactoring' used for any code change. Understanding this phenomenon helps you recognize when buzzwords have drifted from their original intent and why going back to primary sources matters.

This talk gave me an initial foundation on how to safely and responsibly use AI to build software. While the details will continue to evolve, the fundamentals that Dex Horthy lays out I think will continue to be important.
I've spent the past year working intensively with coding agents—Claude Code, Cursor, and others. And while my opinions and recommendations won't lead to many likes on Twitter (or whatever it's called now) they're what I believe will continue to be the core of agentic development for the (foreseeable) future.
These values have led me to adopting a Research → Plan → Implement workflow and patterns like Spec-Driven Development. The specific terminology will suffer from semantic diffusion eventually, but the underlying practices are sound.
LLM performance degrades significantly as context windows grow. This isn't a minor detail—it fundamentally shapes how you should work. Keep sessions focused. Know when to start fresh and pass context forward. Use compaction strategies (like Claude's /compact) before you hit the wall, not after.
Stop trying to one-shot features. The pattern that consistently works: start with "help me figure out how to build this," flesh out product and technical details, and only move to implementation when the plan is solid. Most harnesses have planning modes now—use them. Track plans in markdown files. Your future self (and your future agents) will thank you. Not everything needs a formal plan, of course, but when you're doing real work it will help you in the long run.
A lot of valuable context evaporates when we don't write down the decisions we make with our agents. I think of this like the ADR pattern, but for both human and AI consumption. Don't track everything—track the important decisions. Plans, architectural choices, the "why" behind non-obvious implementations.
Based on research, trusted industry voices, and my own experience: a human still needs to be in the loop. Not as a rubber stamp, but actively steering technical direction, product direction, and maintaining the context needed to support the software long-term. Think of it as code review—but for the entire development process.
Slash commands and skills are what turn coding agents from chat interfaces into repeatable development workflows. Commands let you codify how you work — research, planning, implementation, testing — into reusable, composable steps. Skills provide the building blocks: patterns for exploration, documentation formats, and verification criteria that commands compose together. The terminal below shows what this looks like in practice.
The ecosystem is standardizing around a set of primitives that make agent-driven development repeatable and shareable across teams. These are the key building blocks.
Project context and instructions loaded at startup
~/.claude/ (global) or ./CLAUDE.md (project)# Project: My Application ## Overview This is a Next.js application using TypeScript, Tailwind CSS, and shadcn/ui. ## Architecture - /app - Next.js App Router pages - /components - Reusable React components - /lib - Utility functions and configurations ## Conventions - Use functional components with TypeScript - Prefer server components where possible - Use Tailwind CSS for styling - Follow the existing patterns in the codebase ## Important Notes - Always run tests before committing - Use conventional commit messages - Keep components small and focused