Skip to content

Python boto3 cheatsheet for common AWS services and daily automation tasks.

Terminal window
pip install boto3 botocore
import boto3
from botocore.exceptions import ClientError
session = boto3.Session(
profile_name="dev",
region_name="us-east-1",
)
s3 = session.client("s3")
ec2 = session.resource("ec2")
# Client: low-level API, available for every AWS service
s3_client = boto3.client("s3")
s3_client.list_buckets()
# Resource: higher-level object API, available for selected services
s3_resource = boto3.resource("s3")
bucket = s3_resource.Bucket("my-bucket")

Common places boto3 checks:

  1. Explicit parameters in code.
  2. Environment variables such as AWS_ACCESS_KEY_ID.
  3. Shared credentials file: ~/.aws/credentials.
  4. Shared config file: ~/.aws/config.
  5. Assume-role profiles.
  6. Container or EC2 instance role metadata.
import json
import time
import boto3
from pathlib import Path
from botocore.config import Config
from botocore.exceptions import ClientError, NoCredentialsError, WaiterError
from botocore.config import Config
config = Config(
retries={"max_attempts": 10, "mode": "standard"},
connect_timeout=5,
read_timeout=60,
)
s3 = boto3.client("s3", config=config)

from botocore.exceptions import ClientError
try:
s3.head_bucket(Bucket="my-bucket")
except ClientError as exc:
code = exc.response["Error"]["Code"]
message = exc.response["Error"]["Message"]
print(code, message)
try:
dynamodb.get_item(
TableName="users",
Key={"pk": {"S": "user#1"}},
)
except ClientError as exc:
if exc.response["Error"]["Code"] == "ResourceNotFoundException":
print("Table does not exist")
else:
raise

s3 = boto3.client("s3")
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket="my-bucket", Prefix="logs/"):
for obj in page.get("Contents", []):
print(obj["Key"], obj["Size"])
ec2 = boto3.client("ec2")
ec2.start_instances(InstanceIds=["i-0123456789abcdef0"])
waiter = ec2.get_waiter("instance_running")
waiter.wait(InstanceIds=["i-0123456789abcdef0"])

s3 = boto3.client("s3")
for bucket in s3.list_buckets()["Buckets"]:
print(bucket["Name"], bucket["CreationDate"])
s3.create_bucket(
Bucket="my-unique-bucket-name",
CreateBucketConfiguration={"LocationConstraint": "ap-south-1"},
)

For us-east-1, omit CreateBucketConfiguration.

s3.upload_file(
Filename="report.csv",
Bucket="my-bucket",
Key="reports/report.csv",
)
payload = {"status": "ok"}
s3.put_object(
Bucket="my-bucket",
Key="data/status.json",
Body=json.dumps(payload).encode("utf-8"),
ContentType="application/json",
)
s3.download_file(
Bucket="my-bucket",
Key="reports/report.csv",
Filename="report.csv",
)
response = s3.get_object(Bucket="my-bucket", Key="data/status.json")
data = json.loads(response["Body"].read())
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket="my-bucket", Prefix="logs/"):
for obj in page.get("Contents", []):
print(obj["Key"])
s3.copy_object(
Bucket="target-bucket",
Key="archive/report.csv",
CopySource={"Bucket": "source-bucket", "Key": "reports/report.csv"},
)
s3.delete_object(Bucket="my-bucket", Key="reports/old.csv")
objects = [{"Key": "tmp/a.txt"}, {"Key": "tmp/b.txt"}]
s3.delete_objects(
Bucket="my-bucket",
Delete={"Objects": objects, "Quiet": True},
)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-bucket", "Key": "reports/report.csv"},
ExpiresIn=3600,
)
print(url)

