Migrate On-Premises Compute to AWS

Replatform a single on-prem server into a resilient ALB and Auto Scaling architecture.

Introduction

30 Second Summary

When a physical server crashes, your application goes with it. There is no backup machine waiting in the wings. There is no automatic recovery. Just downtime until someone manually fixes the problem.

In this project, you will migrate a fragile single-server application to a resilient AWS architecture that automatically distributes traffic, replaces failed servers, and maintains a stable endpoint for your users.

What You'll Build

You will have a single URL that serves your web app from multiple servers across different data centres. When you terminate one of those servers, your app keeps running without a blip, and a fresh replacement spins up on its own.

By the end of this project, you'll have:

  • A live web app where you refresh the page and see different server hostnames appear each time, proving traffic is balanced across multiple instances in different Availability Zones.
  • A self-healing architecture where you terminate a server and watch it get replaced automatically with zero downtime. The same failure that took your app offline before now has no visible impact.
  • A golden AMI that stamps out identical servers on demand, replacing the manual "build each server by hand" process that plagues on-premises environments.
  • Secret Mission: elastic scaling that adds server capacity under load in minutes, something physically impossible with on-premises hardware.

Are there any prerequisites?

You need an AWS account that is Free Tier eligible. You should be comfortable navigating the AWS Management Console (finding services, clicking through settings).

No local tools are required. All work happens in the AWS Console and EC2 Instance Connect (a browser-based terminal).

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

A typical on-premises setup runs the app, database, and static files on a single server. Migrating to AWS means decomposing that monolith into separate, resilient services.

Simulate the On-Premises Server

Before you can migrate anything to the cloud, you need something to migrate. In this step, you'll build a simple web application on a single EC2 instance that represents your on-premises server.

This instance mirrors the reality of on-premises environments. One server handles everything, and if it goes down, the application disappears with no automatic recovery.

In this step, get ready to:
  • Launch a t2.micro EC2 instance running Amazon Linux 2023.
  • Deploy a Flask web application as a systemd service.
  • Verify the application is accessible from your browser.
Launch the on-premises EC2 instance

This instance represents the physical server in your data centre. You'll launch it with a security group that opens the ports your application needs.

  • In the AWS Management Console, type EC2 in the top search bar and select EC2.
  • Click Launch instance from the EC2 dashboard.
  • Under Name and tags, enter onprem-server for Name.
  • Under Application and OS Images (Amazon Machine Image), choose Quick Start. Select Amazon Linux and confirm Amazon Linux 2023 AMI is selected.
  • Under Instance type, select t2.micro (Free Tier eligible).
  • Under Key pair (login), select Proceed without a key pair (Not recommended). You'll use EC2 Instance Connect instead, which handles authentication for you.
  • Under Network settings, click Edit.
  • Confirm Auto-assign public IP is set to Enable.
  • Under Firewall (security groups), select Create security group. The first rule for SSH (port 22) is already added.
  • Click Add security group rule. Set Type to Custom TCP, Port range to 5000, and Source type to Anywhere.
  • In the Summary panel on the right, click Launch instance.

A success banner confirms the launch. Click View all instances and wait until Instance state shows Running and Status check shows 2/2 checks passed.

Why no key pair?

Amazon Linux 2023 comes pre-installed with EC2 Instance Connect. This feature pushes a temporary SSH key to the instance metadata each time you connect, so you never need to manage .pem files locally.

Instance stuck in Pending?

It typically takes 1-2 minutes for the instance to pass both status checks. If it stays in Pending for more than 5 minutes, check that you selected t2.micro as the instance type. Some regions may require t3.micro instead if t2.micro is unavailable.

Help me troubleshoot my EC2 instance launch

Connect and deploy the application

With the instance running, you'll connect directly from the browser and install the Flask application that simulates your on-premises workload.

  • Select your onprem-server instance in the Instances list.
  • Click Connect at the top of the page.
  • Choose the EC2 Instance Connect tab, then click Connect.

