There are more AWS SDK examples available in the AWS Doc SDK Examples
Get started using Amazon VPC using the CLI
The following code example shows how to:
Set up your account
Create and configure a VPC
Configure your network
Configure security
Deploy resources
Test and verify
Clean up resources
Consider production implications
Consider security implications
- Bash
-
- AWS CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # VPC Creation Script # This script creates a VPC with public and private subnets, internet gateway, NAT gateway, and security groups # Set up logging LOG_FILE="vpc_creation.log" exec > >(tee -a "$LOG_FILE") 2>&1 # Function to handle errors handle_error() { echo "ERROR: $1" echo "Resources created before error:" for resource in "${CREATED_RESOURCES[@]}" do echo "- $resource" done echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to clean up resources cleanup_resources() { echo "Cleaning up resources in reverse order..." # Reverse the array to delete in reverse order of creation for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)) do resource="${CREATED_RESOURCES[$i]}" resource_type=$(echo "$resource" | cut -d':' -f1) resource_id=$(echo "$resource" | cut -d':' -f2) case "$resource_type" in "INSTANCE") echo "Terminating EC2 instance: $resource_id" aws ec2 terminate-instances --instance-ids "$resource_id" || echo "Failed to terminate instance: $resource_id" # Wait for instance to terminate echo "Waiting for instance to terminate..." aws ec2 wait instance-terminated --instance-ids "$resource_id" || echo "Failed to wait for instance termination: $resource_id" ;; "KEY_PAIR") echo "Deleting key pair: $resource_id" aws ec2 delete-key-pair --key-name "$resource_id" || echo "Failed to delete key pair: $resource_id" # Remove the .pem file if it exists if [ -f "${resource_id}.pem" ]; then rm -f "${resource_id}.pem" fi ;; "NAT_GATEWAY") echo "Deleting NAT Gateway: $resource_id" aws ec2 delete-nat-gateway --nat-gateway-id "$resource_id" || echo "Failed to delete NAT Gateway: $resource_id" # NAT Gateway deletion takes time, wait for it to complete echo "Waiting for NAT Gateway to be deleted..." aws ec2 wait nat-gateway-deleted --nat-gateway-ids "$resource_id" || echo "Failed to wait for NAT Gateway deletion: $resource_id" ;; "EIP") echo "Releasing Elastic IP: $resource_id" aws ec2 release-address --allocation-id "$resource_id" || echo "Failed to release Elastic IP: $resource_id" ;; "ROUTE_TABLE_ASSOCIATION") echo "Disassociating Route Table: $resource_id" aws ec2 disassociate-route-table --association-id "$resource_id" || echo "Failed to disassociate Route Table: $resource_id" ;; "ROUTE_TABLE") echo "Deleting Route Table: $resource_id" aws ec2 delete-route-table --route-table-id "$resource_id" || echo "Failed to delete Route Table: $resource_id" ;; "INTERNET_GATEWAY") echo "Detaching Internet Gateway: $resource_id from VPC: $VPC_ID" aws ec2 detach-internet-gateway --internet-gateway-id "$resource_id" --vpc-id "$VPC_ID" || echo "Failed to detach Internet Gateway: $resource_id" echo "Deleting Internet Gateway: $resource_id" aws ec2 delete-internet-gateway --internet-gateway-id "$resource_id" || echo "Failed to delete Internet Gateway: $resource_id" ;; "SECURITY_GROUP") echo "Deleting Security Group: $resource_id" aws ec2 delete-security-group --group-id "$resource_id" || echo "Failed to delete Security Group: $resource_id" ;; "SUBNET") echo "Deleting Subnet: $resource_id" aws ec2 delete-subnet --subnet-id "$resource_id" || echo "Failed to delete Subnet: $resource_id" ;; "VPC") echo "Deleting VPC: $resource_id" aws ec2 delete-vpc --vpc-id "$resource_id" || echo "Failed to delete VPC: $resource_id" ;; esac done } # Initialize array to track created resources CREATED_RESOURCES=() echo "Starting VPC creation script at $(date)" # Verify AWS CLI configuration echo "Verifying AWS CLI configuration..." aws configure list || handle_error "AWS CLI is not properly configured" # Verify identity and permissions echo "Verifying identity and permissions..." if ! aws sts get-caller-identity; then echo "ERROR: Unable to verify AWS identity. This could be due to:" echo " - Expired credentials" echo " - Missing or invalid AWS credentials" echo " - Insufficient permissions" echo "" echo "Please run 'aws configure' to update your credentials or check your IAM permissions." exit 1 fi # Create VPC echo "Creating VPC with CIDR block 10.0.0.0/16..." VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=MyVPC}]' --query 'Vpc.VpcId' --output text) if [ -z "$VPC_ID" ]; then handle_error "Failed to create VPC" fi CREATED_RESOURCES+=("VPC:$VPC_ID") echo "VPC created with ID: $VPC_ID" # Enable DNS support and hostnames echo "Enabling DNS support and hostnames for VPC..." aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-support || handle_error "Failed to enable DNS support" aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-hostnames || handle_error "Failed to enable DNS hostnames" # Get available Availability Zones echo "Getting available Availability Zones..." AZ1=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0].ZoneName' --output text) AZ2=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[1].ZoneName' --output text) if [ -z "$AZ1" ] || [ -z "$AZ2" ]; then handle_error "Failed to get Availability Zones" fi echo "Using Availability Zones: $AZ1 and $AZ2" # Create public subnets echo "Creating public subnet in $AZ1..." PUBLIC_SUBNET_AZ1=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.0.0/24 \ --availability-zone "$AZ1" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-AZ1}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PUBLIC_SUBNET_AZ1" ]; then handle_error "Failed to create public subnet in AZ1" fi CREATED_RESOURCES+=("SUBNET:$PUBLIC_SUBNET_AZ1") echo "Public subnet created in $AZ1 with ID: $PUBLIC_SUBNET_AZ1" echo "Creating public subnet in $AZ2..." PUBLIC_SUBNET_AZ2=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.1.0/24 \ --availability-zone "$AZ2" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-AZ2}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PUBLIC_SUBNET_AZ2" ]; then handle_error "Failed to create public subnet in AZ2" fi CREATED_RESOURCES+=("SUBNET:$PUBLIC_SUBNET_AZ2") echo "Public subnet created in $AZ2 with ID: $PUBLIC_SUBNET_AZ2" # Create private subnets echo "Creating private subnet in $AZ1..." PRIVATE_SUBNET_AZ1=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.2.0/24 \ --availability-zone "$AZ1" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-Subnet-AZ1}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PRIVATE_SUBNET_AZ1" ]; then handle_error "Failed to create private subnet in AZ1" fi CREATED_RESOURCES+=("SUBNET:$PRIVATE_SUBNET_AZ1") echo "Private subnet created in $AZ1 with ID: $PRIVATE_SUBNET_AZ1" echo "Creating private subnet in $AZ2..." PRIVATE_SUBNET_AZ2=$(aws ec2 create-subnet \ --vpc-id "$VPC_ID" \ --cidr-block 10.0.3.0/24 \ --availability-zone "$AZ2" \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-Subnet-AZ2}]' \ --query 'Subnet.SubnetId' \ --output text) if [ -z "$PRIVATE_SUBNET_AZ2" ]; then handle_error "Failed to create private subnet in AZ2" fi CREATED_RESOURCES+=("SUBNET:$PRIVATE_SUBNET_AZ2") echo "Private subnet created in $AZ2 with ID: $PRIVATE_SUBNET_AZ2" # Create Internet Gateway echo "Creating Internet Gateway..." IGW_ID=$(aws ec2 create-internet-gateway \ --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=MyIGW}]' \ --query 'InternetGateway.InternetGatewayId' \ --output text) if [ -z "$IGW_ID" ]; then handle_error "Failed to create Internet Gateway" fi CREATED_RESOURCES+=("INTERNET_GATEWAY:$IGW_ID") echo "Internet Gateway created with ID: $IGW_ID" # Attach Internet Gateway to VPC echo "Attaching Internet Gateway to VPC..." aws ec2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" || handle_error "Failed to attach Internet Gateway to VPC" # Create public route table echo "Creating public route table..." PUBLIC_RT=$(aws ec2 create-route-table \ --vpc-id "$VPC_ID" \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Public-RT}]' \ --query 'RouteTable.RouteTableId' \ --output text) if [ -z "$PUBLIC_RT" ]; then handle_error "Failed to create public route table" fi CREATED_RESOURCES+=("ROUTE_TABLE:$PUBLIC_RT") echo "Public route table created with ID: $PUBLIC_RT" # Add route to Internet Gateway echo "Adding route to Internet Gateway in public route table..." aws ec2 create-route --route-table-id "$PUBLIC_RT" --destination-cidr-block 0.0.0.0/0 --gateway-id "$IGW_ID" || handle_error "Failed to add route to Internet Gateway" # Associate public subnets with public route table echo "Associating public subnet in $AZ1 with public route table..." PUBLIC_RT_ASSOC_1=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT" --subnet-id "$PUBLIC_SUBNET_AZ1" --query 'AssociationId' --output text) if [ -z "$PUBLIC_RT_ASSOC_1" ]; then handle_error "Failed to associate public subnet in AZ1 with public route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PUBLIC_RT_ASSOC_1") echo "Associating public subnet in $AZ2 with public route table..." PUBLIC_RT_ASSOC_2=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT" --subnet-id "$PUBLIC_SUBNET_AZ2" --query 'AssociationId' --output text) if [ -z "$PUBLIC_RT_ASSOC_2" ]; then handle_error "Failed to associate public subnet in AZ2 with public route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PUBLIC_RT_ASSOC_2") # Create private route table echo "Creating private route table..." PRIVATE_RT=$(aws ec2 create-route-table \ --vpc-id "$VPC_ID" \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Private-RT}]' \ --query 'RouteTable.RouteTableId' \ --output text) if [ -z "$PRIVATE_RT" ]; then handle_error "Failed to create private route table" fi CREATED_RESOURCES+=("ROUTE_TABLE:$PRIVATE_RT") echo "Private route table created with ID: $PRIVATE_RT" # Associate private subnets with private route table echo "Associating private subnet in $AZ1 with private route table..." PRIVATE_RT_ASSOC_1=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT" --subnet-id "$PRIVATE_SUBNET_AZ1" --query 'AssociationId' --output text) if [ -z "$PRIVATE_RT_ASSOC_1" ]; then handle_error "Failed to associate private subnet in AZ1 with private route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PRIVATE_RT_ASSOC_1") echo "Associating private subnet in $AZ2 with private route table..." PRIVATE_RT_ASSOC_2=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT" --subnet-id "$PRIVATE_SUBNET_AZ2" --query 'AssociationId' --output text) if [ -z "$PRIVATE_RT_ASSOC_2" ]; then handle_error "Failed to associate private subnet in AZ2 with private route table" fi CREATED_RESOURCES+=("ROUTE_TABLE_ASSOCIATION:$PRIVATE_RT_ASSOC_2") # Allocate Elastic IP for NAT Gateway echo "Allocating Elastic IP for NAT Gateway..." EIP_ALLOC=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text) if [ -z "$EIP_ALLOC" ]; then handle_error "Failed to allocate Elastic IP" fi CREATED_RESOURCES+=("EIP:$EIP_ALLOC") echo "Elastic IP allocated with ID: $EIP_ALLOC" # Create NAT Gateway echo "Creating NAT Gateway in public subnet in $AZ1..." NAT_GW=$(aws ec2 create-nat-gateway \ --subnet-id "$PUBLIC_SUBNET_AZ1" \ --allocation-id "$EIP_ALLOC" \ --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=MyNATGateway}]' \ --query 'NatGateway.NatGatewayId' \ --output text) if [ -z "$NAT_GW" ]; then handle_error "Failed to create NAT Gateway" fi CREATED_RESOURCES+=("NAT_GATEWAY:$NAT_GW") echo "NAT Gateway created with ID: $NAT_GW" # Wait for NAT Gateway to be available echo "Waiting for NAT Gateway to be available..." aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW" || handle_error "NAT Gateway did not become available" # Add route to NAT Gateway in private route table echo "Adding route to NAT Gateway in private route table..." aws ec2 create-route --route-table-id "$PRIVATE_RT" --destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT_GW" || handle_error "Failed to add route to NAT Gateway" # Enable auto-assign public IP for instances in public subnets echo "Enabling auto-assign public IP for instances in public subnet in $AZ1..." aws ec2 modify-subnet-attribute --subnet-id "$PUBLIC_SUBNET_AZ1" --map-public-ip-on-launch || handle_error "Failed to enable auto-assign public IP for public subnet in AZ1" echo "Enabling auto-assign public IP for instances in public subnet in $AZ2..." aws ec2 modify-subnet-attribute --subnet-id "$PUBLIC_SUBNET_AZ2" --map-public-ip-on-launch || handle_error "Failed to enable auto-assign public IP for public subnet in AZ2" # Create security group for web servers echo "Creating security group for web servers..." WEB_SG=$(aws ec2 create-security-group \ --group-name "WebServerSG-$(date +%s)" \ --description "Security group for web servers" \ --vpc-id "$VPC_ID" \ --query 'GroupId' \ --output text) if [ -z "$WEB_SG" ]; then handle_error "Failed to create security group for web servers" fi CREATED_RESOURCES+=("SECURITY_GROUP:$WEB_SG") echo "Security group for web servers created with ID: $WEB_SG" # Allow HTTP and HTTPS traffic echo "Allowing HTTP traffic to web servers security group..." aws ec2 authorize-security-group-ingress --group-id "$WEB_SG" --protocol tcp --port 80 --cidr 0.0.0.0/0 || handle_error "Failed to allow HTTP traffic" echo "Allowing HTTPS traffic to web servers security group..." aws ec2 authorize-security-group-ingress --group-id "$WEB_SG" --protocol tcp --port 443 --cidr 0.0.0.0/0 || handle_error "Failed to allow HTTPS traffic" # Note: In a production environment, you should restrict the source IP ranges for security echo "NOTE: In a production environment, you should restrict the source IP ranges for HTTP and HTTPS traffic" # Create security group for database servers echo "Creating security group for database servers..." DB_SG=$(aws ec2 create-security-group \ --group-name "DBServerSG-$(date +%s)" \ --description "Security group for database servers" \ --vpc-id "$VPC_ID" \ --query 'GroupId' \ --output text) if [ -z "$DB_SG" ]; then handle_error "Failed to create security group for database servers" fi CREATED_RESOURCES+=("SECURITY_GROUP:$DB_SG") echo "Security group for database servers created with ID: $DB_SG" # Allow MySQL/Aurora traffic from web servers only echo "Allowing MySQL/Aurora traffic from web servers to database servers..." aws ec2 authorize-security-group-ingress --group-id "$DB_SG" --protocol tcp --port 3306 --source-group "$WEB_SG" || handle_error "Failed to allow MySQL/Aurora traffic" # Verify VPC configuration echo "Verifying VPC configuration..." echo "VPC:" aws ec2 describe-vpcs --vpc-id "$VPC_ID" || handle_error "Failed to describe VPC" echo "Subnets:" aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe subnets" echo "Route tables:" aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe route tables" echo "Internet gateway:" aws ec2 describe-internet-gateways --filters "Name=attachment.vpc-id,Values=$VPC_ID" || handle_error "Failed to describe Internet Gateway" echo "NAT gateway:" aws ec2 describe-nat-gateways --filter "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe NAT Gateway" echo "Security groups:" aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" || handle_error "Failed to describe security groups" echo "" # Summary of created resources echo "VPC creation completed successfully!" echo "Summary of created resources:" echo "- VPC: $VPC_ID" echo "- Public Subnet in $AZ1: $PUBLIC_SUBNET_AZ1" echo "- Public Subnet in $AZ2: $PUBLIC_SUBNET_AZ2" echo "- Private Subnet in $AZ1: $PRIVATE_SUBNET_AZ1" echo "- Private Subnet in $AZ2: $PRIVATE_SUBNET_AZ2" echo "- Internet Gateway: $IGW_ID" echo "- Public Route Table: $PUBLIC_RT" echo "- Private Route Table: $PRIVATE_RT" echo "- Elastic IP: $EIP_ALLOC" echo "- NAT Gateway: $NAT_GW" echo "- Web Servers Security Group: $WEB_SG" echo "- Database Servers Security Group: $DB_SG" # Deploy EC2 instances echo "" echo "Deploying EC2 instances..." # Create key pair for SSH access KEY_NAME="vpc-tutorial-key-$(date +%s)" echo "Creating key pair $KEY_NAME..." aws ec2 create-key-pair --key-name "$KEY_NAME" --query 'KeyMaterial' --output text > "${KEY_NAME}.pem" || handle_error "Failed to create key pair" chmod 400 "${KEY_NAME}.pem" echo "Key pair saved to ${KEY_NAME}.pem" CREATED_RESOURCES+=("KEY_PAIR:$KEY_NAME") # Get latest Amazon Linux 2 AMI echo "Getting latest Amazon Linux 2 AMI..." AMI_ID=$(aws ec2 describe-images --owners amazon \ --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" \ --query "sort_by(Images, &CreationDate)[-1].ImageId" --output text) || handle_error "Failed to get AMI" echo "Using AMI: $AMI_ID" # Launch web server in public subnet echo "Launching web server in public subnet..." WEB_INSTANCE=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --count 1 \ --instance-type t2.micro \ --key-name "$KEY_NAME" \ --security-group-ids "$WEB_SG" \ --subnet-id "$PUBLIC_SUBNET_AZ1" \ --associate-public-ip-address \ --user-data '#!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<h1>Hello from $(hostname -f) in the public subnet</h1>" > /var/www/html/index.html' \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=WebServer}]' \ --query 'Instances[0].InstanceId' \ --output text) || handle_error "Failed to launch web server" echo "Web server instance created with ID: $WEB_INSTANCE" CREATED_RESOURCES+=("INSTANCE:$WEB_INSTANCE") # Wait for web server to be running echo "Waiting for web server to be running..." aws ec2 wait instance-running --instance-ids "$WEB_INSTANCE" # Get web server public IP WEB_PUBLIC_IP=$(aws ec2 describe-instances --instance-ids "$WEB_INSTANCE" \ --query 'Reservations[0].Instances[0].PublicIpAddress' --output text) echo "Web server public IP: $WEB_PUBLIC_IP" echo "You can access the web server at: http://$WEB_PUBLIC_IP" # Launch database server in private subnet echo "Launching database server in private subnet..." DB_INSTANCE=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --count 1 \ --instance-type t2.micro \ --key-name "$KEY_NAME" \ --security-group-ids "$DB_SG" \ --subnet-id "$PRIVATE_SUBNET_AZ1" \ --user-data '#!/bin/bash yum update -y yum install -y mariadb-server systemctl start mariadb systemctl enable mariadb' \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=DBServer}]' \ --query 'Instances[0].InstanceId' \ --output text) || handle_error "Failed to launch database server" echo "Database server instance created with ID: $DB_INSTANCE" CREATED_RESOURCES+=("INSTANCE:$DB_INSTANCE") # Wait for database server to be running echo "Waiting for database server to be running..." aws ec2 wait instance-running --instance-ids "$DB_INSTANCE" # Get database server private IP DB_PRIVATE_IP=$(aws ec2 describe-instances --instance-ids "$DB_INSTANCE" \ --query 'Reservations[0].Instances[0].PrivateIpAddress' --output text) echo "Database server private IP: $DB_PRIVATE_IP" echo "EC2 instances deployed successfully!" echo "- Web Server (Public): $WEB_INSTANCE ($WEB_PUBLIC_IP)" echo "- Database Server (Private): $DB_INSTANCE ($DB_PRIVATE_IP)" echo "" echo "Note: To connect to the web server: ssh -i ${KEY_NAME}.pem ec2-user@$WEB_PUBLIC_IP" echo "To connect to the database server, you must first connect to the web server, then use it as a bastion host." echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then echo "Cleaning up resources..." cleanup_resources echo "All resources have been cleaned up." else echo "Resources will not be cleaned up. You can manually clean them up later." fi echo "Script completed at $(date)"
-
For API details, see the following topics in AWS CLI Command Reference.
-
Create a VPC with private subnets and NAT gateways
Get started with Transit Gateway