ec2 = boto3.client("ec2")
response = ec2.describe_instances()
for reservation in response["Reservations"]:
for instance in reservation["Instances"]:
print(instance["InstanceId"], instance["State"]["Name"])
response = ec2.describe_instances(
Filters=[
{"Name": "instance-state-name", "Values": ["running"]},
{"Name": "tag:Environment", "Values": ["prod"]},
]
)
ids = ["i-0123456789abcdef0"]
ec2.start_instances(InstanceIds=ids)
ec2.stop_instances(InstanceIds=ids)
ec2.reboot_instances(InstanceIds=ids)
response = ec2.run_instances(
ImageId="ami-0123456789abcdef0",
InstanceType="t3.micro",
MinCount=1,
MaxCount=1,
KeyName="my-key",
SecurityGroupIds=["sg-0123456789abcdef0"],
SubnetId="subnet-0123456789abcdef0",
TagSpecifications=[
{
"ResourceType": "instance",
"Tags": [{"Key": "Name", "Value": "demo-server"}],
}
],
)
instance_id = response["Instances"][0]["InstanceId"]
ec2.terminate_instances(InstanceIds=["i-0123456789abcdef0"])
for reservation in ec2.describe_instances()["Reservations"]:
for instance in reservation["Instances"]:
print(instance["InstanceId"], instance.get("PublicIpAddress"))
ec2.authorize_security_group_ingress(
GroupId="sg-0123456789abcdef0",
IpPermissions=[
{
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS"}],
}
],
)

