AWS for Beginners: Getting Started
You sign up for AWS, open the console, and get a menu of 200+ services with names like Kinesis, Fargate and Outposts. You just wanted to put a small app on a server. Half an hour later you've clicked into three services you didn't need, and you're quietly worried you've started something that will bill you.
That's the usual first experience, and it's fixable. You don't need most of those services to be productive. You need a handful, understood well. This post walks through that handful: setting up an account safely, launching an EC2 instance, storing files in S3, deploying a Lambda function, and keeping the bill under control. Everything is done with real CLI commands you can run yourself.
What You're Actually Renting
AWS launched in 2006 with three services: S3 (object storage), SQS (message queuing), and EC2 (virtual machines). Amazon had already built a huge internal infrastructure for its own store, and started renting it out to developers. The catalog has grown to more than 200 services since then, covering compute, storage, databases, networking, machine learning, security and IoT.
Strip away the branding and you're renting time on data centers spread around the world, billed by the hour, the second, or the request. Instead of buying a physical server (weeks to arrive, thousands of dollars upfront, idle at 3 AM), you start a virtual machine in about a minute and pay for the time it runs.
Global Infrastructure: Regions and Availability Zones
The hierarchy goes Regions, then Availability Zones (AZs), then data centers. A Region is a geographic location, like us-east-1 in Northern Virginia or ap-south-1 in Mumbai. Each Region has at least two AZs, and usually three or more. An AZ is one or more data centers with their own power, cooling and networking, placed far enough from the other AZs that a fire or flood in one shouldn't take out the rest.
One practical consequence: most resources are regional. If you launch an instance in ap-south-1 and then open the console with us-east-1 selected, it looks like your instance vanished. It hasn't. Check the region dropdown in the top right before you panic.
AWS Global Infrastructure: one Region containing three Availability Zones, each with multiple isolated data centers.
The Shared Responsibility Model
AWS secures the infrastructure: physical hardware, buildings, the hypervisor, and the software behind managed services. You secure everything you run on it. That means OS patches, application code, IAM permissions, encryption, network rules and security groups. If you misconfigure an S3 bucket and private data leaks, that's on you, not Amazon. Keep this split in mind and most AWS security decisions get easier to reason about.
Setting Up Your AWS Account
Account setup takes about 15 minutes. The free tier covers enough to learn on and build small real projects, as long as you watch the limits (more on that below).
Create Your AWS Account
Go to aws.amazon.com and click "Create an AWS Account." You will need a valid email address, a phone number for verification, and a credit card (you will not be charged unless you exceed free tier limits). Pick the "Basic Support" plan. It's free and fine for learning.
Enable MFA on the Root Account
Your root account has unrestricted access to everything, including closing your account and changing billing details. Immediately after creating it, go to IAM → Security credentials → Assign MFA and set up a virtual MFA device (Google Authenticator or Authy work well). Then stop using the root account for day-to-day tasks.
Create an IAM User for Daily Use
Navigate to IAM → Users → Create User. Give it a name (e.g., mayur-admin), attach the AdministratorAccess managed policy for now (you can tighten permissions later), and enable console access with a password. Download the credentials CSV. Enable MFA on this user too.
Set Up Billing Alerts
Go to Billing → Budgets → Create Budget. Create a "Zero spend budget" that emails you the moment any charge appears. Also create a monthly cost budget of $10 to $20. That way you hear about a forgotten resource before it costs real money.
The classic AWS Free Tier has two layers: 12-month free (e.g., 750 hrs/month of t2.micro EC2, 5 GB S3 storage) and always free (e.g., 1 million Lambda requests/month). Go over a limit, even slightly, and the extra is billed at normal rates. AWS reworked the free tier for accounts created from mid-2025 onward into a credit-based plan, so check the free tier page for what your account actually gets. The usual surprises: an EC2 instance left running past 750 hours, instances in several regions all counting against the same 750 hours, NAT Gateways (never free), and data transfer OUT. Run new setups through the AWS Pricing Calculator first, and turn on Cost Explorer from day one.
Core Services Every Developer Should Know
Most real projects get by with fewer than fifteen services. These are the ones you'll reach for first, almost no matter what you're building.
| Service | Type | Primary Use Case | Free Tier |
|---|---|---|---|
| EC2 | Compute | Virtual machines: web servers, build agents, anything that needs a full OS | 750 hrs/mo t2.micro (12 mo) |
| S3 | Storage | Object storage: static files, backups, data lakes, hosting SPAs | 5 GB storage, 20K GET, 2K PUT (12 mo) |
| RDS | Database | Managed relational databases: MySQL, PostgreSQL, MariaDB, SQL Server | 750 hrs/mo db.t3.micro (12 mo) |
| Lambda | Serverless Compute | Event-driven functions: API backends, webhooks, scheduled jobs | 1M requests + 400K GB-sec/mo (always free) |
| VPC | Networking | Isolated virtual network: subnets, routing, security groups, NACLs | Free (VPC itself); NAT Gateway is not |
| CloudFront | CDN | Content delivery network that caches S3 / origin responses at edge locations worldwide | 1 TB data transfer out + 10M HTTP requests/mo (always free) |
| IAM | Security | Identity and access management: users, roles, policies for all AWS resources | Always free |
| CloudWatch | Monitoring | Metrics, logs, alarms, dashboards for AWS resources and custom applications | 10 custom metrics, 5 GB logs/mo (always free) |
People tend to skip VPC because it feels like background plumbing. Don't. Every resource you create lives inside one. If you understand subnets (public vs. private), route tables, internet gateways and security groups, you know whether your database is reachable from the internet. If you don't, you're guessing.
Your First EC2 Instance
Most people's first real AWS resource is an EC2 (Elastic Compute Cloud) instance: a virtual machine in one of Amazon's data centers. You pick the OS, the hardware size (instance type), the storage and the network. Then you SSH in and treat it like any other Linux box.
Install and Configure the AWS CLI
I'd install the CLI before doing much in the console. Nearly everything you can click, you can script, and a script can be rerun, reviewed and committed to git. Clicks can't.
# macOS (using Homebrew)
brew install awscli
# Linux (x86_64)
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Verify installation
aws --version
# aws-cli/2.x.x Python/3.x.x Linux/...
Next, configure the CLI with your IAM user's access keys. Go to IAM → Users → your user → Security credentials → Create access key, choose "CLI" as the use case, and download the key pair.
aws configure
# You will be prompted for:
# AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region name [None]: us-east-1
# Default output format [None]: json
# Verify it works
aws sts get-caller-identity
# {
# "UserId": "AIDAIOSFODNN7EXAMPLE",
# "Account": "123456789012",
# "Arn": "arn:aws:iam::123456789012:user/mayur-admin"
# }
Launch and Connect to an EC2 Instance
You need a key pair for SSH and a security group, which works as a firewall. The script below creates both and then launches a free-tier eligible Amazon Linux 2023 instance.
# Create an SSH key pair and save the private key
aws ec2 create-key-pair \
--key-name my-first-key \
--query 'KeyMaterial' \
--output text > my-first-key.pem
chmod 400 my-first-key.pem
# Create a security group that allows SSH from your IP
aws ec2 create-security-group \
--group-name my-sg \
--description "My first security group"
# Allow SSH (port 22) from your current IP only
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress \
--group-name my-sg \
--protocol tcp \
--port 22 \
--cidr "${MY_IP}/32"
# Get the latest Amazon Linux 2023 AMI ID for us-east-1
AMI_ID=$(aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=al2023-ami-*-x86_64" \
"Name=state,Values=available" \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' \
--output text)
# Launch a t2.micro instance (free tier eligible)
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type t2.micro \
--key-name my-first-key \
--security-groups my-sg \
--query 'Instances[0].InstanceId' \
--output text)
echo "Launched instance: $INSTANCE_ID"
# Wait for it to be running, then get the public IP
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"
PUBLIC_IP=$(aws ec2 describe-instances \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
# SSH into your instance
ssh -i my-first-key.pem ec2-user@"$PUBLIC_IP"
Two things commonly go wrong at that last step. If you skip the chmod 400, SSH refuses the key with WARNING: UNPROTECTED PRIVATE KEY FILE! and then Permission denied (publickey). If the SSH command just hangs until it times out, the security group is usually the culprit: your public IP changed (new Wi-Fi, VPN, ISP reassignment) and port 22 is still open only to the old one. Rerun the authorize-security-group-ingress command with your current IP.
A stopped EC2 instance does not consume compute hours (so it does not eat your free tier allocation or incur hourly charges), but its attached EBS volume still costs money. For short-lived experiments, terminate the instance entirely: aws ec2 terminate-instances --instance-ids $INSTANCE_ID. Terminated instances and their root volumes are deleted automatically.
S3 for Storage
Sooner or later every app needs somewhere to put files: user uploads, build output, backups. On AWS that's S3 (Simple Storage Service), one of the original three services. You store objects (files) in buckets (top-level containers). There are no real directories. The / in an object key is just part of the name, and the console draws it as a folder. You never provision capacity; you just upload.
Typical uses: static websites and SPAs, user-uploaded files, the origin for a CloudFront distribution, archived application logs, Terraform state files, and a staging area for data pipelines.
# Bucket names must be globally unique across all AWS accounts
BUCKET_NAME="my-project-assets-$(date +%s)"
# Create a bucket (us-east-1 doesn't need LocationConstraint)
aws s3api create-bucket --bucket "$BUCKET_NAME" --region us-east-1
# For other regions, add the LocationConstraint:
# aws s3api create-bucket \
# --bucket "$BUCKET_NAME" \
# --region ap-south-1 \
# --create-bucket-configuration LocationConstraint=ap-south-1
# Upload a single file
aws s3 cp ./index.html s3://"$BUCKET_NAME"/index.html
# Upload an entire directory recursively
aws s3 sync ./dist s3://"$BUCKET_NAME"/ --delete
# List objects in a bucket
aws s3 ls s3://"$BUCKET_NAME"/ --recursive --human-readable
# Download a file from S3
aws s3 cp s3://"$BUCKET_NAME"/index.html ./downloaded-index.html
# Generate a pre-signed URL (expires in 1 hour) for private objects
aws s3 presign s3://"$BUCKET_NAME"/index.html --expires-in 3600
# Enable static website hosting
aws s3 website s3://"$BUCKET_NAME"/ \
--index-document index.html \
--error-document 404.html
# Delete all objects and remove the bucket
aws s3 rm s3://"$BUCKET_NAME"/ --recursive
aws s3api delete-bucket --bucket "$BUCKET_NAME"
S3 charges for storage (GB-months), requests (GET, PUT, LIST), and data transfer OUT to the internet. Data transfer IN is free. The mistake I see most is turning on versioning and never cleaning up old versions: every version of every object is billed as storage. S3 Lifecycle Policies can move rarely accessed data to S3-IA or S3 Glacier after a set number of days, which costs a fraction of S3 Standard for data you seldom read.
Lambda: Serverless Functions
Running a whole EC2 instance for a webhook that fires twenty times a day is wasteful. Lambda fits that case better. You upload a function and say what triggers it. AWS handles provisioning, scaling from zero to thousands of requests per second, runtime patching, and bills you only for the compute time used, in 1-millisecond increments.
"Serverless" doesn't mean there are no servers. It means you don't manage them. It suits event-driven work: an API endpoint with spiky traffic, a webhook processor for GitHub pushes, a nightly report job, or a function that runs whenever a file lands in S3.
A Simple Lambda Function in Node.js
// handler.js
// Lambda receives an "event" object (shape depends on trigger)
// and a "context" object with runtime info.
export const handler = async (event, context) => {
console.log('Event received:', JSON.stringify(event, null, 2));
// Example: parsing a body from an API Gateway event
const body = event.body ? JSON.parse(event.body) : {};
const name = body.name || 'World';
// Simulate async work (e.g., DB query, external API call)
const greeting = await buildGreeting(name);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ message: greeting }),
};
};
async function buildGreeting(name) {
// In a real function, this might query DynamoDB or call an API
return `Hello, ${name}! Deployed via AWS Lambda.`;
}
# Zip the function code
zip function.zip handler.js
# Create an IAM execution role for Lambda (one-time setup)
aws iam create-role \
--role-name lambda-basic-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name lambda-basic-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Get the role ARN (replace 123456789012 with your Account ID)
ROLE_ARN=$(aws iam get-role \
--role-name lambda-basic-role \
--query 'Role.Arn' --output text)
# Create the Lambda function
aws lambda create-function \
--function-name my-greeting-api \
--runtime nodejs20.x \
--handler handler.handler \
--role "$ROLE_ARN" \
--zip-file fileb://function.zip
# Invoke it directly from the CLI to test
aws lambda invoke \
--function-name my-greeting-api \
--payload '{"body":"{\"name\":\"Mayur\"}"}' \
--cli-binary-format raw-in-base64-out \
output.json && cat output.json
The handler above uses export, which is ES module syntax. If you zip a plain handler.js with no package.json, Node treats it as CommonJS and the first invoke fails with Runtime.UserCodeSyntaxError: SyntaxError: Unexpected token 'export'. Fix it by renaming the file to handler.mjs (the --handler handler.handler setting stays the same) or by including a package.json with "type": "module" in the zip.
Lambda Trigger Flow
A typical Lambda trigger flow: API Gateway receives an HTTP request, invokes the Lambda function, which reads or writes data to DynamoDB.
Keeping Costs (and Risk) Under Control
The classic beginner story on AWS is a side project that turns into a surprise bill. Security and maintainability matter just as much. These are the habits that do the most work.
Right-Sizing Your Instances
Over-provisioned instances are where most wasted EC2 money goes. Start small (t3.micro or t3.small), watch real CPU and network usage in CloudWatch for a week, and only then size up. One catch: CloudWatch doesn't report memory or disk usage for EC2 out of the box, because AWS can't see inside your OS. Install the CloudWatch agent if you want those numbers. Cost Explorer also has Right Sizing Recommendations that look at your usage and suggest downgrades.
Reserved Instances and Savings Plans
If a workload will run around the clock for a year or more, Reserved Instances typically save somewhere around 40 to 60% over On-Demand. You commit to an instance type and region for 1 or 3 years. Compute Savings Plans are more flexible: you commit to a dollar amount of compute per hour, and the discount applies across EC2, Lambda and Fargate automatically.
Spot Instances for Tolerant Workloads
Spot Instances sell spare AWS capacity at discounts of up to 90% off On-Demand. The catch is that AWS can take them back with a 2-minute warning. That's fine for batch jobs, CI/CD build runners, data science notebooks and other work that can be interrupted. It's not fine for your main production web server.
S3 Lifecycle Policies
Data that starts in S3 Standard often doesn't need to stay there. Lifecycle rules move objects to cheaper storage classes as they age. A typical policy might move objects to S3 Standard-IA after 30 days, to S3 Glacier Instant Retrieval after 90 days, and delete them after 365 days.
CloudWatch Alarms and Anomaly Detection
Besides the billing alerts from setup, put CloudWatch alarms on the metrics that matter: EC2 CPU above 80%, Lambda error rate above 1%, RDS connections close to the limit. AWS Cost Anomaly Detection can flag unusual spending and email you before a runaway process turns into a runaway bill.
Mistakes I See Most Often (and When AWS Isn't the Right Call)
Mistakes worth avoiding early
- Long-lived access keys in code. Keys committed to a public GitHub repo get found and abused fast. On EC2 and Lambda, use IAM roles so there's no key to leak. If you must use keys locally, rotate them.
- Skipping MFA on the root user or your admin IAM user. It takes two minutes to set up.
- Databases in public subnets. RDS and internal APIs belong in private subnets, reachable only from your app's security group.
- No tags. Without
Project,EnvironmentandOwnertags, you'll eventually stare at a bill and not know which of 40 resources is safe to delete. - Console-only infrastructure. Clicking around is fine for learning. For anything you'll need to rebuild or audit, use CloudFormation or Terraform, and turn on CloudTrail so API calls are logged.
AWS isn't always the right answer, though. For a personal blog, a small Laravel app, or a portfolio site, a flat-priced VPS or a platform like Vercel or Netlify is often cheaper and much simpler, with no IAM, VPC or data transfer pricing to learn. Lambda gets awkward for long-running jobs (there's a 15-minute limit) and for apps that need persistent connections. And if nobody on the team has time to learn the billing model, the surprise-bill risk is real.
Where I'd pick AWS: when you need managed databases with backups and failover, when traffic is unpredictable and you want things to scale without rebuilding, or when the project is likely to grow into queues, CDNs and background workers that AWS already has. In those cases, start with the handful of services covered here, keep billing alerts on, and add new services only when a real requirement shows up.