Overview

The EC2 deployment provides a simple, cost-effective way to run Oxy with the following components:

  • EC2 Instance: ARM64-based t4g.small instance running Ubuntu 22.04
  • EBS Storage: 10GB persistent volume for application data
  • Application Load Balancer: With AWS Cognito authentication
  • Security Groups: Configured for web traffic and SSH access

Prerequisites

Before starting, ensure you have:

  • AWS CLI configured with appropriate permissions
  • SSH key pair created in AWS (or create one during setup)
  • Domain name configured in Route53 (optional)
  • Basic familiarity with AWS Console and CLI

Step 1: Network Infrastructure Setup

1.1 Create VPC and Subnets

# Create VPC
VPC_ID=$(aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=oxy-playground-vpc}]' \
  --query 'Vpc.VpcId' --output text)

# Enable DNS hostnames
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames

# Create Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=oxy-playground-igw}]' \
  --query 'InternetGateway.InternetGatewayId' --output text)

# Attach Internet Gateway to VPC
aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID

# Create public subnet
SUBNET_ID=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.1.0/24 \
  --availability-zone us-west-2a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=oxy-playground-public-subnet}]' \
  --query 'Subnet.SubnetId' --output text)

# Enable auto-assign public IP
aws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID --map-public-ip-on-launch

# Create route table
ROUTE_TABLE_ID=$(aws ec2 create-route-table \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=oxy-playground-public-rt}]' \
  --query 'RouteTable.RouteTableId' --output text)

# Add route to Internet Gateway
aws ec2 create-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID

# Associate route table with subnet
aws ec2 associate-route-table --subnet-id $SUBNET_ID --route-table-id $ROUTE_TABLE_ID

1.2 Create Security Groups

# Create security group for EC2 instance
EC2_SG_ID=$(aws ec2 create-security-group \
  --group-name oxy-ec2-sg \
  --description "Security group for Oxy EC2 instance" \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=oxy-ec2-sg}]' \
  --query 'GroupId' --output text)

# Allow SSH access (port 22)
aws ec2 authorize-security-group-ingress \
  --group-id $EC2_SG_ID \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# Allow HTTP access (port 3000 for Oxy)
aws ec2 authorize-security-group-ingress \
  --group-id $EC2_SG_ID \
  --protocol tcp \
  --port 3000 \
  --cidr 0.0.0.0/0

# Allow MCP SSE access (port 8000)
aws ec2 authorize-security-group-ingress \
  --group-id $EC2_SG_ID \
  --protocol tcp \
  --port 8000 \
  --cidr 0.0.0.0/0

# Create security group for ALB
ALB_SG_ID=$(aws ec2 create-security-group \
  --group-name oxy-alb-sg \
  --description "Security group for Oxy Application Load Balancer" \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=oxy-alb-sg}]' \
  --query 'GroupId' --output text)

# Allow HTTP and HTTPS for ALB
aws ec2 authorize-security-group-ingress \
  --group-id $ALB_SG_ID \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0

aws ec2 authorize-security-group-ingress \
  --group-id $ALB_SG_ID \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

1.3 Create SSH Key Pair (if needed)

# Create key pair
aws ec2 create-key-pair \
  --key-name oxy-keypair \
  --query 'KeyMaterial' \
  --output text > ~/.ssh/oxy-keypair.pem

# Set proper permissions
chmod 400 ~/.ssh/oxy-keypair.pem

Step 2: Create IAM Roles and Policies

2.1 Create IAM Role for EC2

# Create trust policy for EC2
cat > ec2-trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# Create IAM role
aws iam create-role \
  --role-name OxyEC2Role \
  --assume-role-policy-document file://ec2-trust-policy.json

# Attach AWS managed policy for SSM
aws iam attach-role-policy \
  --role-name OxyEC2Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

# Create custom policy for Parameter Store access
cat > ssm-access-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParameters",
        "ssm:GetParametersByPath"
      ],
      "Resource": "arn:aws:ssm:us-west-2:*:parameter/oxy-playground/*"
    }
  ]
}
EOF

# Create and attach custom policy
aws iam create-policy \
  --policy-name OxySSMAccessPolicy \
  --policy-document file://ssm-access-policy.json

# Get account ID for policy ARN
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Attach custom policy
aws iam attach-role-policy \
  --role-name OxyEC2Role \
  --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/OxySSMAccessPolicy

# Create instance profile
aws iam create-instance-profile --instance-profile-name OxyEC2InstanceProfile

# Add role to instance profile
aws iam add-role-to-instance-profile \
  --instance-profile-name OxyEC2InstanceProfile \
  --role-name OxyEC2Role