A browser-based terminal opens. You are now logged into your EC2 instance as ec2-user.

  • Update the system packages and install Python with pip by running these commands:
sudo dnf update -y
sudo dnf install -y python3 python3-pip
  • Install Flask by running:
sudo pip3 install flask

What do these commands do?

  • sudo dnf update -y updates all system packages to their latest versions. Amazon Linux 2023 uses dnf as its package manager.
  • sudo dnf install -y python3 python3-pip installs Python 3 and its package installer.
  • sudo pip3 install flask installs the Flask web framework globally so your application can use it.
  • Create the Flask application file by running this command:
cat > /home/ec2-user/app.py << 'EOF'
import socket
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    hostname = socket.gethostname()
    return f"""
    <html>
    <head><title>On-Prem Migration Demo</title></head>
    <body style="font-family: Arial, sans-serif; text-align: center; padding-top: 50px;">
        <h1>On-Premises to AWS Migration</h1>
        <p style="font-size: 1.5em;">Served by: <strong>{hostname}</strong></p>
        <p style="color: gray;">Refresh to see load balancing in action</p>
    </body>
    </html>
    """

@app.route("/health")
def health():
    return "ok", 200

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
EOF

What does this application do?

This is a minimal Flask web app that serves a single HTML page. It uses socket.gethostname() to display the server's hostname on the page. Later, when you have multiple instances behind a load balancer, each instance will show a different hostname so you can visually confirm traffic distribution.

The /health endpoint returns a simple 200 OK response. The load balancer will use this later to check if the instance is healthy.

  • Create the systemd service file and start the application by running:
sudo tee /etc/systemd/system/flaskapp.service > /dev/null << 'EOF'
[Unit]
Description=Flask On-Prem Migration App
After=network.target

[Service]
User=ec2-user
WorkingDirectory=/home/ec2-user
ExecStart=/usr/local/bin/python3 /home/ec2-user/app.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable --now flaskapp

What does the systemd service do?

A systemd service turns your Python script into a managed background process. The Restart=always directive means if the process crashes, systemd restarts it automatically.

The enable --now flag both starts the service immediately and ensures it starts on boot.

  • Verify the service is running:
sudo systemctl status flaskapp

You should see Active: active (running) in the output. This confirms your application is serving on port 5000.

Seeing 'Active: failed'?

Check that the heredoc commands pasted correctly. Run cat /home/ec2-user/app.py to verify app.py has content. If the file is empty, paste the cat > /home/ec2-user/app.py command again.

Also confirm Flask installed successfully by running python3 -c "import flask". If you see an error, re-run sudo pip3 install flask.

Help me fix my flaskapp systemd service

Verify the application from your browser

The application is running inside the instance. Now you need to access it from outside, just like a user would access your on-premises server.

  • Go back to the EC2 Instances page in the console.
  • Select your onprem-server instance and find the Public IPv4 address in the Details tab below. Copy it.

Before you open this URL, what do you expect to see? The app returns an HTML page that displays the server's hostname.

  • Open a new browser tab and navigate to http:// followed by your public IP, then :5000 (for example, http://54.123.45.67:5000).

You should see a page titled "On-Premises to AWS Migration" with the text "Served by:" followed by a hostname. This is your on-premises baseline: one server, one IP, one point of failure.

Page not loading?

Make sure you are using http:// and not https://. The app does not have SSL configured.

Confirm port 5000 is open in your security group. In the EC2 console, select your instance, click the Security tab, then click the security group link. Verify there is an inbound rule for TCP port 5000 with source 0.0.0.0/0.

Help me access my Flask app from the browser

✔️ Awesome, I've got everything!

Great. Make sure you have the browser tab with your running app open. You'll need it in the next step.

ⓧ I'd like to double check the full code

Here is the complete app.py that should be at /home/ec2-user/app.py:

