AWS
EC2
Boto3
Cloud Computing
Python Programming

How to create an ec2 instance using boto3

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Amazon EC2 (Elastic Compute Cloud) lets you spin up virtual servers on demand. When you manage infrastructure at scale, clicking through the AWS console for each instance is not practical. Boto3, the official AWS SDK for Python, gives you a programmatic way to create, configure, and tear down EC2 instances in reproducible scripts.

This article walks you through launching an EC2 instance with Boto3 from scratch, including security group setup, tagging, waiting for the instance to be ready, and cleaning up when you are done.

Prerequisites

Before writing any code, make sure you have:

  1. An active AWS account with an IAM user that has the AmazonEC2FullAccess policy (or equivalent permissions).
  2. AWS credentials configured locally via aws configure or environment variables (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY).
  3. Python 3.8+ and Boto3 installed (pip install boto3).
  4. A key pair already created in the AWS console (or via Boto3) so you can SSH into the instance.
  5. An AMI ID for your desired operating system (for example, ami-0abcdef1234567890 for Amazon Linux 2 in us-east-1).

Setting Up a Security Group

A security group acts as a virtual firewall. You should create one that allows only the traffic you need before launching an instance.

python
1import boto3
2
3ec2_client = boto3.client('ec2', region_name='us-east-1')
4
5# Create a security group
6sg_response = ec2_client.create_security_group(
7    GroupName='my-boto3-sg',
8    Description='Security group for boto3-launched instance',
9    VpcId='vpc-0abc1234def56789a'  # replace with your VPC ID
10)
11security_group_id = sg_response['GroupId']
12
13# Allow SSH (port 22) from your IP only
14ec2_client.authorize_security_group_ingress(
15    GroupId=security_group_id,
16    IpPermissions=[
17        {
18            'IpProtocol': 'tcp',
19            'FromPort': 22,
20            'ToPort': 22,
21            'IpRanges': [{'CidrIp': '203.0.113.0/32'}]  # your IP
22        }
23    ]
24)
25
26print(f"Created security group: {security_group_id}")

Restricting the CIDR to your specific IP address is a good security practice. Opening 0.0.0.0/0 for SSH is strongly discouraged in any environment beyond quick experiments.

Creating the EC2 Instance

The create_instances method on the EC2 resource is the central call. Here is a complete example with the most important parameters.

python
1ec2_resource = boto3.resource('ec2', region_name='us-east-1')
2
3instances = ec2_resource.create_instances(
4    ImageId='ami-0abcdef1234567890',       # Amazon Linux 2 AMI
5    InstanceType='t3.micro',                # free-tier eligible
6    KeyName='my-key-pair',                  # existing key pair name
7    MinCount=1,
8    MaxCount=1,
9    SecurityGroupIds=[security_group_id],
10    SubnetId='subnet-0abc1234def56789b',    # your subnet
11    BlockDeviceMappings=[
12        {
13            'DeviceName': '/dev/xvda',
14            'Ebs': {
15                'VolumeSize': 20,           # GB
16                'VolumeType': 'gp3',
17                'DeleteOnTermination': True
18            }
19        }
20    ],
21    Monitoring={'Enabled': False},          # set True for detailed CloudWatch
22    TagSpecifications=[
23        {
24            'ResourceType': 'instance',
25            'Tags': [
26                {'Key': 'Name', 'Value': 'my-boto3-instance'},
27                {'Key': 'Environment', 'Value': 'development'}
28            ]
29        }
30    ]
31)
32
33instance = instances[0]
34print(f"Launched instance: {instance.id}")

The TagSpecifications parameter lets you tag the instance at creation time, which is much cleaner than making a separate create_tags call afterward.

Waiting Until the Instance Is Running

create_instances returns immediately, but the instance needs time to boot. Use the built-in waiter to block until it reaches the running state.

python
1print("Waiting for instance to enter 'running' state...")
2instance.wait_until_running()
3
4# Reload to get the public IP address
5instance.reload()
6print(f"Instance is running. Public IP: {instance.public_ip_address}")

Under the hood, wait_until_running polls the describe_instances API at regular intervals. If the instance fails to start within the default timeout (about 10 minutes), the waiter raises an exception.

Tagging After Launch

If you need to add or update tags after the instance is already running, use the create_tags method.

python
1instance.create_tags(
2    Tags=[
3        {'Key': 'Team', 'Value': 'backend'},
4        {'Key': 'CostCenter', 'Value': '12345'}
5    ]
6)

Tearing Down the Instance

When you are finished with the instance, terminate it and clean up the security group to avoid ongoing charges.

python
1# Terminate the instance
2instance.terminate()
3print(f"Terminating instance: {instance.id}")
4
5# Wait for termination to complete before deleting the security group
6instance.wait_until_terminated()
7
8# Delete the security group
9ec2_client.delete_security_group(GroupId=security_group_id)
10print(f"Deleted security group: {security_group_id}")

You must wait for the instance to fully terminate before deleting its security group, because AWS will not allow you to remove a security group that is still attached to a running or shutting-down instance.

Common Pitfalls

  • Using the wrong AMI for the region. AMI IDs are region-specific. An AMI that works in us-east-1 will not exist in eu-west-1. Always look up the correct ID for your target region.
  • Opening SSH to 0.0.0.0/0. This exposes port 22 to the entire internet. Restrict ingress rules to your own IP or a VPN range.
  • Forgetting to specify DeleteOnTermination on EBS volumes. Without it, volumes persist after the instance is terminated and you keep paying for them.
  • Not waiting for state transitions. Calling instance.public_ip_address right after create_instances often returns None because the IP has not been assigned yet. Always call wait_until_running and then reload.
  • Hardcoding credentials in source code. Never embed AWS keys in your Python files. Use environment variables, the ~/.aws/credentials file, or IAM roles attached to the machine running the script.

Summary

  • Boto3 lets you automate EC2 lifecycle management entirely from Python, including creation, monitoring, and teardown.
  • Always create a security group with the minimum necessary ingress rules before launching an instance.
  • Use TagSpecifications in create_instances to tag resources at launch time rather than in a separate call.
  • Call wait_until_running and reload before reading properties like public_ip_address.
  • Clean up instances and security groups when they are no longer needed to avoid unnecessary AWS charges.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.