iam = boto3.client("iam")
for user in iam.get_paginator("list_users").paginate():
for item in user["Users"]:
print(item["UserName"])
for role_page in iam.get_paginator("list_roles").paginate():
for role in role_page["Roles"]:
print(role["RoleName"])
iam.create_user(UserName="alice")
iam.attach_user_policy(
UserName="alice",
PolicyArn="arn:aws:iam::aws:policy/ReadOnlyAccess",
)
assume_role_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
iam.create_role(
RoleName="lambda-basic-role",
AssumeRolePolicyDocument=json.dumps(assume_role_policy),
)
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/*",
}
],
}
iam.put_role_policy(
RoleName="lambda-basic-role",
PolicyName="read-bucket",
PolicyDocument=json.dumps(policy),
)

sts = boto3.client("sts")
print(sts.get_caller_identity())
response = sts.assume_role(
RoleArn="arn:aws:iam::123456789012:role/Admin",
RoleSessionName="automation-session",
)
creds = response["Credentials"]
assumed = boto3.Session(
aws_access_key_id=creds["AccessKeyId"],
aws_secret_access_key=creds["SecretAccessKey"],
aws_session_token=creds["SessionToken"],
)

lambda_client = boto3.client("lambda")
for page in lambda_client.get_paginator("list_functions").paginate():
for fn in page["Functions"]:
print(fn["FunctionName"], fn["Runtime"])
response = lambda_client.invoke(
FunctionName="my-function",
InvocationType="RequestResponse",
Payload=json.dumps({"name": "Alice"}).encode("utf-8"),
)
payload = json.loads(response["Payload"].read())
lambda_client.invoke(
FunctionName="my-function",
InvocationType="Event",
Payload=json.dumps({"task": "refresh"}).encode("utf-8"),
)
lambda_client.update_function_configuration(
FunctionName="my-function",
Environment={
"Variables": {
"ENV": "prod",
"LOG_LEVEL": "INFO",
}
},
)
lambda_client.update_function_code(
FunctionName="my-function",
S3Bucket="deploy-artifacts",
S3Key="lambda/my-function.zip",
Publish=True,
)

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("users")
table.put_item(
Item={
"pk": "user#1",
"sk": "profile",
"name": "Alice",
"age": 30,
}
)
response = table.get_item(
Key={"pk": "user#1", "sk": "profile"}
)
item = response.get("Item")
response = table.update_item(
Key={"pk": "user#1", "sk": "profile"},
UpdateExpression="SET age = :age, updated_at = :updated_at",
ExpressionAttributeValues={
":age": 31,
":updated_at": "2026-05-18T00:00:00Z",
},
ReturnValues="ALL_NEW",
)
print(response["Attributes"])
from boto3.dynamodb.conditions import Key
response = table.query(
KeyConditionExpression=Key("pk").eq("user#1")
)
for item in response["Items"]:
print(item)
response = table.query(
IndexName="email-index",
KeyConditionExpression=Key("email").eq("alice@example.com"),
)
from boto3.dynamodb.conditions import Attr
response = table.scan(
FilterExpression=Attr("status").eq("active")
)

Use scan carefully on large tables.

with table.batch_writer() as batch:
for i in range(100):
batch.put_item(Item={"pk": f"user#{i}", "sk": "profile"})
table.delete_item(Key={"pk": "user#1", "sk": "profile"})

sqs = boto3.client("sqs")
response = sqs.create_queue(
QueueName="jobs",
Attributes={"VisibilityTimeout": "60"},
)
queue_url = response["QueueUrl"]
sqs.send_message(
QueueUrl=queue_url,
MessageBody=json.dumps({"job_id": 123}),
)
sqs.send_message(
QueueUrl="https://sqs.us-east-1.amazonaws.com/123/jobs.fifo",
MessageBody="process-order",
MessageGroupId="orders",
MessageDeduplicationId="order-123",
)
response = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=10,
WaitTimeSeconds=20,
VisibilityTimeout=60,
)
for message in response.get("Messages", []):
print(message["Body"])
sqs.delete_message(
QueueUrl=queue_url,
ReceiptHandle=message["ReceiptHandle"],
)
sqs.change_message_visibility(
QueueUrl=queue_url,
ReceiptHandle=message["ReceiptHandle"],
VisibilityTimeout=120,
)

sns = boto3.client("sns")
response = sns.create_topic(Name="alerts")
topic_arn = response["TopicArn"]
sns.publish(
TopicArn=topic_arn,
Subject="Build failed",
Message="The production build failed.",
)
sns.subscribe(
TopicArn=topic_arn,
Protocol="email",
Endpoint="admin@example.com",
)
sns.publish(
TopicArn=topic_arn,
Message=json.dumps({"default": "Fallback", "email": "Email body"}),
MessageStructure="json",
)

logs = boto3.client("logs")
for page in logs.get_paginator("describe_log_groups").paginate():
for group in page["logGroups"]:
print(group["logGroupName"])
response = logs.get_log_events(
logGroupName="/aws/lambda/my-function",
logStreamName="2026/05/18/[$LATEST]abcdef",
startFromHead=True,
)
for event in response["events"]:
print(event["message"])
response = logs.filter_log_events(
logGroupName="/aws/lambda/my-function",
filterPattern="ERROR",
limit=50,
)
for event in response["events"]:
print(event["message"])
cloudwatch = boto3.client("cloudwatch")
cloudwatch.put_metric_data(
Namespace="MyApp",
MetricData=[
{
"MetricName": "JobsProcessed",
"Value": 42,
"Unit": "Count",
"Dimensions": [{"Name": "Environment", "Value": "prod"}],
}
],
)

ssm = boto3.client("ssm")
response = ssm.get_parameter(
Name="/myapp/prod/db-url",
WithDecryption=True,
)
value = response["Parameter"]["Value"]
paginator = ssm.get_paginator("get_parameters_by_path")
for page in paginator.paginate(
Path="/myapp/prod/",
Recursive=True,
WithDecryption=True,
):
for param in page["Parameters"]:
print(param["Name"], param["Value"])
ssm.put_parameter(
Name="/myapp/prod/api-key",
Value="secret-value",
Type="SecureString",
Overwrite=True,
)
response = ssm.send_command(
InstanceIds=["i-0123456789abcdef0"],
DocumentName="AWS-RunShellScript",
Parameters={"commands": ["uptime", "df -h"]},
)
command_id = response["Command"]["CommandId"]

secrets = boto3.client("secretsmanager")
response = secrets.get_secret_value(SecretId="prod/db")
if "SecretString" in response:
secret = json.loads(response["SecretString"])
else:
secret = response["SecretBinary"]
secrets.create_secret(
Name="prod/api",
SecretString=json.dumps({"token": "secret-token"}),
)
secrets.put_secret_value(
SecretId="prod/api",
SecretString=json.dumps({"token": "new-secret-token"}),
)

ecr = boto3.client("ecr")
response = ecr.get_authorization_token()
auth_data = response["authorizationData"][0]
print(auth_data["proxyEndpoint"])

For Docker login, the AWS CLI is usually simpler:

Terminal window
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
ecr.create_repository(
repositoryName="my-service",
imageScanningConfiguration={"scanOnPush": True},
)
response = ecr.list_images(repositoryName="my-service")
for image in response["imageIds"]:
print(image)

ecs = boto3.client("ecs")
clusters = ecs.list_clusters()["clusterArns"]
for cluster in clusters:
services = ecs.list_services(cluster=cluster)["serviceArns"]
print(cluster, services)
ecs.update_service(
cluster="my-cluster",
service="api",
desiredCount=3,
)
ecs.update_service(
cluster="my-cluster",
service="api",
forceNewDeployment=True,
)
ecs.run_task(
cluster="my-cluster",
taskDefinition="worker:12",
launchType="FARGATE",
networkConfiguration={
"awsvpcConfiguration": {
"subnets": ["subnet-0123456789abcdef0"],
"securityGroups": ["sg-0123456789abcdef0"],
"assignPublicIp": "ENABLED",
}
},
)

rds = boto3.client("rds")
for page in rds.get_paginator("describe_db_instances").paginate():
for db in page["DBInstances"]:
print(db["DBInstanceIdentifier"], db["DBInstanceStatus"])
rds.create_db_snapshot(
DBInstanceIdentifier="prod-db",
DBSnapshotIdentifier="prod-db-manual-2026-05-18",
)
rds.stop_db_instance(DBInstanceIdentifier="dev-db")
rds.start_db_instance(DBInstanceIdentifier="dev-db")
rds.modify_db_instance(
DBInstanceIdentifier="dev-db",
AllocatedStorage=100,
ApplyImmediately=True,
)

route53 = boto3.client("route53")
for zone in route53.list_hosted_zones()["HostedZones"]:
print(zone["Name"], zone["Id"])
records = route53.list_resource_record_sets(
HostedZoneId="/hostedzone/Z1234567890",
)
for record in records["ResourceRecordSets"]:
print(record["Name"], record["Type"])
route53.change_resource_record_sets(
HostedZoneId="/hostedzone/Z1234567890",
ChangeBatch={
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com.",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "203.0.113.10"}],
},
}
]
},
)

def ensure_bucket(name: str, region: str = "us-east-1") -> None:
s3 = boto3.client("s3", region_name=region)
try:
s3.head_bucket(Bucket=name)
return
except ClientError as exc:
status = exc.response["ResponseMetadata"]["HTTPStatusCode"]
if status not in (403, 404):
raise
kwargs = {"Bucket": name}
if region != "us-east-1":
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": region}
s3.create_bucket(**kwargs)
def wait_for_instance_state(instance_id: str, state: str, timeout: int = 300) -> None:
ec2 = boto3.client("ec2")
deadline = time.time() + timeout
while time.time() < deadline:
response = ec2.describe_instances(InstanceIds=[instance_id])
current = response["Reservations"][0]["Instances"][0]["State"]["Name"]
if current == state:
return
time.sleep(5)
raise TimeoutError(f"{instance_id} did not reach {state}")
ec2.create_tags(
Resources=["i-0123456789abcdef0"],
Tags=[
{"Key": "Environment", "Value": "prod"},
{"Key": "Owner", "Value": "platform"},
],
)
import os
session = boto3.Session(
profile_name=os.getenv("AWS_PROFILE"),
region_name=os.getenv("AWS_REGION", "us-east-1"),
)
def client(service: str, profile: str | None = None, region: str = "us-east-1"):
session = boto3.Session(profile_name=profile, region_name=region)
return session.client(service)
s3 = client("s3", profile="prod")

import boto3
from botocore.stub import Stubber
s3 = boto3.client("s3")
with Stubber(s3) as stubber:
stubber.add_response(
"list_buckets",
{"Buckets": [], "Owner": {"DisplayName": "me", "ID": "123"}},
)
response = s3.list_buckets()
Terminal window
pip install moto pytest
import boto3
from moto import mock_aws
@mock_aws
def test_create_bucket():
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="test-bucket")
buckets = s3.list_buckets()["Buckets"]
assert buckets[0]["Name"] == "test-bucket"
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:4566",
aws_access_key_id="test",
aws_secret_access_key="test",
region_name="us-east-1",
)

  • Use IAM roles instead of hardcoded credentials.
  • Use least-privilege IAM policies.
  • Set retry and timeout configuration for automation.
  • Use paginators for list operations.
  • Use waiters for supported async workflows.
  • Handle ClientError explicitly.
  • Avoid scan on large DynamoDB tables unless you really need it.
  • Encrypt S3, RDS, SQS, SNS, SSM, and Secrets Manager data where required.
  • Tag resources consistently.
  • Log AWS request IDs when debugging production failures.