import socket
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    hostname = socket.gethostname()
    return f"""
    <html>
    <head><title>On-Prem Migration Demo</title></head>
    <body style="font-family: Arial, sans-serif; text-align: center; padding-top: 50px;">
        <h1>On-Premises to AWS Migration</h1>
        <p style="font-size: 1.5em;">Served by: <strong>{hostname}</strong></p>
        <p style="color: gray;">Refresh to see load balancing in action</p>
    </body>
    </html>
    """

@app.route("/health")
def health():
    return "ok", 200

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

And here is flaskapp.service at /etc/systemd/system/flaskapp.service:

[Unit]
Description=Flask On-Prem Migration App
After=network.target

[Service]
User=ec2-user
WorkingDirectory=/home/ec2-user
ExecStart=/usr/local/bin/python3 /home/ec2-user/app.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

You can verify these on your instance by running cat /home/ec2-user/app.py and cat /etc/systemd/system/flaskapp.service.

Your on-premises server is live. It works, but it has a fatal flaw: if this one instance goes down, the entire application disappears. Next up, you'll experience that failure firsthand.

Experience the On-Premises Failure Modes

Your Flask app is live and serving requests from a single EC2 instance. It works perfectly right now.

But what happens when that server goes down? On-premises servers fail. Hard drives die, power goes out, networks drop. When they do, the application goes offline and there is no automatic recovery. In this step, you will simulate that failure firsthand and discover exactly why a cloud migration is necessary.

Prepare the Migration Image

In the last step, you experienced the two pain points of on-premises infrastructure firsthand: total downtime on failure and an unstable IP address. Now you know exactly what the cloud migration needs to solve.

The first step of any migration is eliminating the "snowflake server" problem. In on-premises environments, every server is hand-built, making it impossible to quickly spin up an identical replacement. You will capture your working application into a reusable image that can stamp out identical servers on demand.

In this step, get ready to:
  • Create a golden AMI that captures your entire application into a reusable image.
  • Create a Launch Template that automates server provisioning from that image.
Create a golden AMI from your running instance

A golden AMI is a pre-configured machine image that packages your operating system, installed software, application code, and service configuration into a single reusable template. Instead of manually rebuilding a server from scratch each time, you launch a new instance from the AMI and it comes up ready to serve traffic.

  • Return to the EC2 console in your browser.
  • In the left navigation pane, click Instances.
  • Select the checkbox next to your running on-premises simulation instance.
  • Click Actions in the top menu bar.
  • Select Image and templates, then click Create image.
  • In the Image name field, enter onprem-migration-ami.
  • Leave Reboot instance checked (this ensures a consistent image).
  • Click Create image.

What does creating an image do?

When you create an AMI, AWS takes a snapshot of the instance's entire disk. This captures everything: the Amazon Linux 2023 OS, Python 3, Flask, your app.py file, and the flaskapp.service systemd configuration.

Any new instance launched from this AMI will be an exact clone, ready to serve traffic without any manual setup. This is the foundation of automated provisioning in the cloud.

The AMI takes 2-3 minutes to become available. Let's verify it is ready before moving on.

  • In the left navigation pane of the EC2 console, click AMIs.
  • From the filter bar, choose Owned by me.
  • Look for onprem-migration-ami in the list. Check the Status column.

What do you expect the status to show once the image is fully created?

You should see the status available. If it still says pending, wait another minute and refresh the page.

AMI stuck in pending?

AMI creation typically completes within 2-3 minutes. If the status remains pending for more than 5 minutes, refresh the page. The status column does not auto-update.

If the status shows failed, check that your instance was running (not stopped) when you created the image, then try again.

Help me troubleshoot my AMI creation

Create a Launch Template using your golden AMI

A Launch Template is a configuration blueprint that tells AWS exactly how to create new instances from your AMI. It specifies the instance type, security group rules, and networking settings so every new server is identical without manual decisions.

  • In the left navigation pane of the EC2 console, under Instances, click Launch Templates.
  • Click Create launch template.
  • In the Launch template name field, enter migration-lt.
  • Under Application and OS Images (Amazon Machine Image), click My AMIs.
  • Select onprem-migration-ami from the list.
  • Under Instance type, select t2.micro.

