DevOps 7 min read

AWS EC2 Deployment Guide: Complete Tutorial for Beginners

Deploy applications on AWS EC2 from scratch. Learn instance setup, security groups, load balancing, auto-scaling, and production best practices.

MR

Moshiour Rahman

Advertisement

What is AWS EC2?

Amazon Elastic Compute Cloud (EC2) provides resizable compute capacity in the cloud. It allows you to launch virtual servers, configure security and networking, and manage storage.

EC2 Instance Types

FamilyUse CaseExample
t3/t3aGeneral purpose, burstableWeb servers, dev environments
m5/m6iBalanced compute/memoryApplication servers
c5/c6iCompute optimizedBatch processing, gaming
r5/r6iMemory optimizedDatabases, caching
g4/p4GPU instancesML training, graphics

Launching an EC2 Instance

Step 1: Choose AMI

# Using AWS CLI
aws ec2 describe-images \
    --owners amazon \
    --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" \
    --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
    --output text

Step 2: Create Key Pair

# Create key pair
aws ec2 create-key-pair \
    --key-name my-key-pair \
    --query 'KeyMaterial' \
    --output text > my-key-pair.pem

# Set permissions
chmod 400 my-key-pair.pem

Step 3: Create Security Group

# Create security group
aws ec2 create-security-group \
    --group-name my-security-group \
    --description "My security group"

# Allow SSH
aws ec2 authorize-security-group-ingress \
    --group-name my-security-group \
    --protocol tcp \
    --port 22 \
    --cidr 0.0.0.0/0

# Allow HTTP
aws ec2 authorize-security-group-ingress \
    --group-name my-security-group \
    --protocol tcp \
    --port 80 \
    --cidr 0.0.0.0/0

# Allow HTTPS
aws ec2 authorize-security-group-ingress \
    --group-name my-security-group \
    --protocol tcp \
    --port 443 \
    --cidr 0.0.0.0/0

Step 4: Launch Instance

aws ec2 run-instances \
    --image-id ami-0123456789abcdef0 \
    --instance-type t3.micro \
    --key-name my-key-pair \
    --security-groups my-security-group \
    --count 1 \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=MyWebServer}]'

Connecting to EC2

SSH Connection

# Connect to instance
ssh -i "my-key-pair.pem" ec2-user@ec2-12-34-56-78.compute-1.amazonaws.com

# Or using public IP
ssh -i "my-key-pair.pem" ec2-user@12.34.56.78

SSH Config (Optional)

# ~/.ssh/config
Host my-ec2
    HostName 12.34.56.78
    User ec2-user
    IdentityFile ~/.ssh/my-key-pair.pem

# Then connect with:
ssh my-ec2

Server Setup

Initial Configuration

# Update system
sudo yum update -y

# Install common tools
sudo yum install -y git htop vim wget curl

# Set timezone
sudo timedatectl set-timezone America/New_York

# Add swap space (if needed)
sudo dd if=/dev/zero of=/swapfile bs=128M count=16
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab

Install Docker

# Install Docker
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ec2-user

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Verify installation (logout and login again)
docker --version
docker-compose --version

Install Node.js

# Using NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install --lts
node --version

# Or using Amazon Linux Extras
sudo amazon-linux-extras install nodejs18

Install Java

# Amazon Corretto (Amazon's OpenJDK)
sudo yum install -y java-17-amazon-corretto-headless

# Verify
java -version

Deploying Applications

Node.js Application

# Clone your repository
git clone https://github.com/yourusername/your-app.git
cd your-app

# Install dependencies
npm install

# Run with PM2 (process manager)
sudo npm install -g pm2
pm2 start app.js --name "my-app"
pm2 startup
pm2 save

# View logs
pm2 logs my-app

Docker Application

# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  app:
    image: your-app:latest
    ports:
      - "80:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=your-database-url
    restart: always
EOF

# Start containers
docker-compose up -d

# View logs
docker-compose logs -f

Java/Spring Boot Application

# Upload JAR file
scp -i my-key-pair.pem target/app.jar ec2-user@12.34.56.78:/home/ec2-user/

# On EC2, create systemd service
sudo cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Spring Boot App
After=network.target

[Service]
User=ec2-user
ExecStart=/usr/bin/java -jar /home/ec2-user/app.jar
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

# Start service
sudo systemctl daemon-reload
sudo systemctl start myapp
sudo systemctl enable myapp

# Check status
sudo systemctl status myapp

Nginx Reverse Proxy

Install and Configure

# Install Nginx
sudo amazon-linux-extras install nginx1

# Configure as reverse proxy
sudo cat > /etc/nginx/conf.d/app.conf << 'EOF'
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}
EOF

# Test and start
sudo nginx -t
sudo systemctl start nginx
sudo systemctl enable nginx

SSL with Let’s Encrypt

# Install Certbot
sudo yum install -y certbot python3-certbot-nginx

# Get certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Auto-renewal (already configured)
sudo certbot renew --dry-run

Load Balancing

Application Load Balancer (ALB)

