Drawing the line between Dev and Ops

- IaC
- AWS
- DevOps
- Serverless
- Terraform
Intro
In the modern, cloud native web, the line between core infrastructure resources and application resources is getting less clear. In this article, I look at my evolution of use of IaC tools over my career so far, and how I'm approaching splitting up the management of core infrastructure and application infrastructure today.
Background
In many ways in life I am set in my ways - but not when it comes to web development. Perhaps that is the JavaScript curse. The ecosystem moves so quick that it becomes a bit tricky to pick a stack and stick to it forever (but it can be done!). If you're anything like me, the way you do things is always changing. I started out many years ago writing just HTML & CSS (it took me way too long to get to grips with JS), before moving onto PHP, discovering Laravel, and then making the jump to full stack JS/TS six years ago.
This is also the time I discovered IaC. Once I got hooked on JS, I started exploring the vast ecosystem of all the cool things people were making. Perhaps this is the moment when my old attitude of sticking to ye ol' reliable (Laravel) vanished for good. I stumbled upon the AWS CDK and wondered how, for all these years, I had been provisioning everything by hand.
Now, the problem with tools is they are not perfect. And while the CDK is pretty darn good, CloudFormation leaves a lot to be desired. I put up with it for a while, but the more I used this imperfect tool, the more the rough edges started to frustrate me. So, as you can imagine, when I was introduced to Terraform in 2024, I jumped ship.
Now Terraform (Although I use OpenTofu now) is absolutely fantastic. One could say near enough perfect?! So my last couple of years using it have been pure zen. Until I started noticing more imperfection. But this time it wasn't so much an imperfection in the tool, but an imperfection in my workflow.
It all started when I decided to give serverless a try again after seeing my AWS bill just for running 2 Fargate tasks and an ALB. I have dabbled with serverless in the past, but my biggest issue has been finding it hard to draw the line between application and infrastructure. If my function is now an AWS resource, is it part of my cloud infrastructure or is it part of my application? Terraform is great for cloud provisioning infrastructure, but something felt wrong about using HCL to define cloud resources so tightly coupled to my application.
So I started looking for something simpler to provision application specific cloud resources. Something more 'developer land' rather than 'ops land'. Despite the existence of modules, Terraform is inherently low level and verbose. Add that to my "always learning, always improving, never creating two projects in the same way" attitude, sometimes I find myself coming back to a project after a few weeks/months and I've got no idea why I did something a particular way.
The Solution
Introducing SST. Previously I have looked at SST when researching the state of IaC tools, but dismissed it as being an overly simplified, less capable IaC tool for developers who don't know anything about ops, written in a developer language language (JS for infra? Sacrilege. Long live HCL!). But now see it filling in the role of controlling infrastructure tightly coupled to the application.
So I decided to take it for a spin. But this time, I'm not ditching the previous tech, unlike when I left my beloved CDK for Terraform. As I said, the tool was not the problem, rather my workflow. I wanted to find a way for SST and Terraform to work together - leaving my core AWS infrastructure to Terraform, and application specific resources for SST. Now, anything shared across applications, such as: the VPC, NAT instances, my RDS database, DNS zones and non-application specific DNS records, generic S3 buckets etc, remain in my main Terraform codebase, and anything application specific, such as: Lambda functions, CloudFront distributions and S3 buckets for web static asset storage are managed by SST.
The result is now, in the last two days:
- I've been able to delete thousands of lines of Terraform code across 5 different web projects and migrate them all to SST so fast it doesn't feel fair. I've freed up my cognitive burden doing so, as each project's Terraform was configured similarly but slightly different, and not having to remember exactly why some things were done a certain way is nice.
- Bootstrapping and deployment for each SST project now takes minutes
- SST deploys rapidly and in very few steps - a stark contrast to the multi step bootstrapping process I previously had.
Now, given I've been satisfied with SST, that doesn't mean it's without it's flaws. As I said before, no tool is perfect. First, I do feel some level of vendor lock in, although this is absolutely minimal. SST manages secrets across different stages which are stored encrypted in a proprietary state management system, and injects them into the application which can then be read using a special Resource object. Normally I would have used AWS SSM parameter store SecureStrings and injected them into the environment.
Second, I don't agree with all architectural decisions.
- SST creates two CloudFront distributions when a redirect is configured, which means I have two distributions for each site, because I like to redirect the domain root to
www - SST uses a placeholder CloudFront origin and creates a CloudFront function for routing to origins. I get why these decisions were made ultimately. It's designed to be flexible for different projects needs.
Conclusion
The biggest takeaway I've had from this, is learning to accept giving up some control and flexibility. Managing everything in Terraform is great because I can make things exactly as I want - but exactly what I want doesn't stay the same forever, and I still have projects I'd like to maintain from many years ago.
I keep thinking I need 'ultimate control' and for everything to be 'production grade' but actually managing IAM policies, S3 bucket policies etc. for a static website with a handful of pages is just not necessary. It's a lot to think about constantly, and a lot of code to duplicate project to project. Being able to tell SST "This app is a TanStack Start app - deploy it" and it does everything is lovely, as SST also handles the deployment as well as the infra, so no more needing to write a script to upload assets to S3 and create CloudFront invalidations. Not to mention having my SSR web apps running in Lambda for pennies every month instead of running as Fargate tasks makes my wallet very happy.
As someone who developed a lot of strong opinions on development & deployment over the years, I've always struggled to find balance between wanting ownership of everything, and making things only as complicated as absolutely necessary.