2.2 Create Setup Script

Create the setup script that will be executed on instance launch:

cat > oxy-setup.sh << 'EOF'
#!/bin/bash
set -e

# Update system
sudo apt-get update -y
sudo apt-get install -y curl wget unzip awscli

# Mount EBS volume
DEVICE="/dev/nvme1n1"
MOUNT_POINT="/mnt/data"

sudo mkdir -p $MOUNT_POINT

# Check if device exists and format if needed
if [ -b "$DEVICE" ]; then
    # Check if filesystem exists
    if ! sudo blkid "$DEVICE"; then
        echo "Formatting $DEVICE..."
        sudo mkfs.ext4 "$DEVICE"
    fi
    
    # Mount the device
    sudo mount "$DEVICE" "$MOUNT_POINT"
    
    # Add to fstab for persistence
    echo "$DEVICE $MOUNT_POINT ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab
    
    # Set permissions
    sudo chown ubuntu:ubuntu "$MOUNT_POINT"
fi

# Install Oxy
curl --proto '=https' --tlsv1.2 -LsSf https://internal.oxy.tech | sudo bash

# Create directories
mkdir -p /mnt/data/oxy_data
mkdir -p /mnt/data/customer-demo

# Get environment variables from Parameter Store
aws ssm get-parameter \
  --name "/oxy-playground/oxy/env" \
  --region us-west-2 \
  --query 'Parameter.Value' \
  --output text > /mnt/data/oxy.env

# Get BigQuery credentials if they exist
if aws ssm get-parameter --name "/oxy-playground/oxy/bigquery_sample_key" --region us-west-2 --with-decryption --query 'Parameter.Value' --output text > /tmp/bigquery-key.json 2>/dev/null; then
    mv /tmp/bigquery-key.json /mnt/data/customer-demo/bigquery-sample.key
fi

# Create systemd service
sudo tee /etc/systemd/system/oxy.service << 'SERVICE_EOF'
[Unit]
Description=Oxy Application
After=network.target

[Service]
Type=forking
User=ubuntu
WorkingDirectory=/mnt/data
EnvironmentFile=/mnt/data/oxy.env
ExecStart=/bin/bash -c '/usr/local/bin/oxy serve --auth-mode cognito --port 3000 & /usr/local/bin/oxy mcp-sse --port 8000 & wait'
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
SERVICE_EOF

# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable oxy
sudo systemctl start oxy

# Wait for service to be ready
sleep 10
sudo systemctl status oxy
EOF

# Make script executable
chmod +x oxy-setup.sh

Step 3: Launch EC2 Instance

3.1 Get Latest Ubuntu ARM64 AMI

# Get latest Ubuntu 22.04 ARM64 AMI ID
AMI_ID=$(aws ec2 describe-images \
  --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-arm64-server-*" \
  --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
  --output text)

echo "Using AMI: $AMI_ID"

3.2 Launch EC2 Instance

# Encode user data script
USER_DATA=$(base64 -i oxy-setup.sh)

# Launch instance
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --count 1 \
  --instance-type t4g.small \
  --key-name oxy-keypair \
  --security-group-ids $EC2_SG_ID \
  --subnet-id $SUBNET_ID \
  --iam-instance-profile Name=OxyEC2InstanceProfile \
  --user-data "$USER_DATA" \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=oxy-playground-instance}]' \
  --query 'Instances[0].InstanceId' \
  --output text)

echo "Launched instance: $INSTANCE_ID"

# Wait for instance to be running
aws ec2 wait instance-running --instance-ids $INSTANCE_ID
echo "Instance is now running"

3.3 Create and Attach EBS Volume

# Get instance availability zone
AZ=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' \
  --output text)

# Create EBS volume
VOLUME_ID=$(aws ec2 create-volume \
  --size 10 \
  --availability-zone $AZ \
  --volume-type gp3 \
  --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=oxy-playground-data}]' \
  --query 'VolumeId' \
  --output text)

# Wait for volume to be available
aws ec2 wait volume-available --volume-ids $VOLUME_ID

# Attach volume to instance
aws ec2 attach-volume \
  --volume-id $VOLUME_ID \
  --instance-id $INSTANCE_ID \
  --device /dev/xvdf

echo "Attached volume: $VOLUME_ID"

Step 4: Configure Application Secrets

4.1 Store Environment Variables

# Store application environment variables
aws ssm put-parameter \
  --name "/oxy-playground/oxy/env" \
  --value "$(cat << 'EOF'