Now you need to configure a security group that allows traffic on the ports your application uses.

  • Scroll down to Network settings.
  • Under Firewall (security groups), select Create security group.
  • Click Add security group rule.
  • For the first rule: set Type to HTTP and Source type to Anywhere.
  • Click Add security group rule again.
  • For the second rule: set Type to Custom TCP, Port range to 5000, and Source type to Anywhere.
  • Click Create launch template.

You should see a success banner confirming the launch template was created.

Why HTTP on port 80 and Custom TCP on port 5000?

Port 80 is the standard HTTP port that the Application Load Balancer will use to receive traffic from users. Port 5000 is where your Flask app listens.

In the next step, the ALB will accept requests on port 80 and forward them to port 5000 on your instances. Both ports need to be open in the security group for traffic to flow end to end.

Launch template creation failed?

Make sure you selected your custom AMI (onprem-migration-ami) under My AMIs, not an Amazon-provided image from the Quick Start tab.

If you do not see your AMI in the list, confirm it shows status available on the AMIs page. An AMI that is still pending cannot be used in a Launch Template.

Help me fix my Launch Template creation

You now have an automated way to produce identical servers on demand. This replaces the on-premises process of manually building and configuring each new server. Next up, you will wire these pieces together into a cloud-native architecture with a load balancer and auto scaling group that finally solves both on-premises pain points.

Build the Cloud-Native Architecture

In the previous step, you captured your application into a golden AMI and created a Launch Template. You now have an automated way to produce identical servers on demand.

This step solves the two on-premises pain points you experienced earlier: total downtime on failure, and an unstable IP address. You will wire together three AWS resources that eliminate both problems at once.

In this step, get ready to:
  • Create a Target Group that tells the load balancer where to send traffic.
  • Create an Application Load Balancer that gives your app a single, stable DNS endpoint.
  • Create an Auto Scaling Group that maintains a fleet of healthy instances and replaces any that fail.
Create a Target Group

A Target Group defines the pool of instances that receive traffic from your load balancer. It also runs health checks against those instances so unhealthy ones get removed automatically.

  • In the EC2 console left sidebar, under Load Balancing, click Target Groups.
  • Click Create target group.
  • For Choose a target type, select Instances.
  • Enter migration-tg in the Target group name field.
  • Set Protocol to HTTP.
  • Set Port to 5000.
  • In the Health checks section, set the Health check path to /.
  • Click Next.

Why skip registering targets?

On the Register targets page, you will see an empty list. Leave it empty. The Auto Scaling Group will automatically register its instances with this target group when it launches them.

  • Click Create target group without adding any targets to the list.

You should see migration-tg appear in your Target Groups list with a status of None associated (no load balancer linked yet).

Don't see your target group?

Make sure you clicked Create target group on the final page (not just Next). Check that your target group name uses only lowercase letters, numbers, and hyphens.

Help me troubleshoot this issue

Create an Application Load Balancer

The Application Load Balancer gives your app a single, stable DNS name. Clients connect to this one address, and the ALB distributes their requests across every healthy instance in the target group.

  • In the left sidebar, click Load Balancers.
  • Click Create load balancer.
  • Under Application Load Balancer, click Create.
  • Enter migration-alb in the Load balancer name field.
  • For Scheme, select Internet-facing.
  • Under Network mapping, select your default VPC.
  • Select at least 2 Availability Zones and their corresponding subnets.

Why at least 2 Availability Zones?

An ALB requires at least two public subnets in different Availability Zones. This ensures that even if one data center has an outage, the ALB can still route traffic to instances in the other zone.

  • Under Security groups, remove the default security group.
  • Choose create a new security group to open the security group creation page.
  • Give the security group a name (such as migration-alb-sg) and a description.
  • Add an inbound rule with Type HTTP, Port 80, and Source Anywhere-IPv4 (0.0.0.0/0).
  • Click Create security group, then return to the ALB creation tab.
  • Select your new security group (migration-alb-sg) in the list.
  • Under Listeners and routing, confirm the listener is set to HTTP port 80.
  • For Default action, select migration-tg from the target group dropdown.
  • Click Create load balancer.

