You Can Run a Production App for 12 Months Without Spending a Single Rupee
Not a demo. Not a toy. A real, internet-facing application with a server, static file hosting, and serverless API endpoints — all running on AWS infrastructure in Mumbai. Zero cost for a full year.
I know that sounds like one of those YouTube thumbnails where someone’s pointing at a laptop with their mouth open. But it’s actually true, and I’ve done it twice now — once with a portfolio site and once with a small SaaS MVP I was testing with about 200 users in Pune and Bangalore.
Here’s the deal. AWS Free Tier isn’t some stripped-down playground. You get 750 hours of EC2 compute per month (enough to run a server 24/7), 5 GB of S3 storage, and — this is the wild part — 1 million Lambda invocations every single month, permanently. Not “for 12 months.” Forever. And when you deploy everything in the ap-south-1 Mumbai region, your Indian users get response times under 50ms instead of the 200-300ms they’d see from US-based servers.
We’re going to set all of this up together, step by step. Every command is tested. Every gotcha I ran into, I’ll flag before you hit it. By the end, you’ll have three different deployment options running in production, and your AWS bill will read exactly 0.00 INR.
Let’s get into it.
Creating Your AWS Account (And the RuPay Problem)
First things first. Head to aws.amazon.com and create an account.
Now, something that trips up a lot of Indian developers right away: RuPay cards don’t work. You’ll need a Visa or Mastercard — either credit or debit. AWS places a temporary hold of $1 (roughly 84 INR as of early 2026) to verify the card. It gets refunded, but your card needs to support international transactions. If you’re using an SBI debit card, make sure international usage is enabled through the YONO app first. I learned this one the hard way after three failed attempts.
Once you’re in, we need the AWS CLI installed on your machine. Open up a terminal and run these commands:
# Install AWS CLI v2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Configure with your credentials
aws configure
# AWS Access Key ID: YOUR_ACCESS_KEY
# AWS Secret Access Key: YOUR_SECRET_KEY
# Default region name: ap-south-1
# Default output format: json
# Verify configuration
aws sts get-caller-identity
See that ap-south-1 region? That’s Mumbai. Always, always set this as your default. Every resource we create today goes here. Your users in India will thank you with faster page loads, and you won’t accidentally spin up instances in Ohio or Virginia where the latency to Delhi is 250ms+.
Don’t Skip This: Create a Separate IAM User
Your root account has god-mode access to everything. You probably shouldn’t be running CLI commands with it. Most people skip this step because it feels bureaucratic, but one wrong aws ec2 terminate-instances with root credentials and you could nuke something you didn’t mean to.
Create a dedicated user for deployments:
# Create an IAM user for CLI access
aws iam create-user --user-name deploy-user
# Attach necessary policies
aws iam attach-user-policy \
--user-name deploy-user \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess
aws iam attach-user-policy \
--user-name deploy-user \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
aws iam attach-user-policy \
--user-name deploy-user \
--policy-arn arn:aws:iam::aws:policy/AWSLambda_FullAccess
# Create access keys
aws iam create-access-key --user-name deploy-user
Save those access keys somewhere safe. A password manager, ideally. Not a sticky note on your monitor, not a text file called keys.txt on your desktop. I’ve seen both in production environments and both ended badly.
Run aws configure again with these new keys. From here on, every command runs as this limited user.
EC2: Your Own Server in Mumbai (Free for 12 Months)
Right, so EC2 is where most people start, and for good reason. You get a virtual machine — a proper Linux server — that you can SSH into, install whatever you want on, and run any application you’d run on a VPS from DigitalOcean or Linode.
Free Tier gives you 750 hours per month of a t2.micro instance. Quick math: there are about 744 hours in a 31-day month. So one t2.micro running around the clock = completely free. Specs-wise, you’re looking at 1 vCPU and 1 GB of RAM. Enough for a Node.js app, a Flask API, a small Java service, or really any side project that doesn’t need to handle thousands of concurrent requests.
t2.micro, NOT t3.micro. They look similar but have different Free Tier rules. A t3.micro will start billing you after the credits run out. I’ve seen this mistake cost people 1,500-2,000 INR in a single month.
Let’s launch one. We’ll create a key pair for SSH, set up a security group to allow web traffic, and spin up the instance:
# Create a key pair for SSH access
aws ec2 create-key-pair \
--key-name my-app-key \
--query 'KeyMaterial' \
--output text \
--region ap-south-1 > my-app-key.pem
chmod 400 my-app-key.pem
# Create a security group
aws ec2 create-security-group \
--group-name my-app-sg \
--description "Security group for my application" \
--region ap-south-1
# Allow SSH (port 22) and HTTP (port 80)
SG_ID=$(aws ec2 describe-security-groups \
--group-names my-app-sg \
--query 'SecurityGroups[0].GroupId' \
--output text \
--region ap-south-1)
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp --port 22 --cidr 0.0.0.0/0 \
--region ap-south-1
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp --port 80 --cidr 0.0.0.0/0 \
--region ap-south-1
# Launch the instance (Amazon Linux 2023 AMI)
aws ec2 run-instances \
--image-id ami-0e35ddab05955cf57 \
--instance-type t2.micro \
--key-name my-app-key \
--security-group-ids $SG_ID \
--region ap-south-1 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=my-app-server}]'
# Get the public IP
INSTANCE_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=my-app-server" \
--query 'Reservations[0].Instances[0].InstanceId' \
--output text --region ap-south-1)
aws ec2 describe-instances \
--instance-ids $INSTANCE_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text --region ap-south-1
Give it a minute. Seriously, grab some chai. The instance needs about 30-60 seconds to boot up and get a public IP assigned.
Once it’s ready, SSH in and deploy a simple Node.js app:
# SSH into the instance
ssh -i my-app-key.pem ec2-user@YOUR_PUBLIC_IP
# Install Node.js
sudo dnf install -y nodejs
# Create a simple Express app
mkdir ~/myapp && cd ~/myapp
npm init -y
npm install express
cat > index.js <<'EOF'
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.json({
message: 'Hello from AWS Mumbai!',
region: 'ap-south-1',
timestamp: new Date().toISOString(),
});
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', uptime: process.uptime() });
});
app.listen(80, () => console.log('Server running on port 80'));
EOF
# Run with sudo (port 80 requires root)
sudo node index.js
Open a browser, punch in that public IP, and you should see a JSON response saying hello from Mumbai. That’s your app. Running on your own AWS server. In India. For free.
Now, running sudo node index.js directly is fine for testing, but obviously it’ll die the moment you close your SSH session. For anything resembling production, you’d want to use PM2 or set up a systemd service. But that’s a topic for a different post — we’re focused on getting deployed today.
S3: Host a Static Website Without a Server
Maybe you don’t need a full server. Maybe you’ve built a React app, a portfolio site, a documentation page, or a landing page for your startup idea. Static files. HTML, CSS, JavaScript. No backend logic running on the server.
S3 handles this beautifully, and the Free Tier allocation is generous: 5 GB of storage, 20,000 GET requests, and 2,000 PUT requests per month. For a personal site or small project, you won’t come close to those limits.
# Create a bucket (names must be globally unique)
aws s3 mb s3://my-app-static-2026 --region ap-south-1
# Enable static website hosting
aws s3 website s3://my-app-static-2026 \
--index-document index.html \
--error-document error.html
# Create and upload a simple website
cat > index.html <<'EOF'
My App
Deployed on S3 Mumbai
This static site is hosted on AWS S3 in ap-south-1.
EOF
# Upload with public-read ACL
aws s3 cp index.html s3://my-app-static-2026/ \
--content-type "text/html" \
--region ap-south-1
# Set bucket policy for public access
cat > bucket-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-app-static-2026/*"
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket my-app-static-2026 \
--policy file://bucket-policy.json \
--region ap-south-1
# Your site is now live at:
# http://my-app-static-2026.s3-website.ap-south-1.amazonaws.com
That URL is ugly, I know. my-app-static-2026.s3-website.ap-south-1.amazonaws.com isn't exactly what you'd print on a business card. You can point a custom domain at it using Route 53 or just a CNAME record from your existing DNS provider. A .in domain from BigRock or GoDaddy costs around 500-800 INR per year, and that would probably be your only expense.
build/ or out/ folder: aws s3 sync ./build s3://my-app-static-2026/ --region ap-south-1
Lambda: Serverless APIs That Scale to Zero
Okay, this is where things get really interesting. And honestly, if I were starting a side project today in April 2026, I'd probably skip EC2 entirely and go straight to Lambda for most use cases.
Why? Because Lambda's Free Tier doesn't expire. It's not a 12-month thing. Every month, forever, you get 1 million requests and 400,000 GB-seconds of compute. For context, that's enough to handle roughly 30,000 API calls per day without paying a paisa. And when nobody's using your app at 3 AM? Lambda scales to zero. No idle server burning through your free hours.
Let's deploy a Python API endpoint:
# Create the Lambda function code
cat > lambda_function.py <<'EOF'
import json
from datetime import datetime
def handler(event, context):
path = event.get('rawPath', '/')
method = event.get('requestContext', {}).get('http', {}).get('method', 'GET')
if path == '/api/greet':
name = event.get('queryStringParameters', {}).get('name', 'World')
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({
'message': f'Namaste, {name}!',
'region': 'ap-south-1',
'timestamp': datetime.utcnow().isoformat(),
}),
}
return {
'statusCode': 404,
'body': json.dumps({'error': 'Not found'}),
}
EOF
# Package the function
zip function.zip lambda_function.py
# Create an IAM role for Lambda
cat > trust-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name lambda-basic-role \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy \
--role-name lambda-basic-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Wait for role propagation, then create the function
ROLE_ARN=$(aws iam get-role --role-name lambda-basic-role \
--query 'Role.Arn' --output text)
aws lambda create-function \
--function-name my-api \
--runtime python3.12 \
--handler lambda_function.handler \
--zip-file fileb://function.zip \
--role $ROLE_ARN \
--region ap-south-1 \
--memory-size 128 \
--timeout 10
# Create a Function URL (free alternative to API Gateway)
aws lambda create-function-url-config \
--function-name my-api \
--auth-type NONE \
--region ap-south-1
# Allow public access
aws lambda add-permission \
--function-name my-api \
--statement-id FunctionURLAllowPublicAccess \
--action lambda:InvokeFunctionUrl \
--principal "*" \
--function-url-auth-type NONE \
--region ap-south-1
# Get the function URL
aws lambda get-function-url-config \
--function-name my-api \
--region ap-south-1 \
--query 'FunctionUrl' --output text
A couple of things to notice. We're using Function URLs instead of API Gateway. Why? API Gateway has its own pricing, and while it does have a free tier (1 million REST API calls per month for 12 months), Function URLs are completely free with no limits. One less thing to worry about.
Also, we set the memory to 128 MB — the minimum. Lambda bills by memory-time, so lower memory = more free invocations within your 400,000 GB-seconds allowance. For a simple API like this, 128 MB is plenty.
What This Actually Costs vs. Paid Alternatives
Let's put some numbers on the table. Because "free" is nice to hear, but you probably want to know what happens when the 12-month EC2 window closes, and how this stacks up against other hosting options Indian developers commonly use.
| Service | AWS Free Tier (12 mo) | AWS After Free Tier | DigitalOcean | Railway/Render |
|---|---|---|---|---|
| Server (1 vCPU, 1 GB) | 0 INR/mo | ~750 INR/mo | ~500 INR/mo | ~420-850 INR/mo |
| Static hosting (5 GB) | 0 INR/mo | ~10-15 INR/mo | ~500 INR/mo (Spaces) | Free (limited) |
| Serverless API (1M req/mo) | 0 INR/mo (forever) | 0 INR/mo (forever) | N/A (no equivalent) | Free tier varies |
| Mumbai region latency | <50ms | <50ms | ~60-80ms (Bangalore) | 150-300ms (US/EU) |
Couple of things stand out. First, Lambda stays free forever. If your app can be built as a collection of API endpoints (and many can), you might never pay for compute at all. Second, AWS in Mumbai gives you the lowest latency to Indian users — DigitalOcean's Bangalore datacenter comes close, but Railway and Render don't have Indian regions yet as of April 2026.
After the 12-month EC2 free tier expires, you've got options. You could migrate your server workload to Lambda. You could downgrade to a t4g.nano spot instance (often under 200 INR/month). Or you could move to Lightsail, which starts at around 300 INR/month for a Mumbai instance. But honestly, by the time your free year is up, you'll have enough AWS experience to make that call yourself.
Billing Alerts: Set These Up Before You Deploy Anything Else
I probably should've put this section first. Maybe I should have. Because the number one fear every Indian developer has about AWS — and I see this question in every Reddit thread, every Discord server, every college WhatsApp group — is "what if I get a surprise bill for $500?"
It's a valid fear. Stories about accidental AWS charges are legendary. Some developer in Hyderabad forgot to terminate a GPU instance and woke up to a $3,000 bill. A startup in Gurgaon ran an unoptimized ECS cluster for two weeks and burned through $1,200. These things happen. But they're completely preventable.
Set up a billing alarm right now:
# Enable billing alerts in us-east-1 (billing metrics are only in us-east-1)
aws cloudwatch put-metric-alarm \
--alarm-name "billing-alarm-500inr" \
--alarm-description "Alert when estimated charges exceed $6 (approx 500 INR)" \
--metric-name EstimatedCharges \
--namespace AWS/Billing \
--statistic Maximum \
--period 21600 \
--threshold 6 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:YOUR_ACCOUNT_ID:billing-alerts \
--dimensions Name=Currency,Value=USD \
--region us-east-1
Notice that billing metrics live in us-east-1, not ap-south-1. That's an AWS quirk — billing data is always in US East regardless of where your resources run. Don't let it confuse you.
Beyond CLI alarms, go into the AWS Console and do these two things manually:
- Navigate to Billing → Billing preferences → Free Tier usage alerts. Turn it on. AWS will email you when you hit 85% of any Free Tier limit. Takes five seconds.
- Set up AWS Budgets (free for the first two budgets). Create a "Zero Spend Budget" that alerts you the moment any charge appears. Even a $0.01 charge will trigger the alert, giving you time to investigate before it snowballs.
Common Gotchas That'll Cost You Money
After running Free Tier projects for the better part of two years and helping a few friends do the same, here are the mistakes I see most often. Some of them seem obvious in hindsight. They weren't obvious at 2 AM when we were debugging.
Elastic IPs you're not using. If you allocate an Elastic IP and don't attach it to a running instance, AWS charges you about $3.65/month (roughly 300 INR). It's free only when it's associated with a running instance. Stopped your EC2 instance for the weekend? Release the Elastic IP too, or you're paying for air.
EBS volumes hanging around after instance termination. When you terminate an EC2 instance, the attached EBS volume might not get deleted automatically depending on your settings. An orphaned 30 GB volume costs around $3/month. Check your volumes periodically: aws ec2 describe-volumes --region ap-south-1
Running two t2.micro instances. You get 750 hours total across all t2.micro instances combined. Two instances = 1,500 hours/month needed, but you only have 750 free. The second one starts billing on day one.
Data transfer charges. Free Tier includes 100 GB of data transfer out to the internet per month (updated from the old 15 GB limit in late 2024). That's usually enough, but if you're serving large files or video, watch it. Data transfer between AWS services within the same region is free, though.
CloudWatch logs piling up. Lambda functions write logs to CloudWatch automatically. Free Tier gives you 5 GB of log ingestion and 5 GB of log storage. If you're logging aggressively or have a function that errors in a loop, those logs add up fast. Set a retention policy: aws logs put-retention-policy --log-group-name /aws/lambda/my-api --retention-in-days 7 --region ap-south-1
A Quick Architecture for a Real Project
Let's pull everything together. Say you're building a small web app — a URL shortener, a feedback form, a restaurant menu system, whatever. Here's how I'd architect it within Free Tier limits:
- Frontend: React/Vue/plain HTML hosted on S3 in
ap-south-1. Zero server costs. - API: Lambda functions behind Function URLs. Handles all the business logic. Zero compute costs (within 1M requests/month).
- Database: DynamoDB with 25 GB of storage and 25 write/25 read capacity units — also permanently free. Or if you prefer SQL, use the RDS Free Tier (750 hours of
db.t3.microMySQL or PostgreSQL for 12 months). - Domain: A
.indomain from any Indian registrar. 500-800 INR/year — your only actual expense.
That setup can realistically serve a few hundred daily active users. It won't handle Flipkart-level traffic, obviously. But for validating an idea, building a portfolio piece, or running a tool for your college department? More than enough. Way more than enough, actually.
I ran a feedback collection tool on almost exactly this stack for about eight months. 150-ish daily users. Monthly AWS cost: 0.00 INR. The only thing I paid for was the domain name.
Why This Matters for Your Career
Look, I'm not going to pretend that deploying a side project to AWS Free Tier makes you a cloud architect. It doesn't. But here's what it does do, and I think this matters more than most people realize, especially if you're a developer in India looking at the 2026 job market.
AWS is the most widely used cloud platform in Indian companies. Not Azure, not GCP — AWS. When Infosys, TCS, Wipro, and the hundreds of startups in Bangalore's tech corridor post job listings requiring "cloud experience," they overwhelmingly mean AWS. Having actual deployment experience — not just watching tutorials, not just getting a certificate, but having a live project running on AWS that you configured yourself — puts you ahead of probably 80% of applicants at the fresher and 1-3 year experience level.
And Free Tier removes the financial barrier entirely. You don't need your company to provision an AWS account. You don't need to convince your college to buy credits. You don't need to spend money you might not have. You just... sign up and start building.
That's kind of a big deal.
Your Free Tier Deployment Checklist
We covered a lot of ground. Here's everything you need to set up, in order:
- Create an AWS account with a Visa/Mastercard (not RuPay)
- Install and configure AWS CLI with
ap-south-1as default region - Create an IAM user for deployments — stop using root
- Set up billing alerts (CloudWatch alarm + Free Tier usage alerts + zero-spend budget)
- Launch a
t2.microEC2 instance for server workloads (NOTt3.micro) - Create an S3 bucket with static website hosting for frontends
- Deploy at least one Lambda function with a Function URL
- Check for orphaned resources weekly: Elastic IPs, EBS volumes, forgotten instances
- Set CloudWatch Logs retention to 7 days on all Lambda log groups
- Bookmark the Billing Console and check it every few days for the first month
Start with the billing alerts. Seriously. Everything else can wait. But once those are in place, go spin up an EC2 instance, deploy something, and share the public IP with a friend. There's something quietly thrilling about saying "yeah, this is running on my own AWS server in Mumbai" — even if it's just a JSON endpoint that says Namaste.
You've got twelve months. Go build something.