OXY_STATE_DIR=/mnt/data/oxy_data
AWS_REGION=us-west-2
BIGQUERY_CREDENTIALS_PATH=/mnt/data/customer-demo/bigquery-sample.key
# Add other environment variables as needed
EOF
)" \
  --type "String" \
  --region us-west-2

4.2 Store BigQuery Credentials

# Store BigQuery service account key
aws ssm put-parameter \
  --name "/oxy-playground/oxy/bigquery_sample_key" \
  --value "$(cat path/to/your/bigquery-key.json)" \
  --type "SecureString" \
  --region us-west-2

Step 5: Set Up Cognito Authentication

5.1 Create Cognito User Pool

# Create user pool
USER_POOL_ID=$(aws cognito-idp create-user-pool \
  --pool-name oxy-users \
  --policies '{
    "PasswordPolicy": {
      "MinimumLength": 8,
      "RequireUppercase": true,
      "RequireLowercase": true,
      "RequireNumbers": true,
      "RequireSymbols": true
    }
  }' \
  --auto-verified-attributes email \
  --username-attributes email \
  --query 'UserPool.Id' \
  --output text)

echo "Created User Pool: $USER_POOL_ID"

# Create user pool client
CLIENT_ID=$(aws cognito-idp create-user-pool-client \
  --user-pool-id $USER_POOL_ID \
  --client-name oxy-alb-client \
  --generate-secret \
  --supported-identity-providers COGNITO \
  --callback-urls "https://your-domain.com/oauth2/idpresponse" \
  --logout-urls "https://your-domain.com/logout" \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes openid email profile \
  --allowed-o-auth-flows-user-pool-client \
  --query 'UserPoolClient.ClientId' \
  --output text)

echo "Created Client: $CLIENT_ID"

# Create user pool domain
aws cognito-idp create-user-pool-domain \
  --domain oxy-auth-$(date +%s) \
  --user-pool-id $USER_POOL_ID

5.2 Create Application Load Balancer

# Create ALB
ALB_ARN=$(aws elbv2 create-load-balancer \
  --name oxy-alb \
  --subnets $SUBNET_ID \
  --security-groups $ALB_SG_ID \
  --scheme internet-facing \
  --type application \
  --ip-address-type ipv4 \
  --tags Key=Name,Value=oxy-alb \
  --query 'LoadBalancers[0].LoadBalancerArn' \
  --output text)

# Create target group
TG_ARN=$(aws elbv2 create-target-group \
  --name oxy-tg \
  --protocol HTTP \
  --port 3000 \
  --vpc-id $VPC_ID \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3 \
  --query 'TargetGroups[0].TargetGroupArn' \
  --output text)

# Register instance with target group
aws elbv2 register-targets \
  --target-group-arn $TG_ARN \
  --targets Id=$INSTANCE_ID,Port=3000

# Create ALB listener
aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=authenticate-cognito,AuthenticateCognitoConfig='{
    "UserPoolArn":"arn:aws:cognito-idp:us-west-2:'$ACCOUNT_ID':userpool/'$USER_POOL_ID'",
    "UserPoolClientId":"'$CLIENT_ID'",
    "UserPoolDomain":"oxy-auth-'$(date +%s)'",
    "OnUnauthenticatedRequest":"authenticate"
  }',Type=forward,TargetGroupArn=$TG_ARN

Step 6: Access Your Oxy Instance

6.1 Get Instance Information

# Get the instance public IP
INSTANCE_IP=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text)

echo "Instance IP: $INSTANCE_IP"

# Get ALB DNS name
ALB_DNS=$(aws elbv2 describe-load-balancers \
  --load-balancer-arns $ALB_ARN \
  --query 'LoadBalancers[0].DNSName' \
  --output text)

echo "ALB DNS: $ALB_DNS"

6.2 SSH Access (for debugging)

# Connect via SSH
ssh -i ~/.ssh/oxy-keypair.pem ubuntu@$INSTANCE_IP

6.3 Check Service Status

# On the EC2 instance
sudo systemctl status oxy
sudo journalctl -u oxy -f  # Follow logs

Step 7: Create Cognito Users

7.1 Create Admin User

# Create a user in Cognito
aws cognito-idp admin-create-user \
  --user-pool-id $USER_POOL_ID \
  --username admin \
  --user-attributes Name=email,Value=admin@your-domain.com \
  --temporary-password TempPass123! \
  --message-action SUPPRESS \
  --region us-west-2

# Set permanent password
aws cognito-idp admin-set-user-password \
  --user-pool-id $USER_POOL_ID \
  --username admin \
  --password SecureAdminPass123! \
  --permanent \
  --region us-west-2

Step 8: Monitoring and Maintenance

8.1 Monitor Instance Health