You should see a success banner and your ALB state will show Provisioning. It takes 2-3 minutes to become Active.

ALB stuck in Provisioning?

This is normal. Wait 2-3 minutes and refresh the page. If it stays in Provisioning for more than 5 minutes, check that your selected subnets are in different Availability Zones and that your VPC has an internet gateway attached.

Help me troubleshoot my ALB

Create an Auto Scaling Group and verify

The Auto Scaling Group is the engine that keeps your fleet alive. It launches instances using your Launch Template and automatically registers them with the target group.

  • In the left sidebar, click Auto Scaling Groups.
  • Click Create Auto Scaling group.
  • Enter migration-asg as the Auto Scaling group name.
  • Select migration-lt as the Launch template.
  • Click Next.
  • Under Network, select your default VPC.
  • For Availability Zones and subnets, choose at least 2 subnets in different Availability Zones (the same ones you used for the ALB).
  • Click Next to reach the Configure advanced options page.

What is load balancer attachment?

When you attach an Auto Scaling Group to a target group, every instance the ASG launches gets automatically registered with the load balancer. Likewise, every instance the ASG terminates gets automatically deregistered. You never manually manage the target list.

  • Under Load balancing, choose Attach to an existing load balancer.
  • Select Choose from your load balancer target groups.
  • Choose migration-tg from the list.
  • Under Additional health check types, select Turn on Elastic Load Balancing health checks.
  • Click Next to reach the Configure group size and scaling page.
  • Set Desired capacity to 2.
  • Set Min desired capacity to 2.
  • Set Max desired capacity to 4.

Why Desired 2, Min 2, Max 4?

Desired 2 tells the ASG to launch 2 instances immediately. Min 2 means the ASG will never let the fleet drop below 2 healthy instances. Max 4 allows room for scaling out under load (you will test this in the Secret Mission).

  • Click Skip to review.
  • Review your settings, then click Create Auto Scaling group.

The ASG immediately begins launching 2 instances. Wait 2-3 minutes for them to pass their health checks.

  • Navigate to Load Balancers in the left sidebar.
  • Select migration-alb and copy the DNS name from the Description tab.

Before you open this URL, think about what you expect to see. When you refresh the page multiple times, will the hostname stay the same, or change?

  • Paste the ALB DNS name into your browser's address bar and press Enter.

You should see "Served by:" followed by a hostname. Refresh the page several times. Different hostnames appear, proving traffic is distributed across multiple servers in different Availability Zones.

What does this prove?

Your clients now connect to one stable DNS name instead of a raw IP address. Behind that name, the ALB distributes every request to a healthy instance. If one instance fails, the ALB stops sending traffic to it, and the ASG launches a replacement.

Both on-premises pain points are solved: stable endpoint (no more changing IPs) and automatic recovery (no more manual restarts).

Seeing a 502 or 503 error?

This usually means the instances haven't finished registering or passing health checks yet. Wait another 1-2 minutes and refresh.

If the error persists, check the Target Groups page. Click migration-tg and open the Targets tab. Your instances should show a health status of healthy. If they show unhealthy, verify that the Launch Template's security group allows inbound traffic on port 5000.

Help me fix my ALB 502 error

Your cloud-native architecture is live. Next up, you will put it to the ultimate test: terminate an instance and watch the system heal itself without any downtime.

Validate the Migration

Your cloud-native architecture is live and distributing traffic across multiple servers. The real test of any migration is not whether it works when everything is fine. It's whether it survives failure.

In Step 2, stopping a single instance meant total downtime and a changed IP address. Now you'll inflict the same failure on your new architecture and prove it delivers what on-premises never could: automatic recovery with zero downtime and a stable endpoint.

In this step, get ready to:
  • Terminate an instance to simulate the same hardware failure from Step 2.
  • Confirm the ALB continues serving traffic with zero downtime during the failure.
  • Observe the ASG automatically replacing the failed instance and restoring full capacity.
