How I Accidentally Became the DevOps Guy

July 31, 2026

2089 words


I have been deploying full stack products for more than three years now, from simple CRUD apps to real-time systems and long running tasks. But until recently, I had never used AWS, GCP, or any traditional cloud provider directly. I had never even created an account on one. For most personal and work products, the modern SaaS and PaaS (Software as a Service and Platform as a Service) ecosystem was more than enough. My stack for the last two years was mostly Supabase for Postgres, authentication, and object storage, Vercel for frontends and Render for servers.

I loved the Render + Supabase + Vercel combo because it was easy to operate. Deployments, logs, and metrics worked out of the box, and the infrastructure rarely demanded fixes. Vercel even gave every project a free URL, which was perfect for putting something online and showing it to people. Supabase was similarly hard to argue against as It provided authentication, Postgres, object storage, a table viewer, and a SQL editor in one place. The performance was good enough for what I was building, and the free tier was generous. I used this stack for almost every personal project and for roughly the first year and a half of my work at an early stage startup.

But with time costs changed, Render’s legacy Hobby plan included 100 GB of outbound bandwidth, but the new Hobby plan reduced that to just 5 GB. Although Render made additional bandwidth cheaper but losing 95 GB of included bandwidth was still a major turnoff for me. For the instance sizes we compared, Render’s managed compute could also cost close to twice as much as the raw compute on AWS or GCP. It is not a completely fair comparison because Render also gives us deployments, logs, metrics, and a much simpler operating experience, but the difference becomes harder to ignore as the system grows. It becomes especially visible when every product got development, staging, and production environments. AWS would still require those environments, of course, but repeatedly paying the higher per-service price makes the total climb quickly.

Supabase itself was not the problem I actually love Supabase. Once we decided to move the larger system to AWS, keeping the database there through RDS (Relational Database Service) made more sense than leaving a major dependency outside the new infrastructure. That is how I ended up planning an AWS migration without having used a traditional cloud provider even once before.

One of our first decisions was to use Terraform from the start. If the entire infrastructure were one EC2 instance, creating it manually from the AWS console would probably be fine. But once we have several projects, environments, accounts, permissions, and connected services, remembering every console setting and repeating it correctly becomes unrealistic. This migration covered four projects. Some had development and production environments, while others also had staging. A few needed equivalent deployments across different AWS accounts for client companies and our own team. Terraform turned that setup into a repeatable and deterministic step instead of me keeping everything in my head.

I learned Terraform while doing the migration, so I would not claim to understand every corner of it and hence I stayed with the foundations. I kept databases, compute services, and the other parts of the infrastructure organized separately. I reused the same Terraform definitions with environment-specific values, while names, clusters, accounts, and state remained isolated. This let us create variations of the same infrastructure without rewriting it for every environment.

For compute, we chose ECS (Elastic Container Service) with Fargate rather than managing EC2 (Elastic Compute Cloud) instances ourselves. ECS managed the containerized services, Fargate maintained the underlying servers and ECS Service Auto Scaling gave us a path to increase or decrease the number of tasks as demand changed.

The backend migration roughly looked like this:

BeforeAfterPurpose
Render deploymentsGitHub Actions + ECRBuild and deploy containers
Render runtimeECS on FargateRun backend services
Platform-managed routingRoute 53 + ACM + ALBDomains, HTTPS, and routing
Render environment variablesSecrets Manager + ECS configurationSecrets and configuration
Supabase AuthClerkAuthentication
Supabase PostgresRDSDatabase
Supabase StorageS3Object storage
UpstashElastiCacheRedis
ARQSQS + EventBridgeBackground jobs and scheduling
GrafanaGrafanaObservability

Clerk was not an AWS replacement, but it was simple, trusted, and had a generous free tier. Grafana did not need replacing at all. We were already sending it our logs, metrics, and traces before the migration, so moving to CloudWatch would have created work without solving a real problem.

The new setup also introduced pieces I had never needed to manage directly. The networked application resources lived inside a VPC (Virtual Private Cloud), security groups controlled which services could communicate, and the database stayed away from direct public access. ECR (Elastic Container Registry) held the Docker images used by ECS, while an ACM (AWS Certificate Manager) wildcard certificate gave the services under aws.ourdomain.com a shared HTTPS setup.

We used that subdomain as a clean namespace for the whole backend. A service could live at product1-service2.aws.ourdomain.com, while another could use product2-service5.aws.ourdomain.com, instead of exposing a collection of unrelated AWS-generated URLs. The relationship between Route 53 and the Application Load Balancer confused me initially, but the mental model is simple. Route 53 is the DNS layer that points each hostname toward the ALB (Application Load Balancer). The ALB reads the hostname on the incoming request, matches a listener rule, and forwards the request to the target group connected to the correct ECS service. Route 53 gets traffic to the common entrance, and the ALB decides which service behind that entrance receives it.