# Check instance status
aws ec2 describe-instance-status --instance-ids $INSTANCE_ID

# Monitor EBS volume
aws ec2 describe-volumes --volume-ids $VOLUME_ID

8.2 Application Logs

# SSH into instance and check logs
ssh -i ~/.ssh/oxy-keypair.pem ubuntu@$INSTANCE_IP
sudo journalctl -u oxy -f

8.3 Data Backup

# Create manual snapshot
SNAPSHOT_ID=$(aws ec2 create-snapshot \
  --volume-id $VOLUME_ID \
  --description "Oxy data backup $(date +%Y-%m-%d)" \
  --query 'SnapshotId' \
  --output text)

echo "Created snapshot: $SNAPSHOT_ID"

Troubleshooting

Common Issues

1. Instance fails to start Oxy service

# Check user data execution
ssh -i ~/.ssh/oxy-keypair.pem ubuntu@$INSTANCE_IP
sudo cat /var/log/cloud-init-output.log
sudo systemctl status oxy
sudo journalctl -u oxy

2. EBS volume not mounting

# Check if volume is attached
lsblk
sudo blkid /dev/nvme1n1
# Manually mount if needed
sudo mount /dev/nvme1n1 /mnt/data

3. Cannot access through ALB

# Check security group rules
aws ec2 describe-security-groups --group-ids $ALB_SG_ID
# Verify target group health
aws elbv2 describe-target-health --target-group-arn $TG_ARN

4. Cognito authentication issues

# Verify Cognito configuration
aws cognito-idp describe-user-pool --user-pool-id $USER_POOL_ID
aws cognito-idp describe-user-pool-client --user-pool-id $USER_POOL_ID --client-id $CLIENT_ID

Scaling Considerations

For the EC2 deployment:

  • Vertical scaling: Increase instance size (t4g.medium, t4g.large)
  • Storage scaling: Increase EBS volume size
  • High availability: Consider multiple instances with a shared database

For higher scale requirements, consider the ECS deployment guide.

Cleanup

To destroy the infrastructure:

# Stop and delete instance
aws ec2 terminate-instances --instance-ids $INSTANCE_ID

# Wait for termination
aws ec2 wait instance-terminated --instance-ids $INSTANCE_ID

# Delete EBS volume
aws ec2 delete-volume --volume-id $VOLUME_ID

# Delete load balancer
aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN

# Delete target group
aws elbv2 delete-target-group --target-group-arn $TG_ARN

# Delete Cognito user pool
aws cognito-idp delete-user-pool --user-pool-id $USER_POOL_ID

# Delete security groups
aws ec2 delete-security-group --group-id $EC2_SG_ID
aws ec2 delete-security-group --group-id $ALB_SG_ID

# Delete IAM resources
aws iam remove-role-from-instance-profile --instance-profile-name OxyEC2InstanceProfile --role-name OxyEC2Role
aws iam delete-instance-profile --instance-profile-name OxyEC2InstanceProfile
aws iam detach-role-policy --role-name OxyEC2Role --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam.detach-role-policy --role-name OxyEC2Role --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/OxySSMAccessPolicy
aws iam delete-role --role-name OxyEC2Role
aws iam delete-policy --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/OxySSMAccessPolicy

# Delete VPC resources
aws ec2 delete-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0
aws ec2 disassociate-route-table --association-id $(aws ec2 describe-route-tables --route-table-ids $ROUTE_TABLE_ID --query 'RouteTables[0].Associations[0].RouteTableAssociationId' --output text)
aws ec2 delete-route-table --route-table-id $ROUTE_TABLE_ID
aws ec2 delete-subnet --subnet-id $SUBNET_ID
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID
aws ec2 delete-vpc --vpc-id $VPC_ID

# Delete key pair
aws ec2 delete-key-pair --key-name oxy-keypair
rm ~/.ssh/oxy-keypair.pem

# Delete Parameter Store parameters
aws ssm delete-parameter --name "/oxy-playground/oxy/env"
aws ssm delete-parameter --name "/oxy-playground/oxy/bigquery_sample_key"

Security Best Practices

  1. Regularly update the EC2 instance:

    sudo apt update && sudo apt upgrade -y
    
  2. Rotate SSH keys and API credentials periodically

  3. Monitor CloudWatch logs for suspicious activity

  4. Use VPC Flow Logs for network monitoring

  5. Enable AWS Config for compliance monitoring

Next Steps

  • Set up CloudWatch monitoring and alarms
  • Configure automated EBS snapshots
  • Implement CI/CD for application updates
  • Consider migrating to ECS for production workloads
  • Set up log aggregation with CloudWatch Logs or ELK stack