By the end of this phase, you'll have a secure AWS foundation with networking, security, and container storage ready for your application deployment.
Step 1: AWS Account Setup
If you don't have an AWS account yet, let's create one. This will be your production account.
- Go to https://aws.amazon.com/
- Click "Create an AWS Account"
- Enter your email address and choose a password
- Provide contact information and payment details
- Enable MFA on root account immediately!
Never use your root account for daily operations. We'll create an admin user in the next step.
The AWS CLI lets you manage AWS services from your terminal. This is essential for deployment.
# Check if AWS CLI is installed
aws --version
# If not installed, download from:
# https://aws.amazon.com/cli/
# Configure AWS CLI with your credentials
aws configure
# Enter your Access Key ID
# Enter your Secret Access Key
# Enter default region: us-east-1
# Enter default output format: json
Your Access Key ID and Secret Access Key are like a username and password for AWS CLI. Keep them secure and never share them!
Create a dedicated admin user for daily operations instead of using the root account.
- Go to IAM Console → Users → Add users
- Username:
helium-admin - Access type: ✅ Programmatic access + ✅ AWS Management Console access
- Attach policy:
AdministratorAccess - Save credentials securely (download CSV)
- Enable MFA on this user
This is critical! Set up budget alerts to avoid unexpected charges.
AWS charges can add up quickly. Setting up budget alerts NOW will save you from bill shock later.
- Go to AWS Cost Management → Budgets
- Click "Create budget"
- Budget type: Cost budget
- Budget name:
helium-production-monthly - Budget amount: $500 (adjust based on your needs)
- Configure alerts at: 50%, 80%, 100% of budget
- Add your email for notifications
Step 2: Network Architecture (VPC)
A Virtual Private Cloud (VPC) is your own isolated network in AWS. Think of it as your private data center in the cloud where you control all networking.
We'll create a VPC with public and private subnets across two availability zones for high availability.
VPC Configuration:
- Name: helium-production-vpc
- CIDR block: 10.0.0.0/16
- DNS hostnames: ✅ Enabled
- DNS resolution: ✅ Enabled
# Create VPC using AWS CLI
aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=helium-production-vpc},{Key=Project,Value=helium},{Key=Environment,Value=production}]' \
--region us-east-1
# Enable DNS hostnames
aws ec2 modify-vpc-attribute \
--vpc-id vpc-xxxxxxxxx \
--enable-dns-hostnames
# Enable DNS support
aws ec2 modify-vpc-attribute \
--vpc-id vpc-xxxxxxxxx \
--enable-dns-support
Replace
vpc-xxxxxxxxx with the actual VPC ID you get from the create command. AWS will give you this ID in the response.
We need three types of subnets: public (for load balancers), private (for applications), and isolated (for databases).
Subnet Layout:
- 10.0.1.0/24 (us-east-1a)
- 10.0.2.0/24 (us-east-1b)
- 10.0.10.0/24 (us-east-1a)
- 10.0.11.0/24 (us-east-1b)
- 10.0.20.0/24 (us-east-1a)
- 10.0.21.0/24 (us-east-1b)
# Create Public Subnet 1
aws ec2 create-subnet \
--vpc-id vpc-xxxxxxxxx \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=helium-public-1a}]'
# Create Public Subnet 2
aws ec2 create-subnet \
--vpc-id vpc-xxxxxxxxx \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=helium-public-1b}]'
# Create Private Subnet 1
aws ec2 create-subnet \
--vpc-id vpc-xxxxxxxxx \
--cidr-block 10.0.10.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=helium-private-1a}]'
# Create Private Subnet 2
aws ec2 create-subnet \
--vpc-id vpc-xxxxxxxxx \
--cidr-block 10.0.11.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=helium-private-1b}]'
# Create Isolated Subnet 1
aws ec2 create-subnet \
--vpc-id vpc-xxxxxxxxx \
--cidr-block 10.0.20.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=helium-isolated-1a}]'
# Create Isolated Subnet 2
aws ec2 create-subnet \
--vpc-id vpc-xxxxxxxxx \
--cidr-block 10.0.21.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=helium-isolated-1b}]'
# Enable auto-assign public IP for public subnets
aws ec2 modify-subnet-attribute \
--subnet-id subnet-xxxxxxxxx \
--map-public-ip-on-launch
Internet Gateway allows public subnets to access the internet. NAT Gateways allow private subnets to access the internet (for updates, API calls) without being directly accessible from the internet.
# Create Internet Gateway
aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=helium-production-igw}]'
# Attach to VPC
aws ec2 attach-internet-gateway \
--internet-gateway-id igw-xxxxxxxxx \
--vpc-id vpc-xxxxxxxxx
# Allocate Elastic IPs for NAT Gateways
aws ec2 allocate-address --domain vpc
aws ec2 allocate-address --domain vpc
# Create NAT Gateway 1 (in public subnet 1)
aws ec2 create-nat-gateway \
--subnet-id subnet-public-1a \
--allocation-id eipalloc-xxxxxxxxx \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=helium-nat-1a}]'
# Create NAT Gateway 2 (in public subnet 2)
aws ec2 create-nat-gateway \
--subnet-id subnet-public-1b \
--allocation-id eipalloc-yyyyyyyyy \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=helium-nat-1b}]'
NAT Gateways cost ~$32/month each plus data transfer. For production, you need 2 (one per AZ) for high availability. Total: ~$65/month.
Route tables control where network traffic goes. We need different routes for public, private, and isolated subnets.
# Create Public Route Table
aws ec2 create-route-table \
--vpc-id vpc-xxxxxxxxx \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=helium-public-rt}]'
# Add route to Internet Gateway
aws ec2 create-route \
--route-table-id rtb-xxxxxxxxx \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id igw-xxxxxxxxx
# Associate with public subnets
aws ec2 associate-route-table \
--route-table-id rtb-xxxxxxxxx \
--subnet-id subnet-public-1a
aws ec2 associate-route-table \
--route-table-id rtb-xxxxxxxxx \
--subnet-id subnet-public-1b
# Create Private Route Table 1
aws ec2 create-route-table \
--vpc-id vpc-xxxxxxxxx \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=helium-private-rt-1a}]'
# Add route to NAT Gateway 1
aws ec2 create-route \
--route-table-id rtb-private-1a \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id nat-xxxxxxxxx
# Associate with private subnet 1a
aws ec2 associate-route-table \
--route-table-id rtb-private-1a \
--subnet-id subnet-private-1a
# Repeat for private subnet 1b with NAT Gateway 2
Step 3: Security Groups
Security groups are virtual firewalls that control inbound and outbound traffic to your AWS resources. They're essential for security!
This security group controls traffic to your Application Load Balancer.
# Create ALB Security Group
aws ec2 create-security-group \
--group-name helium-alb-sg \
--description "Security group for Application Load Balancer" \
--vpc-id vpc-xxxxxxxxx
# Allow HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-alb-xxxxxxxxx \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
# Allow HTTP (for redirect to HTTPS)
aws ec2 authorize-security-group-ingress \
--group-id sg-alb-xxxxxxxxx \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
# Create ECS Task Security Group
aws ec2 create-security-group \
--group-name helium-ecs-task-sg \
--description "Security group for ECS tasks" \
--vpc-id vpc-xxxxxxxxx
# Allow traffic from ALB only
aws ec2 authorize-security-group-ingress \
--group-id sg-ecs-xxxxxxxxx \
--protocol tcp \
--port 8000 \
--source-group sg-alb-xxxxxxxxx
# Allow all outbound traffic (for API calls, updates)
aws ec2 authorize-security-group-egress \
--group-id sg-ecs-xxxxxxxxx \
--protocol -1 \
--cidr 0.0.0.0/0
# Create ElastiCache Security Group
aws ec2 create-security-group \
--group-name helium-elasticache-sg \
--description "Security group for ElastiCache Redis" \
--vpc-id vpc-xxxxxxxxx
# Allow Redis traffic from ECS tasks only
aws ec2 authorize-security-group-ingress \
--group-id sg-redis-xxxxxxxxx \
--protocol tcp \
--port 6379 \
--source-group sg-ecs-xxxxxxxxx
Step 4: Container Registry (ECR)
Elastic Container Registry (ECR) is where you store your Docker images. Think of it as a private Docker Hub for your AWS account.
# Create backend repository
aws ecr create-repository \
--repository-name helium-backend \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=AES256 \
--region us-east-1
# Create frontend repository (if needed)
aws ecr create-repository \
--repository-name helium-frontend \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=AES256 \
--region us-east-1
Image scanning is now enabled by default to detect vulnerabilities. Encryption ensures your images are secure at rest.
Lifecycle policies automatically clean up old images to save storage costs.
{
"rules": [
{
"rulePriority": 1,
"description": "Keep last 10 images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
}
]
}
# Save the JSON above as lifecycle-policy.json, then:
aws ecr put-lifecycle-policy \
--repository-name helium-backend \
--lifecycle-policy-text file://lifecycle-policy.json \
--region us-east-1
Let's test that you can push images to ECR successfully.
# Get your AWS account ID
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Login to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
$ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
# Build a test image (from your backend directory)
cd backend
docker build -t helium-backend:test .
# Tag the image
docker tag helium-backend:test \
$ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/helium-backend:test
# Push to ECR
docker push $ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/helium-backend:test
# Verify the image
aws ecr describe-images \
--repository-name helium-backend \
--region us-east-1
If you see your image listed, ECR is working correctly. You're ready to move forward!
Phase 1 Verification Checklist
Before proceeding to Phase 2, make sure all these items are complete:
- AWS account created and configured
- IAM admin user created with MFA enabled
- Cost budgets and alerts configured
- CloudTrail enabled and logging
- VPC created with all subnets
- Internet Gateway and NAT Gateways configured
- Route tables configured correctly
- Security groups created with appropriate rules
- ECR repositories created
- ECR lifecycle policies configured
- Test image pushed to ECR successfully
- All resources tagged appropriately
Common Issues & Solutions
Solution:
# Re-authenticate Docker with ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
$ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
Check:
- Verify route table has route to NAT Gateway
- Check NAT Gateway is in public subnet
- Verify Elastic IP is attached
- Check security groups allow traffic
Solution:
# Verify AWS CLI is configured
aws configure list
# Check your credentials
aws sts get-caller-identity
# If issues persist, reconfigure
aws configure