S3 (Simple Storage Service) objects were accessed through presigned URLs, so clients could upload or download files directly from S3 without receiving AWS credentials or sending the files through our application server. Secrets Manager held application credentials. SQS (Simple Queue Service) and EventBridge replaced our ARQ-based background job setup, and a dead-letter queue kept jobs that repeatedly failed processing from silent failure.

One convenience I only understood after losing it was automatic deployment. Render and Vercel made the workflow almost invisible, connect a GitHub repository, select a branch, and every push updates the deployment. I had used that flow for years without much thought.

On AWS, I rebuilt it with GitHub Actions. GitHub connected to AWS through OpenID Connect. This allowed a workflow from an approved repository and branch to assume an IAM (Identity and Access Management) role with short-lived credentials instead of storing a permanent AWS key in GitHub. The workflow lived in a GitHub Actions YAML file. A push to a configured branch built and tagged the Docker image, pushed it to ECR, updated the ECS task definition, and deployed the new revision to the service. It gave us the same push-to-deploy experience, but this time every part of it was in-house.

The most tiring part of AWS was access across the accounts set up through Control Tower. Control Tower organized the multi-account setup, but the failures I kept hitting came from IAM Identity Center permissions and the service control policies applied as guardrails. An administrator profile sounded like it should be enough, yet an SCP (Service Control Policy) could still block an operation at the account level. Each failure sent me through roles, permission sets, and guardrails before I could return to the work I originally wanted to do.

Regions created a sillier version of the same problem. Majority of our infrastructure lived in us-east-1 (N. Virginia), but sometimes I would open the console or run a command while the active region was Singapore or Mumbai which is near my geographic location. For the first two or three minutes, resources appeared to be missing until I noticed that I was simply looking in the wrong place.

The biggest downgrade in everyday developer experience was the database. Supabase let me browse tables almost like spreadsheets, run SQL in the browser, and inspect the schema without any setup. With RDS, even confirming that a migration had applied meant opening AWS CloudShell, connecting with the database credentials, and writing SQL. The database still worked, but casually looking at the data became a task of its own which is still the most annoying part for me.

One week after the migration, production requests suddenly started failing. I checked Grafana first. The logs, metrics, and traces looked clean. ECS was not overloaded, scaling did not appear to be the problem, and staging still worked normally. The error was generic enough to give me almost no direction, so I started testing increasingly unrelated things. I changed my internet provider, changed DNS settings, and searched everywhere because nothing inside the application looked obviously broken.

After around two hours, I found the problem. We had enabled RDS to manage its master password through Secrets Manager. What I did not know was that RDS rotates that managed secret every seven days by default. Our application was not reading that source secret directly. I had composed the username, password, host, port, and database into one DATABASE_URL, then stored that complete URL as a separate secret. The master password rotated, but the copied URL did not therefore production was connecting with stale credentials. The fix was to stop treating the entire database URL as one permanent secret. Stable values such as the host, port, database name, and username became regular ECS task configuration, while the password came from the RDS-managed secret directly.

There was one more catch. ECS injects secrets when a task starts, so an already-running container does not receive a rotated password automatically. After a rotation, the service needs a fresh deployment so new tasks start with the latest value, unless the application retrieves and refreshes the secret at runtime. The complete lesson was not just to split the URL. It was to keep one source of truth for the password and make credential rotation part of the deployment flow.

Most of the migration went smoothly after that, but the database remained my least favorite part of AWS. Our RDS gave me nothing close to Supabase’s table and SQL experience, then our incomplete handling of an automatic security feature took production down and consumed half my night because the failure appeared far away from its cause. I understand why credential rotation exists but that did not make the night any less painful.

For a personal project, I would still choose Vercel and Supabase almost every time. They have less overhead, their interfaces are better for moving quickly, and I would rather spend my limited time building the product than maintaining its infrastructure. The migration also changed the shape of my work. I am a full stack developer, but someone still has to own Terraform, IAM, deployments, credential rotation, and the occasional infrastructure failure. I had accidentally become the DevOps guy too. Every hour spent maintaining those systems is an hour taken away from product development.

That is why I think moving directly to a cloud provider only makes sense when the team has enough bandwidth to own it. Provisioning the infrastructure is a one-time project. Maintaining it is a lifetime of a job. It is still too early to say whether the savings were worth it because we have only run the migrated infrastructure for around a month. The first bill at least gives us a rough comparison. Before AWS, four paid Supabase instances at roughly $30 each cost around $120, while two additional instances stayed on the free tier. Vercel cost $20, and Render varied between $75 and $82. The old stack therefore cost somewhere between $215 and $222 per month.

Our first month on AWS cost was $201, putting the visible savings around 15-20$. But that comparison only counts services. It ignores the time spent learning Terraform, rebuilding deployment automation, debugging permissions, handling credential rotation, and maintaining everything afterward.

All in all, I still do not have a dramatic verdict on whether moving to AWS was worth it. It saved us some money, gave us more control, and taught me far more about infrastructure than another year on Render probably would have. But it also made me appreciate what Render, Vercel, and Supabase were doing for me under the hood.

Brainmade Logo