# Create target group
aws elbv2 create-target-group \
    --name my-targets \
    --protocol HTTP \
    --port 80 \
    --vpc-id vpc-0123456789abcdef0 \
    --health-check-path /health \
    --target-type instance

# Create ALB
aws elbv2 create-load-balancer \
    --name my-load-balancer \
    --subnets subnet-0123456789abcdef0 subnet-0987654321fedcba0 \
    --security-groups sg-0123456789abcdef0

# Create listener
aws elbv2 create-listener \
    --load-balancer-arn arn:aws:elasticloadbalancing:region:account-id:loadbalancer/app/my-load-balancer/... \
    --protocol HTTP \
    --port 80 \
    --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-targets/...

# Register targets
aws elbv2 register-targets \
    --target-group-arn arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-targets/... \
    --targets Id=i-0123456789abcdef0

Auto Scaling

Launch Template

# Create launch template
aws ec2 create-launch-template \
    --launch-template-name my-template \
    --launch-template-data '{
        "ImageId": "ami-0123456789abcdef0",
        "InstanceType": "t3.micro",
        "KeyName": "my-key-pair",
        "SecurityGroupIds": ["sg-0123456789abcdef0"],
        "UserData": "base64-encoded-script"
    }'

Auto Scaling Group

# Create auto scaling group
aws autoscaling create-auto-scaling-group \
    --auto-scaling-group-name my-asg \
    --launch-template LaunchTemplateName=my-template,Version='$Latest' \
    --min-size 1 \
    --max-size 4 \
    --desired-capacity 2 \
    --vpc-zone-identifier "subnet-0123456789abcdef0,subnet-0987654321fedcba0" \
    --target-group-arns arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-targets/...

# Create scaling policy
aws autoscaling put-scaling-policy \
    --auto-scaling-group-name my-asg \
    --policy-name cpu-target-tracking \
    --policy-type TargetTrackingScaling \
    --target-tracking-configuration '{
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "ASGAverageCPUUtilization"
        },
        "TargetValue": 70.0
    }'

User Data Scripts

Bootstrap Script

#!/bin/bash
# User data script for EC2 instance

# Update system
yum update -y

# Install Docker
yum install -y docker
systemctl start docker
systemctl enable docker

# Install Docker Compose
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# Clone and deploy application
cd /home/ec2-user
git clone https://github.com/yourusername/your-app.git
cd your-app
docker-compose up -d

# Install CloudWatch agent
yum install -y amazon-cloudwatch-agent

Monitoring with CloudWatch

CloudWatch Agent Configuration

{
    "agent": {
        "metrics_collection_interval": 60,
        "run_as_user": "root"
    },
    "metrics": {
        "metrics_collected": {
            "mem": {
                "measurement": ["mem_used_percent"]
            },
            "disk": {
                "measurement": ["disk_used_percent"],
                "resources": ["/"]
            }
        }
    },
    "logs": {
        "logs_collected": {
            "files": {
                "collect_list": [
                    {
                        "file_path": "/var/log/messages",
                        "log_group_name": "my-app-logs",
                        "log_stream_name": "{instance_id}"
                    },
                    {
                        "file_path": "/home/ec2-user/app/logs/*.log",
                        "log_group_name": "my-app-application",
                        "log_stream_name": "{instance_id}"
                    }
                ]
            }
        }
    }
}

Create CloudWatch Alarms

# CPU utilization alarm
aws cloudwatch put-metric-alarm \
    --alarm-name high-cpu-utilization \
    --alarm-description "Alarm when CPU exceeds 80%" \
    --metric-name CPUUtilization \
    --namespace AWS/EC2 \
    --statistic Average \
    --period 300 \
    --threshold 80 \
    --comparison-operator GreaterThanThreshold \
    --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
    --evaluation-periods 2 \
    --alarm-actions arn:aws:sns:region:account-id:my-topic

Security Best Practices

Security Group Rules

# Restrict SSH to specific IP
aws ec2 authorize-security-group-ingress \
    --group-id sg-0123456789abcdef0 \
    --protocol tcp \
    --port 22 \
    --cidr YOUR_IP/32

# Remove public SSH access
aws ec2 revoke-security-group-ingress \
    --group-id sg-0123456789abcdef0 \
    --protocol tcp \
    --port 22 \
    --cidr 0.0.0.0/0

IAM Role for EC2

# Create IAM role for EC2 with S3 access
aws iam create-role \
    --role-name EC2-S3-Access \
    --assume-role-policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {"Service": "ec2.amazonaws.com"},
            "Action": "sts:AssumeRole"
        }]
    }'

# Attach S3 policy
aws iam attach-role-policy \
    --role-name EC2-S3-Access \
    --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

Cost Optimization

StrategySavings
Reserved InstancesUp to 72%
Spot InstancesUp to 90%
Right-sizing20-40%
Auto ScalingVariable

Summary

TaskService/Tool
ComputeEC2
Load BalancingALB/NLB
Auto ScalingASG
StorageEBS
MonitoringCloudWatch
SecuritySecurity Groups, IAM

AWS EC2 provides flexible, scalable compute capacity for deploying any application to the cloud.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.