Deploy to AWS Free Tier: Complete Guide for Indian Developers

AWS Free Tier gives you enough compute, storage, and serverless capacity to run a production-quality side project for 12 months without spending a rupee. This guide is tailored for Indian developers — we use the ap-south-1 (Mumbai) region for lowest latency to Indian users, and every command is tested and ready to run.

Setting Up Your AWS Account

Create your AWS account at aws.amazon.com. You will need a credit or debit card (Visa/Mastercard — RuPay is not supported). AWS will place a temporary hold of $1 (approximately 84 INR) for verification. Install and configure the AWS CLI:

# 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

Security first: Never use your root account for daily operations. Create an IAM user with programmatic access and attach the appropriate policies.

ADVERTISEMENT
# 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

EC2: Your Virtual Server

The Free Tier gives you 750 hours per month of a t2.micro instance (1 vCPU, 1 GB RAM). That is enough to run a small Node.js, Python, or Java application 24/7. Let us launch one:

# 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

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

S3: Static File Hosting

S3 Free Tier gives you 5 GB storage, 20,000 GET requests, and 2,000 PUT requests per month. Perfect for hosting a static website or storing assets:

# 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'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My App</title>
  <style>
    body { font-family: system-ui; max-width: 600px; margin: 80px auto; padding: 0 20px; }
    h1 { color: #1a1a2e; }
  </style>
</head>
<body>
  <h1>Deployed on S3 Mumbai</h1>
  <p>This static site is hosted on AWS S3 in ap-south-1.</p>
</body>
</html>
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

Lambda: Serverless Functions

Lambda Free Tier gives you 1 million requests and 400,000 GB-seconds of compute per month — permanently, not just for 12 months. Here is how to deploy an 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

Cost Monitoring: Avoid Surprise Bills

The biggest fear for Indian developers using AWS is an unexpected bill. Set up billing alerts immediately:

# 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

Also enable AWS Free Tier usage alerts in Billing → Billing preferences → Free Tier usage alerts. AWS will email you when you approach 85% of any Free Tier limit.

Conclusion

With EC2 for server workloads, S3 for static hosting, and Lambda for serverless APIs — all in the ap-south-1 Mumbai region — you have a complete deployment stack that costs nothing within Free Tier limits. The key is to always set billing alerts, use t2.micro (not t3.micro which has different Free Tier rules), and shut down resources you are not using. AWS is the most widely used cloud platform in India’s job market, and hands-on experience with real deployments will set you apart.

ADVERTISEMENT

Leave a Comment

Your email address will not be published. Required fields are marked with an asterisk.