Firebase

It was the early days of iOS development...well for me at least. I was developing the MVP of my first iOS app Polinu (now available in the app store) and since I was already familiar with AWS, I decided to go that route when deploying my backend.

It was pretty simple, or so I thought.

My backend was nodejs and typescript which was then deployed in a docker container which was then put into an EC2 container in AWS. Then I configured the ALB which is AWS's load balancer. The load balancer starts with 3 instances of the original EC2 container and if needed, it would allocate another 3 instances and so on. Then I configured the target groups, which are the thing the ALB actually points at. The load balancer doesn't really "know" about my instances directly — it forwards traffic to a target group, and the target group holds the list of healthy instances. To figure out which ones are healthy, I set up a health check: the ALB pings something like /health on each instance every few seconds, and if an instance stops answering, it quietly stops sending it traffic. That part felt clever. It also turned out to be the first of about ten things I'd need to keep straight in my head.

(Quick correction to my own past self, since I got this slightly wrong for a while: the load balancer isn't actually the thing spinning up new instances. That's the Auto Scaling Group. The ALB just spreads traffic across whatever's currently healthy. The ASG is what watches CPU or request count and decides "okay, we need three more of these." Two different boxes doing two different jobs that I kept mentally merging into one.)

So here's roughly the pile of things I ended up touching, in the order you tend to hit them.

The image had to live somewhere

A Docker container doesn't magically appear on an EC2 instance. You build the image locally, then push it to a registry so the instance can pull it down. On AWS that's ECR (Elastic Container Registry). So before any of the fun stuff, I was tagging images, authenticating to ECR, and pushing — and then making sure the instance had permission to pull from it (more on permissions in a second).

The networking stuff I half-remember

This is the part I genuinely blanked on, and it's the part that quietly underpins everything else: the VPC. Every AWS account gets a virtual private cloud — basically your own isolated network. Inside it you carve out subnets, and the good-practice version is to spread them across multiple Availability Zones so that if one AWS data center has a bad day, you're still up. Public subnets for the load balancer (the thing that needs to face the internet), private subnets for the EC2 instances (the thing you'd rather the internet couldn't poke at directly). Then there's an Internet Gateway so the public side can actually reach the outside world, route tables telling traffic where to go, and — if your private instances need to make outbound calls — a NAT Gateway.

I remember setting most of this up, nodding like I understood it, and never wanting to look at it again.

Security Groups, aka the firewalls

This one I do remember because it bit me. Security Groups are virtual firewalls, and the clean way to wire them up is: the load balancer's security group allows ports 80 and 443 from anywhere, and the EC2 instances' security group only allows traffic from the load balancer's security group, on the app's port. Not from the whole internet — just from the ALB. So the instances aren't directly reachable; everything has to come through the front door. The first time I set this up I opened the instance port to the world by accident, which works great and is also exactly what you're not supposed to do.

HTTPS, which is its own little quest

You can't ship an API over plain HTTP, so you need a certificate. AWS has ACM (Certificate Manager) for this, and the nice part is the cert is free and auto-renews. You request one, prove you own the domain (usually by adding a DNS record), and then attach the cert to the load balancer's listener on port 443. Then you add a second listener on port 80 whose only job is to redirect everyone to 443. So now there are two listeners, a target group, health checks, and a cert — all just so a request can reach my Node app over HTTPS.

DNS — Route 53

Once all of that exists, the domain still has to point at it. That's Route 53, AWS's DNS. You create a hosted zone for your domain and add an A record — specifically an alias record — pointing at the load balancer. That's what turns api.mydomain.com into "the thing in front of my instances." If you bought your domain elsewhere, you also point that registrar's nameservers at Route 53 so it's actually in charge.

The glue: IAM and CloudWatch

Two things I almost forgot because they're invisible until they're not. IAM is permissions — the EC2 instances need a role that lets them pull from ECR and write logs, and I needed a user with the right access to deploy any of this. Get an IAM policy slightly wrong and nothing works, with an error message that politely refuses to tell you why.

And CloudWatch is where the logs and metrics live. Container logs, load balancer metrics, and the alarms that actually drive the Auto Scaling Group ("CPU over 70% for five minutes? scale out"). It's the dashboard that tells you whether any of the above is healthy.

And this is where I lost the thread

If you've been counting, that's a VPC, subnets across AZs, an internet gateway, route tables, security groups, ECR, EC2, an Auto Scaling Group, an ALB with two listeners and a target group and health checks, an ACM certificate, Route 53, IAM roles, and CloudWatch — and I've probably forgotten two or three more, which is sort of the whole point of this post. None of it is hard, exactly. But it's a lot of moving parts to hold in your head when the thing you actually wanted to build was a language-learning app, not a data center.

At some point I realized I was spending more time being a part-time infrastructure engineer than an iOS developer.

Enter Firebase

The switch to Firebase wasn't because AWS was bad — it's genuinely powerful and I learned a ton. It was because, for a solo developer shipping an MVP, almost everything above is overhead I didn't need to own.

Here's what collapsed when I moved over:

The whole EC2 + Docker + Auto Scaling Group + load balancer stack became Cloud Functions. I write a function, I deploy it, and Google handles where it runs and how many copies exist. No instances to manage, nothing to scale by hand.

The VPC, subnets, security groups, internet gateway, NAT — gone from my plate entirely. Firebase handles the networking.

ACM and the listener-redirect dance became... nothing. HTTPS is just there.

Route 53 became a single domain connection step in the console.

My self-managed database became Firestore, and authentication became Firebase Auth — two more things I no longer babysit.

CloudWatch's job is now just the Firebase console: logs and metrics in one place I didn't have to wire up.

The honest trade-offs

I'm not going to pretend Firebase is free of cost. You trade control for convenience. You're more locked into Google's ecosystem than I was on AWS. Cloud Functions have cold starts, so an idle endpoint can be a beat slower than an always-warm EC2 instance. And the pricing model is different in a way worth watching — you're paying per invocation and per read/write rather than for a box that's always on, which is cheaper at small scale and can surprise you at large scale.

But for where Polinu was — one developer, an MVP, a need to actually ship — none of those trade-offs outweighed getting most of an afternoon back every time I wanted to change something on the backend.

AWS taught me how all the pieces fit together, and I don't regret the time. But Firebase let me go back to building the app instead of maintaining the thing the app runs on. For this stage, that was the whole game.