Skip to main contentUkasyah Nata Dewastoro
Cloud

Stage 1: First servers on a startup budget

Two founders have an app that only runs on their laptops, and every investor demo means carrying a MacBook to the meeting. It needs to go live on AWS with no platform team and no money to spare. Two EC2 hosts, a public web tier and a private data tier, built by script so it can be rebuilt from nothing.

~$32/mo saved
Role
One of three; deployment scripts and AWS network
Scale
2 EC2 hosts, 1 region
Stack
AWS EC2, VPC, Route 53
Targets
Live on AWS, private data tier, under ~$50 a month

Context

This series follows one product as it grows, from a garage to a company that has to stay up at scale, and ends on a real production estate. Each stage raised the bar, and each page covers the infrastructure built at that point: from two servers on a tight budget to GitOps on Kubernetes and a live Terraform migration.

At this stage the product only runs on the founders' laptops. It needs to be online, secure enough to handle user data, documented well enough for another engineer to maintain, and cheap. There is no platform team, no CI and no infrastructure code yet. My job was everything around the application, not the application itself.

Constraints:

  • A team of three, with no prior AWS footprint
  • Budget measured in tens of dollars a month
  • One region, no managed databases

Architecture and failover

The web host sits in a public subnet behind a Route 53 record, with Nginx terminating TLS from Let's Encrypt and proxying to the app. The data host sits in a private subnet with Postgres, Redis, RabbitMQ and a background worker. It has no public IP.

Instead of a NAT gateway, the web host runs an apt package cache that the data host uses for updates, and an S3 gateway endpoint keeps object storage traffic inside AWS. Secrets come from Secrets Manager at startup instead of a file on disk. A private Route 53 zone gives the hosts stable internal names, and the CloudWatch agent ships metrics to a dashboard with alarms.

Either host failing takes the product down. We accepted that trade-off at this stage: a startup buys simplicity first and redundancy later.

How the environment is built and deployed
  1. 1
    Build the app

    Compile the release on the workstation.

  2. 2
    Create the network

    VPC, public and private subnets, route tables, S3 gateway endpoint.

  3. 3
    Security groups and IAM

    Only the web host accepts internet traffic; only the web host reaches the data host.

  4. 4
    Launch both hosts

    Store secrets in Secrets Manager, then create public and private DNS records.

  5. 5
    DeployDeploy the data tier

    Postgres, Redis, RabbitMQ and the worker on the private host.

  6. 6
    DeployDeploy the web tier

    Nginx, TLS and the app on the public host.

  7. 7
    GateVerify

    Health check through Nginx and a test request end to end.

    If it fails: Undeploy the tier that failed and run its deploy script again.

Key decisions

A private data tier, even with two servers

  • VPC
  • security

The first version ran everything on one public host: database, cache, queue and web server, with SSH open to the internet.

  • Everything on one public hostCheapest, but a single compromised process reaches the database.
  • Managed services for every data storeLeast operational work, but well past the budget for a first launch.
  • Public web host plus private data hostChosenOnly Nginx and the app face the internet; Postgres, Redis and RabbitMQ sit in a private subnet.

Trade-off: Two hosts cost more than one, and the data host needs a way to reach package mirrors without a public IP.

Lifecycle

  1. Create the VPC, subnets, route tables and the S3 endpoint.
  2. Create the security groups: only the web host accepts traffic from the internet, and only the web host can reach the data host.
  3. Launch both hosts, then create the public and private DNS records.
  4. Store secrets in Secrets Manager.

Failure modes

FailureBlast radiusDetectionRecovery
Web host lostWhole product offlineCloudWatch alarmRelaunch from the scripts and redeploy
Data host lostWhole product offline; data at riskCloudWatch alarmRelaunch and restore from backup
Package mirror unreachableData host cannot updateFailed updateRestart the cache on the web host

Results

~$32/mosaved by a package cache instead of a NAT gateway
~$39–48estimated monthly cost, before free tier
0data services exposed to the internet

What I'd change

I wrote the security audit after the first version shipped. I would write it first and treat every item as a launch blocker. SSH open to the whole internet and secrets in .env files should never have reached a running host.

I never tested a backup restore at this stage. I would run one before calling anything production, because an untested backup is a guess.

Both hosts are single points of failure. That was the right call for the budget, and I would write it down as a known risk with a trigger for when to fix it, such as the first paying customer.

Next stage: the product gets real users, three environments, and a delivery pipeline.