How I Built an AWS App Without Spending a Penny — Introduction

Abhishek Chaudhuri
9 min readSep 22, 2023

--

AWS logo with dollar sign crossed out

AWS is the world’s largest cloud provider. It is prevalent across the entire tech industry. If you’re starting your career, there’s a good chance you’ll need to know AWS for your job. But with over 200 services, it can feel intimidating. I’m a firm believer that the best way to learn something is to have hands-on experience, rather than just following a tutorial online. The more you fail, the more likely that knowledge will stick. (I guess it’s ironic given I’m writing this article, but this is not meant to be a tutorial on how to use AWS. Rather, my goal is to provide tips on how to best leverage AWS for a side project. You are free to decide what kind of project you want to work on.)

Personal projects on GitHub are a great way to showcase your coding ability with other developers. But given these are passion projects, you likely won’t be making any income off them. Therefore, cost plays a big factor in what kind of projects you develop. AWS offers a pay-for-what-you-use cost model. Instead of paying upfront, you only pay for the services you use and how long you use them. This can be a double-edged sword. On one hand, if you briefly play around with AWS and then shut everything down, you only pay for that short moment. On the other hand, if you forget to turn off that EC2 instance, you will wake up to a nice bill to Bezos. AWS offers free tiers, but many of them are only valid for 12 months (or less). I created my AWS account a few years ago when I heard about the DeepRacer challenge, where you use reinforcement learning to drive an autonomous car around a course as fast as possible. Afterward, I hardly touched my account until more recently when I started learning AWS. (In fact, when I first started using my account after a long time, it got locked! I guess AWS thought someone hacked into my account and flagged it as unusual activity?) But even then, if we want to keep our project for the long term, we’d rather use services that are always free under a certain threshold.

I did a lot of training, but I still wasn’t getting that much hands-on experience. So, that’s when I decided to do a side project. My goal was to utilize as much AWS as possible, without spending a penny. That’s quite the task, but with enough planning, I was able to build a decently sized project using around 15 services and a $0 bill. As a bonus, these skills will help me be more conscious about costs when deploying real apps on AWS. Although I’m on a tight budget, I made sure to follow best practices when developing an app on AWS, such as writing everything as code, securing everything, monitoring usage, and setting up an automated CI/CD pipeline.

Creating a Budget

Before we start, the very first thing to do is to set up a budget to alert you if you spend over a penny. (It’s free after all. It would be silly if this cost anything.) I set this up in the console, but here is an equivalent CloudFormation template to create the budget (we’ll go over these in more depth later):

Parameters:
Email:
Type: String
# Simple regex from https://stackoverflow.com/a/201378
AllowedPattern: "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"
Resources:
Budget:
Type: AWS::Budgets::Budget
Properties:
Budget:
TimeUnit: MONTHLY
BudgetType: COST
BudgetLimit:
Amount: 0.01
Unit: USD
CostTypes:
IncludeCredit: false
IncludeRefund: false
NotificationsWithSubscribers:
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 100
ThresholdType: PERCENTAGE
Subscribers:
- Address: !Ref Email
SubscriptionType: EMAIL

Here we are checking to see if the total monthly cost is greater than 1₵. If so, we send an email to the provided address alerting the user that they went over their budget. Under cost types, we are considering all kinds of costs, including subscriptions, tax, and upfront costs. The only costs we’re not considering are credits and refunds, which are deducted from our overall bill (trying to be conservative here). And we’re measuring unblended costs, instead of blended or amortized costs, since we’re not dealing with savings plans, reservations, or multiple accounts.

For extra precaution, go to Billing Preferences and make sure to enable “PDF invoices delivery by email”, “AWS Free Tier alerts”, and “CloudWatch billing alerts” to receive emails based on your AWS usage.

I also want to bring up a useful feature provided by AWS to check if you’re reaching the free tier limits. Under Billing and Cost Management, go to Free Tier to browse a table containing all the services you’re currently using. It shows how much of the free tier you’re currently using and are projected to use by the end of the month, with a nice little visualization. The screenshot below shows how much I’m using AWS once I got this entire project set up. We can see that our greatest limiting factor is CodeBuild since we’re only given 100 build minutes per month. But even then, on a normal month, we’re not even using a third of that amount.

AWS Billing Free Tier page
AWS Billing Free Tier page

Note that this only shows services that have a free tier. For example, S3 is another service we’ll need to watch out for given its storage and API call limits. But, since its free tier only covers the first year you create your account, it doesn’t show in this table. If you want to view the full monthly usage of all AWS services you’re using, go to the Bills section under Billing and Payments and scroll down to the Charges by service table. You can change the billing period in the top right to view your usage in previous months or years.

Planning it Out

