Lightsail is the AWS service that people either love or quietly regret. The love is easy to explain: you click once, you get a server with a public IP and a monthly price you can say out loud. The regret shows up eight months later, when the project has grown a queue, a Lambda function and a data lake, and the server is sitting in a corner of AWS that does not talk to any of them the way you expected. This article is the definition, the honest economics, and a build you can finish in about thirty minutes for pocket change. It ends with the part most tutorials skip: exporting the server to EC2 so you know the exit door opens before you need it.
What it actually is
Lightsail is a simplified front end to a small set of AWS building blocks: virtual private servers, container services, managed MySQL and PostgreSQL databases, load balancers, CDN distributions built on CloudFront, block storage and object storage buckets. The AWS documentation describes it as an easy-to-use cloud platform, and the key word is "bundle". You do not pick an instance type, a volume, an IP and a transfer budget separately. You pick a bundle, and the bundle contains all of them at one monthly price.
The mental model the docs bury: Lightsail is not a cheaper EC2. It is a different product with a different billing contract. EC2 sells you components and charges for each one as you use it. Lightsail sells you a pre-assembled package at a fixed price, with a data transfer allowance included, and makes you live inside the package. The savings are real when your workload fits the package and disappear when it does not.
Lightsail is also not a dying service you should avoid. AWS has kept shipping on it through 2026: memory-optimized and compute-optimized bundles, larger managed database bundles, new regions, CDN support for IPv6-only origins, and an AWS Marketplace integration. I checked, because the EOL guard for this series matters, and nothing I found says Lightsail is closed to new customers. It is open, and it is growing upward.
The model
An instance is a virtual machine created from a blueprint (an OS, or an OS plus an application such as WordPress or a LAMP stack) and a bundle (vCPU, memory, SSD, transfer allowance). Every instance lives in one Availability Zone of one Region. It gets a private IP always, and a public IP unless you choose an IPv6-only bundle. By default the public IPv4 address changes if you stop and start the instance, so anything you point DNS at should get a static IP, which you attach to the instance.
Networking is the part that differs most from a VPC you build yourself. Each instance has two firewalls, one for IPv4 and one for IPv6, and you configure them separately. Rules are allow-only, you cannot write a deny rule, outbound traffic is unrestricted, and the rules apply to the public IP, not the private one. A new base-OS instance opens SSH on 22 and HTTP on 80. If two rules cover the same port the most permissive wins.
Lightsail resources run in an AWS-managed VPC. To talk privately to anything you built in your own VPC, you enable VPC peering, and the peering connects Lightsail to the default VPC in the same Region. It is a per-Region toggle, you must have a default VPC, and security groups on the EC2 side still have to allow the traffic. S3, CloudFront and DynamoDB do not need peering because they are reached over public endpoints.
The performance contract is the one that surprises people. Most bundles are burstable. Each plan has a baseline CPU percentage per vCPU, and you accrue burst capacity while below it and spend it while above it. The documented baselines for current-generation dual-stack plans are 5 percent on the $5 plan, 10 percent on $7, 20 percent on $12 and $24, 30 percent on $44, and 40 percent on the $84, $164 and larger plans. Accrual is 4.17 percent of burst capacity per hour, capped at what you could earn in 24 hours, which is 100 percent. The very large plans ($384 and above on Linux) burst automatically and do not use credits.
What AWS operates: the hypervisor, the host, the network, snapshots storage, the managed database engine on the database product. What you operate: the OS, patching, the web server, the firewall rules, backups beyond the snapshot schedule you configure, and the decision to leave.
When to use it, when not to
Lightsail instance | EC2 | Lightsail containers | ECS on Fargate | |
|---|---|---|---|---|
Pricing contract | Fixed monthly bundle | Per-second components | Fixed monthly per node | Per vCPU-second and GB-second |
Data transfer | Allowance included | Billed per GB out | 500 GB per service included | Billed per GB out |
Scale ceiling | Up to 64 vCPU bundles | Hundreds of instance types | Up to XLarge node, scaled by node count | Task-level |
Networking | Managed VPC, peering to default VPC | Your VPC, full control | Managed | Your VPC |
Auto scaling | None built in | ASG | Manual node count | Service auto scaling |
Best for | Sites, small APIs, side projects, dev boxes | Anything with a growth path | Small container apps | Production container platforms |
Use Lightsail for a WordPress or small web app that will stay small, a development or staging box, a personal project, a workload that serves a lot of bytes (the transfer allowance is where it beats EC2), or a team with no AWS expertise that needs a predictable bill. The AWS decision guide for Lightsail versus EC2 points the same direction: Lightsail for simplicity and predictable pricing, EC2 for flexibility, deep service integration and scaling.
Do not use it when you need auto scaling groups, instance profiles of the kind EC2 gives you (check the current docs for how a Lightsail instance gets AWS credentials, because the default answer is often long-lived access keys on the box), fine-grained VPC design, spot capacity, or tight integration with the rest of your AWS estate. Also do not use it as the default for anything you expect to grow into a real platform. The migration is possible, as the build below shows, but it is a project, not a button.
One note on documentation drift. The AWS decision guide says Lightsail tops out at 8 vCPUs, while the current pricing page lists bundles up to 64 vCPUs and 256 GB. The pricing page is the newer source, so I use it. This is exactly why you check the dated source before you decide.
What it costs
Instance bundles on the pricing page for Linux with a public IPv4 address start at $5 per month for 2 vCPUs, 0.5 GB RAM, 20 GB SSD and 1 TB of transfer. Then $7 (1 GB, 40 GB, 2 TB), $12 (2 GB, 60 GB, 3 TB), $24 (4 GB, 80 GB, 4 TB), $44 (8 GB, 160 GB, 5 TB), $84 (4 vCPU, 16 GB, 320 GB, 6 TB), and so on up to $1,764 for 64 vCPUs and 256 GB. IPv6-only bundles start at $3.50. Memory-optimized bundles run $74 to $2,344 and compute-optimized $42 to $1,688. Windows costs 50 to 90 percent more at comparable sizes.
The other products: container nodes from $7 (Nano, 0.25 shared vCPU, 512 MB) to $160 (XLarge, 4 vCPU, 8 GB) with 500 GB of transfer per service. Managed databases from $15 for 1 GB and 40 GB, with a high-availability plan at double the price. A load balancer is $18. CDN distributions are $2.50 for 50 GB, $10 for 200 GB and $35 for 500 GB. Additional block storage is $0.10 per GB-month. Snapshots are $0.05 per GB-month, and successive snapshots of the same instance only charge for changed data.
The thing that surprises people is that stopping does not stop the bill. The billing documentation is explicit: instances and managed databases incur charges until they are deleted, including while stopped. The second surprise is the static IP. It is free while attached, but an unattached static IP costs $0.005 per hour once it has been unattached for more than an hour, about $3.60 a month for a reservation you forgot. The third is transfer overage. When you exceed the bundle allowance you pay per GB, starting at $0.09 per GB and varying by Region. That sounds small until you do the arithmetic: 3 TB at $0.09 per GB is roughly $270, which is why the included allowance is the real product.
Free tier reality: the old short-term Lightsail free trial has been replaced by the AWS Free Tier credits (up to $200 according to the billing documentation), so a new account can try Lightsail without paying, but there is no always-free Lightsail instance.
Cost of following along: the build below uses the $5 bundle for under an hour, one snapshot of a 20 GB disk, and a static IP that is attached the whole time. Expect a few cents. The snapshot, if you forget it, tops out around $1 a month, and the export to EC2 creates an AMI and an EBS snapshot with their own small storage charge, which the cleanup section deletes.
The limits that bite
The default quotas from the AWS general reference: instances are limited by vCPU, 20 vCPUs per Region by default and adjustable. Static IPs are 5 per Region, adjustable. Block storage is 15 disks per instance, 20,000 GB total per Region, 16,000 GB max disk size, none adjustable. Load balancers are 10 per Region, databases 40 per Region, container services 100 per Region, distributions 20 per account, DNS zones 6 per account. Parallel SSH connections are limited to 5 per Region and RDP to 1.
Lightsail is available in 18 Regions. If your compliance team wants a Region that is not on the list, the conversation ends there.
The firewall limits matter more day to day. A console rule accepts up to 30 source IPs, the API and CLI accept up to 60. And the API call you will reach for first, PutInstancePublicPorts, replaces every rule on the instance. It closes everything not in your request, including the SSH rule you are using to log in. Use OpenInstancePublicPorts to add.
The CPU credit model is the failure mode at scale. A $5 plan has a 5 percent baseline per vCPU. A web server that idles at 2 percent accrues credit, and a nightly cron job that pegs both vCPUs drains it. The documentation I read covers accrual and persistence but is not explicit about what happens at zero, so do not guess: watch the BurstCapacityPercentage and BurstCapacityTime metrics and size the plan so you do not live near zero. Instances created on or after June 29, 2023 keep their accrued burst capacity for 7 days across stop and start. Older ones lose it.
Build it
The deliverable: a Lightsail web server with a static IP and a locked-down firewall, a snapshot, a read of its CPU burst metrics, and an export of that snapshot to Amazon EC2 so you have proven the exit path.
Prerequisites: Python 3.9+, boto3 installed, AWS credentials configured, and a Region that supports Lightsail (this uses us-east-1). The IAM permissions your user or role needs are the Lightsail actions below. Lightsail uses a service-linked role for the export, which it creates for you if the permission to do so is present.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"lightsail:GetBlueprints", "lightsail:GetBundles",
"lightsail:CreateInstances", "lightsail:GetInstance", "lightsail:GetInstances",
"lightsail:DeleteInstance", "lightsail:AllocateStaticIp",
"lightsail:AttachStaticIp", "lightsail:ReleaseStaticIp", "lightsail:GetStaticIp",
"lightsail:OpenInstancePublicPorts", "lightsail:PutInstancePublicPorts",
"lightsail:CreateInstanceSnapshot", "lightsail:GetInstanceSnapshot",
"lightsail:DeleteInstanceSnapshot", "lightsail:GetInstanceMetricData",
"lightsail:ExportSnapshot", "lightsail:GetExportSnapshotRecords",
"lightsail:GetOperation"
],
"Resource": "*"
}]
}
Step 1: discover the blueprint and bundle IDs instead of hardcoding them. IDs change as new OS versions ship, which is the exact kind of fact you should not take from an article.
import boto3, time, datetime as dt
REGION, AZ = "us-east-1", "us-east-1a"
NAME, IPNAME, SNAP = "sl110-web", "sl110-ip", "sl110-snap"
ls = boto3.client("lightsail", region_name=REGION)
blueprints = [b for b in ls.get_blueprints()["blueprints"]
if b["type"] == "os" and "ubuntu" in b["blueprintId"].lower() and b["isActive"]]
blueprint_id = sorted(blueprints, key=lambda b: b["blueprintId"])[-1]["blueprintId"]
bundles = [b for b in ls.get_bundles(includeInactive=False)["bundles"]
if "LINUX_UNIX" in b["supportedPlatforms"] and "ipv6" not in b["bundleId"]]
bundle = min(bundles, key=lambda b: b["price"])
print("blueprint:", blueprint_id)
print("bundle:", bundle["bundleId"], bundle["price"], "USD,", bundle["cpuCount"], "vCPU,",
bundle["ramSizeInGb"], "GB RAM,", bundle["transferPerMonthInGb"], "GB transfer")
Check that the printout shows a $5 or similar entry bundle. If it prints something else, the pricing has moved and you should trust the printout over this article.
Step 2: create the instance with a user-data script that installs nginx, then wait until it is running.
user_data = """#!/bin/bash
apt-get update -y && apt-get install -y nginx
echo '<h1>SL#110 Lightsail</h1>' > /var/www/html/index.html
"""
ls.create_instances(instanceNames=[NAME], availabilityZone=AZ,
blueprintId=blueprint_id, bundleId=bundle["bundleId"],
userData=user_data, ipAddressType="dualstack")
while ls.get_instance(instanceName=NAME)["instance"]["state"]["name"] != "running":
time.sleep(5)
print("running")
Step 3: allocate a static IP and attach it, so the address survives a stop and start.
ls.allocate_static_ip(staticIpName=IPNAME)
ls.attach_static_ip(staticIpName=IPNAME, instanceName=NAME)
ip = ls.get_static_ip(staticIpName=IPNAME)["staticIp"]["ipAddress"]
print("static ip:", ip)
Step 4: set the firewall. HTTP stays open to the world. SSH is restricted to your own address. Replace the placeholder CIDR with yours. Because PutInstancePublicPorts replaces everything, list every rule you want in one call, and do not forget SSH or you lock yourself out.
MY_CIDR = "203.0.113.10/32" # replace with your public IP
ls.put_instance_public_ports(instanceName=NAME, portInfos=[
{"fromPort": 80, "toPort": 80, "protocol": "tcp", "cidrs": ["0.0.0.0/0"], "ipv6Cidrs": ["::/0"]},
{"fromPort": 22, "toPort": 22, "protocol": "tcp", "cidrs": [MY_CIDR]},
])
Step 5: verify the server is serving.
import urllib.request
time.sleep(60) # user-data needs a minute to finish
print(urllib.request.urlopen(f"http://{ip}", timeout=10).read().decode())
You should see the SL#110 heading. If you do not, see the next section.
Step 6: read the burst metrics. This is the number that tells you whether the $5 bundle is the right size, and it is the part of the Lightsail contract that no dashboard on the instance page makes obvious.
end = dt.datetime.utcnow(); start = end - dt.timedelta(hours=1)
for metric, unit in [("BurstCapacityPercentage", "Percent"), ("BurstCapacityTime", "Seconds"),
("CPUUtilization", "Percent")]:
pts = ls.get_instance_metric_data(instanceName=NAME, metricName=metric, period=300,
startTime=start, endTime=end, unit=unit, statistics=["Average"])["metricData"]
print(metric, [round(p["average"], 1) for p in sorted(pts, key=lambda p: p["timestamp"])])
A freshly created instance may show few data points. The point is to learn the habit: before you commit to a bundle for a real workload, run it and read BurstCapacityPercentage. If it trends toward zero under normal load, move up a plan.
Step 7: snapshot, then export to EC2. The snapshot is a point-in-time copy of the instance. The export turns it into an AMI plus an EBS snapshot in the same Region, with new identifiers. This is the exit path, and you are running it on purpose while nothing depends on it.
ls.create_instance_snapshot(instanceSnapshotName=SNAP, instanceName=NAME)
while ls.get_instance_snapshot(instanceSnapshotName=SNAP)["instanceSnapshot"]["state"] != "available":
time.sleep(10)
ls.export_snapshot(sourceSnapshotName=SNAP)
for _ in range(60):
recs = ls.get_export_snapshot_records()["exportSnapshotRecords"]
mine = [r for r in recs if r["sourceInfo"]["name"] == SNAP]
if mine and mine[0]["state"] in ("Succeeded", "Failed"):
break
time.sleep(20)
print(mine[0]["state"], mine[0].get("destinationInfo"))
The destinationInfo shows the AMI and EBS snapshot IDs that now exist in EC2. From there you launch a real EC2 instance from the AMI. The AWS documentation lists the supported destination types through Lightsail as C5, R5, T3 and M5, with the caveat that some older Lightsail instances lack enhanced networking and need a previous-generation type (T2, M4, C4, R4) first. Exports stay in the same Region, so to land in another Region you copy the snapshot there first. The documentation also warns that residual Lightsail SSH keys can remain on a migrated Linux instance, so remove them after the move. That checklist is the migration, and now you know it works for your image.
When it breaks
The page does not load after Step 5. Almost always the user-data script has not finished (give it two minutes), or the HTTP rule was replaced by a later PutInstancePublicPorts call that did not include port 80. Check the firewall with get_instance_port_states.
You cannot SSH in after Step 4. You replaced the rules and your current IP is not in MY_CIDR, or it changed. Open port 22 to your new address with OpenInstancePublicPorts, or use the browser-based SSH from the console, which uses the lightsail-connect alias.
The static IP allocation fails with a limit error. You hit the 5-per-Region quota. Release an unused one or request an increase in Service Quotas.
The instance feels slow under sustained load though the CPU graph looks modest. The console averages CPU across vCPUs. On a multi-vCPU plan one pegged core and one idle core average to 50 percent. Look at BurstCapacityPercentage, not just CPUUtilization.
The export fails or the resulting EC2 instance will not start on T3. It is the enhanced networking issue: launch on T2 first, update the network driver, then change the type. cPanel and WHM on CentOS 7 images cannot be exported at all, per the docs.
Your bill is not zero after you thought you stopped everything. Stopped instances and databases keep billing, unattached static IPs bill hourly after the first hour, and snapshots bill per GB-month. All three show up in the cleanup.
Cleanup
Delete in this order so nothing is left holding a charge.
ls.release_static_ip(staticIpName=IPNAME) # releasing frees it; attached IPs are free, but we delete the instance next
ls.delete_instance(instanceName=NAME)
ls.delete_instance_snapshot(instanceSnapshotName=SNAP)
Then clean up the exported EC2 artifacts, which live in EC2 and not in Lightsail. In the EC2 console under AMIs, deregister the AMI that the export created, then under Snapshots delete the EBS snapshot it was built from. Look for the IDs in the destinationInfo you printed in Step 7. Finally confirm: run get_instances, get_static_ips and get_instance_snapshots and check all three return empty lists, and check the Billing console after a day to see that no Lightsail line items are accruing.
Lightsail rewards you for knowing the shape of the package before you buy it. It is the right answer more often than AWS purists admit and a trap less often than its critics claim, and the only thing separating the two cases is whether you tested the exit before you needed it.