Simulate a hardware failure

You are about to do the same thing that caused total downtime in Step 2: kill a server. This time, the Application Load Balancer and Auto Scaling Group should handle it without breaking a sweat.

  • Open the ALB DNS name in a browser tab and keep it open. You should see the familiar "Served by: <hostname>" page.
  • In the AWS Console, navigate to EC2 > Instances.
  • Identify one of the two instances launched by your ASG (they will have names like migration-asg or be unnamed but running in your ASG subnets).
  • Select that instance by clicking its checkbox.
  • Click Instance state > Terminate instance.
  • Confirm the termination when prompted.

What does this simulate?

This is the exact same scenario as Step 2: a server dying. In on-premises terms, this is a hard drive failure, a power outage, or a crashed OS. The only difference is what happens next.

Before you refresh your ALB browser tab, make a prediction. What do you think will happen? Will the app go down like it did in Step 2, or will something different happen this time?

Confirm zero-downtime recovery
  • Switch to the browser tab with your ALB DNS name.
  • Refresh the page repeatedly.

The app continues responding. You see "Served by: <hostname>" from the surviving instance. No downtime. No changed URL. The ALB simply routed around the failed instance.

Why is this different from Step 2?

In Step 2, your single server was the only thing between users and the app. When it died, users hit a dead IP address. Now the ALB is the entry point, and it only sends traffic to healthy instances.

With one instance terminated, the ALB's health check detects the failure and stops routing traffic there. The remaining instance handles all requests seamlessly.

Now check that the ASG is already working to restore full capacity.

  • In the AWS Console, navigate to EC2 > Auto Scaling Groups.
  • Click on migration-asg.
  • Select the Activity tab.

Within 1-3 minutes of termination, you should see an activity entry showing a new instance launching. The ASG detected that the actual count (1) dropped below the desired count (2) and is automatically launching a replacement.

Don't see a new instance launching?

The ASG can take 1-3 minutes to detect the terminated instance and begin launching a replacement. Refresh the Activity tab after waiting.

If no activity appears after 5 minutes, check that ELB health checks are enabled on your ASG. Select the Health checks section and confirm Elastic Load Balancing health checks is turned on.

Help me troubleshoot why my ASG is not replacing a terminated instance.

Verify the fleet self-healed

The replacement instance needs a couple of minutes to boot, run the Flask app from the golden AMI, and pass its health check. Once it does, your fleet is back to full strength.

  • Wait 2-3 minutes for the new instance to become healthy.
  • Navigate to EC2 > Target Groups and click on migration-tg.
  • Select the Targets tab and confirm 2 targets show a status of healthy.

Now make one final prediction. When you refresh the ALB URL, how many different hostnames do you expect to see?

  • Switch to your ALB browser tab and refresh the page several times.

You see two different hostnames appearing as you refresh. The fleet has fully self-healed. No manual restart. No changed IP. No downtime for your users.

Compare this to Step 2

In Step 2, the same failure required you to manually start the instance, wait for it to boot, then discover the public IP had changed. Every second was downtime.

Here, the ALB gave your users a stable DNS endpoint that never changed. The ASG detected the failure and launched a replacement without any human intervention. This is the core value of the migration: your architecture now handles failures that would have paged an engineer at 3am.

What about the original on-premises instance?

The original EC2 instance from Step 1 (the one on port 5000) is still running. In a real migration, you would decommission the old server once the new architecture is validated. You can leave it for now and clean it up in the Before You Go section.

Secret mission

Add Elastic Scaling (Impossible On-Premises)

Your architecture self-heals, but can it grow on demand? In this challenge, you will configure a CPU-based scaling policy and generate artificial load to watch your fleet expand from 2 to 3+ instances in minutes. This is elastic capacity that on-premises hardware physically cannot provide.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses AWS Free Tier eligible resources (750 ALB hours/month and 750 EC2 t2.micro hours/month for 12 months), but leaving them running counts against your Free Tier limits.