AWS has a Free Tier website that shows how much of each service is covered under the free tier, and for how long. I listed all the major services and calculated how much I could use each service for a penny. Then I categorized the services based on their free tier type (always free, free for 12 months, free for less time, or not at all). Almost immediately, I was able to tell what types of services I would need to use to save the most money. Anything that is charged for how long it is running was out of the question. This includes (prepare for all the acronyms) EC2, ECS, EKS, EBS, RDS, ELB, VPC, ElastiCache, Redshift, and SageMaker. The key to saving on costs is serverless.

Some of the other services I excluded include:

  • Detective, GuardDuty, Inspector, Macie, Security Hub, Shield Advanced, and WAF: Although security is nice, all these services either have no free tier or a limited free trial. We can do similar security checks from Inspector using free open-source tools provided by GitHub. And AWS provides basic DDoS protection using Shield Standard. ($3K/month is a little out of my budget for Advanced.)
  • Secrets Manager: It costs 40₵ per month for every secret and only has a free tier of 1 month. We can store up to 10K secrets for free using Parameter Store. However, there are some limitations, such as a lack of auto rotation. Plus, secret strings aren’t supported in many services like CloudFormation.
  • CodeCommit: We have GitHub.
  • CodeDeploy: We don’t need CodeDeploy for the front end since the artifacts are already deployed to S3 using CodeBuild. And the Lambda functions for the microservices will be tied to a separate pipeline created in GitHub.
  • CodePipeline: For V1, we’re only limited to one active pipeline per month under the free tier. For V2, we’re limited to 100 minutes under the free tier. This is similar to CodeBuild’s free tier, so I opted to just use CodeBuild instead of CodePipeline.
  • Cloud9: We have VSCode.
  • CodeGuru: Again, we can achieve similar functionality with open-source tools on GitHub. Plus, it currently only supports Java and Python.
  • CodeStar: This isn’t needed if we’re not utilizing all the Code* services (now I see why it’s called CodeStar).
  • Config: You can only have a couple of configuration items before it costs 1₵.
  • Fargate: This is a different kind of serverless from Lambda. Although AWS will manage the underlying infrastructure for you, you’re still charged for how long EC2/ECS is running. With Lambda, on the other hand, you’re only charged for how long the code runs and don’t need to manage any infrastructure.
  • Kinesis: We don’t need to do any real-time data streaming for this project.
  • Elastic Beanstalk, App Runner, Lightsail: These are all services designed to help you deploy apps quickly. But these can spin up resources without you knowing. For example, Elastic Beanstalk will deploy the code on EC2 with a load balancer. Both services charge you for how long your app is running, and the bill could potentially be $5/month. There’s also no option to scale to 0 when your app is inactive to save on costs like in Heroku or Google App Engine.
  • Organizations, Control Tower: We only need one account for this project. Be careful with Control Tower since detective controls are built using Config, which can quickly incur costs.

I should also bring up the elephant in the room: Amplify, a service to help build full-stack web and mobile apps. It’s a neat tool for developers who want to focus on building an app, without worrying about which services to use or how much they cost. But under the hood, it utilizes several AWS services, including Cognito (authentication), AppSync/API Gateway (APIs), S3 (file storage), and Lambda (functions). So, you’re subject to those pricing models when using Amplify. Also, Amplify abstracts much of the AWS API. That’s again good for simple apps, but for larger apps with more complex functionality, you would want to use an escape hatch to call the underlying SDKs. (For mobile in particular, the Swift and Kotlin SDKs are still in Developer Preview as of this writing.) That adds to the complexity of building an app. Plus, since my ultimate goal was to practice for the certification exams, it was better to stick with the underlying services. They’re much more likely to appear on the exams compared to Amplify, which is relatively newer.

I took inspiration from the Architecting Serverless Applications course on AWS Skill Builder, which included an architecture diagram of a sample web application:

AWS architecture diagram for a sample full-stack web app
From Architecting Serverless Applications

For the front end, we can store up to 435 MB of code in S3 (under the standard tier). CloudFront will host the site using HTTPS for free (if traffic doesn’t blow up), with some caching across edge locations as a nice bonus.

For the back end, Lambda is one of the cheapest AWS services out there. We can use it to interact with DynamoDB and expose the function using API Gateway.

Everything built will be defined in CloudFormation templates. This has several benefits:

  1. CloudFormation by itself is free. You only pay for the API calls it makes to provision all the resources.
  2. Since this is code, we can push it to GitHub and apply version control, so we know what changed and when it was changed. We can also include it as part of a pipeline to do automated testing and deployment (which I’ll go into detail later).
  3. We can roll back changes if something goes wrong. (CloudFormation does this automatically, but we can also do this through Git to make sure the templates are the source of truth.) And if one day we wanted to delete all our resources, we could simply delete the stack.
  4. The other benefit is that it forces us to consider all the different configuration options we can apply to every resource, so we can see which makes sense for our app and is within our budget.

Since this is a big task, I decided to split this up into multiple parts. Check out part 2 where I start building the front end.

--

--