Cost warning

If you are outside the 12-month Free Tier window, or if you have other ALBs or EC2 instances running that consume your free hours, these resources will incur charges. The ALB costs $0.0225 per hour and each t2.micro instance costs roughly $0.0116 per hour outside Free Tier.

If you are not continuing to Project 2 (database migration), delete all resources to avoid unexpected charges.

Resources you used:

  • 1 Application Load Balancer (migration-alb).
  • 2-4 EC2 instances launched by the Auto Scaling Group (migration-asg).
  • 1 original on-premises simulation EC2 instance.
  • 1 AMI (onprem-migration-ami) and its associated EBS snapshot.
  • 1 Launch Template (migration-lt).
  • 1 Target Group (migration-tg).
  • Security groups created during the project.

Keep everything running

No action needed. Choose this if you are still actively building or plan to continue with Project 2 (database migration).

  • Your ALB and ASG instances will stay within Free Tier if you have no other ALBs or EC2 instances consuming your 750 free hours.
  • Monitor your usage in the AWS Billing Dashboard to make sure you stay within free limits.
  • The original on-premises simulation instance is still running and consuming Free Tier hours. Consider terminating it since the migration is complete.

Pause - I'll come back to this later

Shut down running instances to free up Free Tier hours while keeping your configuration intact.

  • Go to EC2 > Auto Scaling Groups. Select migration-asg.
  • Click Edit in the Group details section. Set Desired capacity, Minimum capacity, and Maximum capacity all to 0. Click Update.
  • Go to EC2 > Instances. Select the original on-premises simulation instance. Click Instance state > Stop instance.
  • The ALB will remain active but serve no traffic. It still counts against your 750 free ALB hours per month.
  • To resume later, edit the ASG and set Desired and Minimum back to 2 and Maximum back to 4. Start your on-premises instance if needed.

Delete - I don't want to use this again

Remove all project resources completely. Follow these steps in order because some resources depend on others.

  • Go to EC2 > Auto Scaling Groups. Select migration-asg. Click Delete. In the confirmation dialog, type delete to confirm. This terminates all ASG-managed instances.
  • Go to EC2 > Load Balancers. Select migration-alb. Click Actions > Delete load balancer. Type confirm in the dialog and click Delete.
  • Go to EC2 > Target Groups. Select migration-tg. Click Actions > Delete. Confirm the deletion.
  • Go to EC2 > Launch Templates. Select migration-lt. Click Actions > Delete template. Type Delete in the confirmation field and click Delete.
  • Go to EC2 > AMIs (under Images). Select onprem-migration-ami. Click Actions > Deregister AMI. Confirm the deregistration.
  • Go to EC2 > Snapshots (under Elastic Block Store). Find the snapshot associated with onprem-migration-ami (check the Description column). Select it, click Actions > Delete snapshot, and confirm.
  • Go to EC2 > Instances. Select the original on-premises simulation instance. Click Instance state > Terminate instance. Confirm the termination.
  • Go to EC2 > Security Groups. Delete the security groups you created for this project (the ones allowing port 5000, port 80, and SSH). You cannot delete a security group still in use, so wait until all instances and the ALB are fully terminated first.

That's a Wrap!

That's a Wrap!

Nice work! You just migrated a fragile on-premises-style application to a production-grade AWS architecture with built-in resilience and elasticity.

You've learned how to:

  • Simulated an on-premises server and experienced its failure modes firsthand: total downtime and unstable IP addresses.
  • Created a golden AMI to capture the application into a reusable image, eliminating the "snowflake server" problem common in on-premises environments.
  • Migrated the compute layer to a cloud-native architecture with an Application Load Balancer and Auto Scaling Group across multiple Availability Zones, then validated self-healing by proving the same failure that caused total downtime on-premises now results in zero downtime in the cloud.
  • Secret Mission: Demonstrated elastic scaling by adding capacity in minutes under load. This is something physically impossible with on-premises hardware.

Ready to quiz yourself?