Migrate a Monolithic App with AWS DMS
Migrate a Flask app from EC2-hosted MySQL to Amazon RDS using AWS DMS.
Introduction
30 Second Summary
Every company running its own database server eventually hits the same wall: the app and the database live on the same machine, so a crash takes down everything at once. Moving the database to a managed service is the single most common migration pattern in cloud architecture.
In this project, you will migrate a running web application from a simulated on-premises monolith to a properly architected AWS setup using AWS Database Migration Service.
What You'll Build
You'll hit a product catalog API with curl, watch DMS replicate every row into a managed database, then flip the connection string and prove the app keeps serving data without missing a beat.
By the end of this project, you'll have:
- A running Flask API on EC2 that you can query with curl and see live product data returned as JSON.
- A fully migrated MySQL database on Amazon RDS that you verified by querying it directly and comparing row counts against the original.
- A complete DMS migration pipeline (replication instance, source endpoint, target endpoint, migration task) visible in your AWS console that you can screenshot and explain in interviews.
- Secret Mission: Enable Change Data Capture to continuously replicate new writes during migration, proving zero-data-loss cutover skills.
Are there any prerequisites?
You'll need an AWS account with free tier active and the AWS CLI installed and configured on your machine. A basic comfort level with the terminal is helpful, but every command is provided for you.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. Understanding the migration scenario upfront will guide every decision you make in the steps ahead.
Launch and Configure the Monolith
Every cloud migration starts with a source environment. In this project, that source is a single EC2 instance running both a Flask application and a MySQL database on the same box.
This is the classic monolith pattern. One server does everything. Your job over the next few steps is to decompose it by migrating the database to a managed service. First, you need to build the monolith so you have something to migrate.
In this step, get ready to:
- Launch an EC2 instance running Amazon Linux 2023 with SSH access configured.
- Install MySQL 8.0 and Python 3 on the instance.
- Seed a product database with 10 sample records and a dedicated migration user.
Launch a t3.micro EC2 instance
You need a virtual server in AWS to act as your simulated on-premises monolith. You will launch it through the AWS Management Console.
- Open the AWS Management Console at https://console.aws.amazon.com/ec2/.
- From the navigation bar at the top, confirm you are in your preferred region (e.g. us-east-1).
- Click Launch instance on the EC2 dashboard.
- Under Name and tags, enter migration-monolith.
- Under Application and OS Images (Amazon Machine Image), choose Quick Start, then select Amazon Linux. Confirm the AMI shows Amazon Linux 2023.
- Under Instance type, select t3.micro (Free Tier eligible).
- Under Key pair (login), click Create new key pair.
- Name it migration-key, keep the type as RSA and format as .pem, then click Create key pair.
- Your browser downloads migration-key.pem automatically. Save it somewhere you can find it (your Downloads folder is fine for now).
- Under Network settings, click Edit.
- Keep Auto-assign public IP set to Enable.
- Under Firewall (security groups), select Create security group. Name it migration-monolith-sg.
- Confirm the default rule allows SSH on port 22 with source set to My IP.
- Leave all other settings at their defaults and click Launch instance.
Wait for the instance state to show Running in the EC2 console. This takes about 30 seconds.
What is an EC2 instance?
An EC2 instance is a virtual server in AWS. You choose the operating system (AMI), the hardware size (instance type), and the network rules (security group). The key pair lets you prove your identity when connecting over SSH.
Instance stuck in Pending?
EC2 instances typically start within 60 seconds. If yours stays in Pending for more than 2 minutes, check that you selected t3.micro and not a larger instance type that may have capacity limits in your region.
Help me troubleshoot my EC2 instance not launching.
SSH into the instance and install MySQL and Python
With the instance running, you can now connect to it and install the software the monolith needs. Amazon Linux 2023 already includes Python 3.9, but you need to add pip and MySQL separately.
- In the EC2 console, select your migration-monolith instance and click Connect.
- Select the SSH client tab. Copy the example SSH command shown at the bottom.
- Open Terminal on your Mac by pressing Cmd+Space and typing Terminal.
- Navigate to your Downloads folder (or wherever you saved the key) by running:
cd ~/Downloads
- Set the correct permissions on your key file by running:
chmod 400 migration-key.pem
- Connect to your instance by running the SSH command from the console. It looks like this:
ssh -i "migration-key.pem" ec2-user@[[EC2_PUBLIC_DNS="your EC2 Public IPv4 DNS from the console"]]
- When prompted "Are you sure you want to continue connecting?", type yes and press Enter.
You are now inside the EC2 instance. You should see a prompt starting with [ec2-user@...].
Permission denied or connection timeout?
If you see "Permission denied (publickey)", double-check that you ran chmod 400 migration-key.pem and that the key file name matches exactly.
If the connection times out, return to the EC2 console, select your instance, click the Security tab, and confirm the security group has an inbound rule for SSH (port 22) with your current IP address as the source.
Help me debug my SSH connection to EC2.
Now install MySQL 8.0. Amazon Linux 2023 does not include MySQL in its default repositories, so you need to add the official MySQL community repository first.
- Add the MySQL community repository and import its GPG key by running:
sudo dnf install -y https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
- Install MySQL Community Server by running:
sudo dnf install mysql-community-server -y
Why a separate repository?
Amazon Linux 2023 ships with MariaDB in its default package list. To get the official MySQL 8.0 Community Server (which is what DMS expects for binary log replication later), you add MySQL's own yum repository. The GPG key import verifies that the packages are authentic.
- Start MySQL and enable it to start automatically on boot by running:
sudo systemctl start mysqld
sudo systemctl enable mysqld
- Install pip for Python 3 by running:
sudo dnf install python3-pip -y
Before you move on, predict what you will see when you check the MySQL version. What command would confirm it is installed?
- Verify MySQL is installed and responding by running:
mysql --version
You should see output like mysql Ver 8.0.x confirming MySQL 8.0 is ready.
Seeing "command not found"?
If mysql --version returns "command not found", the install may have failed silently. Re-run sudo dnf install mysql-community-server -y and watch for any errors about GPG keys or missing repositories.
Help me fix MySQL not installing on Amazon Linux 2023.
Create the product database and migration user
The monolith needs data to serve. You will create a database called productdb with a product catalog table, seed it with 10 records, and create a dedicated MySQL user that AWS DMS will use later to read from this database.
MySQL 8.0 generates a temporary root password on first start. You need to retrieve it before you can log in.
- Retrieve the temporary root password by running:
sudo grep 'temporary password' /var/log/mysqld.log
Copy the password shown at the end of the output line. You will paste it in the next command.
- Secure the MySQL installation and set a new root password by running:
sudo mysql_secure_installation
What will mysql_secure_installation ask?
The script prompts you for the temporary password first. Then it asks you to set a new root password. Choose a password you will remember (it must include uppercase, lowercase, numbers, and a special character to satisfy MySQL 8.0's default policy).
For the remaining prompts (remove anonymous users, disallow root login remotely, remove test database, reload privileges), answer y to each.
- Log in to MySQL as root with your new password by running:
mysql -u root -p
Enter the new password you just set when prompted. You should see the mysql> prompt.
Now create the database, table, seed data, and DMS user. Run the following SQL statements inside the MySQL prompt.
- Create the database and switch to it by running:
CREATE DATABASE IF NOT EXISTS productdb;
USE productdb;
- Create the products table by running:
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
category VARCHAR(100) NOT NULL
);
What does this table store?
This is the product catalog for your monolith application. Each row represents a product with an auto-incrementing ID, a name, a price, and a category. The Flask API you build in the next step will query this table and return the results as JSON.
- Insert 10 sample products by running:
INSERT INTO products (name, price, category) VALUES
('Wireless Mouse', 29.99, 'Electronics'),
('USB-C Hub', 49.99, 'Electronics'),
('Standing Desk', 399.99, 'Furniture'),
('Mechanical Keyboard', 89.99, 'Electronics'),
('Monitor Light Bar', 59.99, 'Accessories'),
('Laptop Stand', 34.99, 'Accessories'),
('Webcam HD', 79.99, 'Electronics'),
('Desk Pad XL', 24.99, 'Accessories'),
('Ergonomic Chair', 549.99, 'Furniture'),
('Cable Management Kit', 19.99, 'Accessories');
- Verify the data landed by running:
SELECT COUNT(*) FROM products;
You should see 10 in the output. This confirms all products were inserted.
- Create the DMS migration user with replication privileges by running:
CREATE USER IF NOT EXISTS 'dms_user'@'%' IDENTIFIED BY 'dms-password-here';
GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'dms_user'@'%';
FLUSH PRIVILEGES;
Why does dms_user need replication privileges?
AWS DMS reads data from the source database by connecting as a regular MySQL user. For a full-load migration, SELECT is enough. But DMS also needs REPLICATION CLIENT and REPLICATION SLAVE privileges to read the binary log during Change Data Capture (CDC). You grant these now so the user is ready for both the full-load step and the Secret Mission later.
- Exit the MySQL prompt by running:
EXIT;
Your monolith database is fully configured. The EC2 instance now has MySQL 8.0 running with a seeded product catalog and a dedicated migration user.
Your monolith environment is complete. You have a running EC2 instance with MySQL 8.0 and Python 3 installed, a product database seeded with 10 records, and a migration user ready for DMS. Next up, you will build the Flask API that serves this data.
Run the Monolith App
Your EC2 instance is running with MySQL loaded and 10 products waiting in the database. Now you need the actual web application serving that data over HTTP.
A real monolith runs both the application and its database on the same server. In this step, you will build that baseline with Flask so you have a working "before" state to migrate from.
In this step, get ready to:
- Install Flask and the pymysql database connector on the EC2 instance.
- Create a Flask app that queries the local MySQL database and returns product data as JSON.
- Verify the monolith baseline by curling the running app and seeing all 10 products.
Install Flask and pymysql
Your app needs two Python packages. Flask handles HTTP requests and pymysql connects Python to the MySQL database running on the same instance.
- Install both packages by running this command in your SSH session:
pip3 install flask pymysql
- Confirm the install succeeded by running:
python3 -c "import flask; import pymysql; print('Flask', flask.__version__, '| PyMySQL', pymysql.__version__)"
You should see version numbers printed for both packages with no errors.
Seeing a permission error?
If you see a permissions error, try running the install with sudo:
sudo pip3 install flask pymysql
ask
Create the Flask application
The app connects to the local MySQL database and exposes product data as a JSON API. You will create it in two parts: the database configuration first, then the route handlers.
- Create the first half of app.py with the database connection setup by running:
cat > app.py << 'EOF'
from flask import Flask, jsonify
import pymysql
app = Flask(__name__)
# Monolith state: points to localhost
DB_HOST = "localhost"
DB_USER = "root"
DB_PASSWORD = "your-local-mysql-password"
DB_NAME = "productdb"
def get_db_connection():
return pymysql.connect(
host=DB_HOST,
user=DB_USER,
password=DB_PASSWORD,
database=DB_NAME,
cursorclass=pymysql.cursors.DictCursor
)
EOF
What does this code do?
- DB_HOST is set to localhost because the database lives on the same machine as the app. This is the monolith pattern you will break apart later.
- DB_PASSWORD should be replaced with the MySQL root password you set in Step 1.
- get_db_connection() creates a connection to MySQL and returns rows as Python dictionaries (thanks to DictCursor), which Flask can easily convert to JSON.
- Confirm the file was created by running:
cat app.py
You should see the imports, configuration variables, and the get_db_connection function printed to your terminal.
- Now append the route handlers and main block to app.py by running:
cat >> app.py << 'EOF'
@app.route("/products", methods=["GET"])
def get_products():
conn = get_db_connection()
try:
with conn.cursor() as cursor:
cursor.execute("SELECT id, name, price, category FROM products")
products = cursor.fetchall()
return jsonify(products)
finally:
conn.close()
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "healthy", "database": DB_HOST})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
EOF
What do the routes do?
- /products queries all rows from the products table and returns them as a JSON array. The finally block ensures the database connection closes even if something goes wrong.
- /health returns which database host the app is connected to. After migration, this will show the RDS endpoint instead of localhost.
- host="0.0.0.0" tells Flask to listen on all network interfaces, not just localhost.
- Replace your-local-mysql-password with the MySQL root password you created in Step 1. Open the file with:
nano app.py
- Find the line DB_PASSWORD = "your-local-mysql-password" and replace the placeholder with your actual password.
- Save the file by pressing Ctrl+O, then Enter, then Ctrl+X to exit nano.
✔️ Awesome, I've got everything!
Great. Make sure you have saved app.py with your real MySQL password before continuing.
ⓧ I'd like to double check the full code
Your app.py should contain these sections in order:
- Imports (flask and pymysql) and app = Flask(__name__).
- Four config variables: DB_HOST set to localhost, DB_USER set to root, DB_PASSWORD with your password, DB_NAME set to productdb.
- The get_db_connection() function returning a pymysql.connect() call with DictCursor.
- The /products route that queries SELECT id, name, price, category FROM products and returns JSON.
- The /health route that returns the current DB_HOST value.
- The if __name__ == "__main__" block running on 0.0.0.0 port 5000.
Scroll up and confirm each section matches what you pasted.
Start the app and verify the monolith baseline
Time to prove the monolith works end-to-end. The Flask app should connect to the local MySQL database and return all 10 products you loaded in Step 1.
- Start the Flask app in the background by running:
python3 app.py &
What does the & do?
The & at the end runs the process in the background so you can keep using the same terminal. Flask will print its startup message, then return you to the command prompt.
You should see Flask print a message indicating it is running on http://0.0.0.0:5000.
Before you run the next command, what do you think the response will contain?
- Query the products endpoint by running:
curl http://localhost:5000/products
You should see a JSON array containing all 10 products from your local MySQL database, including items like Wireless Mouse, USB-C Hub, and Standing Desk with their prices and categories.
- Check the health endpoint too by running:
curl http://localhost:5000/health
The response should show {"database":"localhost","status":"healthy"}. That localhost value is what you will watch change to an RDS endpoint after migration.
Seeing a connection refused error?
- Make sure the Flask app is still running. Run jobs to check. If nothing is listed, start it again with python3 app.py &.
- If you see a MySQL access denied error, confirm your DB_PASSWORD in app.py matches the root password you set in Step 1.
- Check that MySQL is running with sudo systemctl status mysqld.
help me debug my Flask app connection issue on EC2
This is your monolith baseline. One server running both the app and the database, exactly the pattern a Solutions Architect would recommend migrating away from. Next up, you will create the RDS target database so DMS has somewhere to send the data.
Create the RDS Target Database
Your monolith is alive and serving product data from a single EC2 box. The next challenge is giving that data a proper home.
Right now, the MySQL database shares the same server as the application. If that instance goes down, both the app and the database disappear with it. Amazon RDS solves this by hosting your database on a managed instance with automated backups, patching, and failover capabilities. In this step, you create an RDS MySQL instance that will become the migration target.
In this step, get ready to:
- Launch an RDS MySQL instance using the Free Tier template.
- Configure security groups so both EC2 and DMS can reach the new database.
- Connect to the RDS instance and create an empty target database.
Create the RDS MySQL instance
The migration target needs to exist before DMS can replicate data into it. You will create a db.t3.micro RDS MySQL instance that sits in the same VPC as your EC2 instance so the two can communicate over the private network.
- In the AWS Management Console, type RDS into the search bar at the top and select RDS from the results.
- In the left navigation pane, choose Databases.
- Click Create database.
- Under Choose a database creation method, select Standard create.
- Under Engine options, select MySQL.
- Under Templates, select Free tier.
- Under Settings, enter migration-target-db for the DB instance identifier.
- Leave Master username as admin.
- Enter a password you will remember for Master password and confirm it.
Write down your RDS password
You will need this password later when connecting from EC2 and when configuring the DMS target endpoint. Store it somewhere safe.
- Under Instance configuration, confirm the DB instance class is set to db.t3.micro.
- Under Storage, confirm Allocated storage is 20 GB and storage type is gp2. Uncheck Enable storage autoscaling to stay within free tier limits.
- Under Connectivity, for Virtual private cloud (VPC), select the same VPC your EC2 instance is in.
- For Public access, select No. The RDS instance only needs to be reachable from inside the VPC.
- Leave all other settings at their defaults.
- Scroll to the bottom and click Create database.
The database status shows Creating. It takes 5 to 10 minutes to become Available. While you wait, you will set up the security groups.
Why Free Tier and Single-AZ?
The Free Tier template pre-selects Single-AZ deployment and a db.t3.micro instance class. You get 750 hours per month of Single-AZ micro instance usage and 20 GB of General Purpose (SSD) storage at no cost within your free tier eligibility period.
Single-AZ means there is no standby replica in another Availability Zone. For a learning project this is fine. In production, you would enable Multi-AZ for high availability.
Configure security groups for database access
Your RDS instance needs to accept MySQL connections from two sources: the EC2 instance (so the app and the mysql client can talk to it) and the DMS replication instance (which you will create in the next step). You will create a dedicated security group for DMS now, then add both as inbound sources on the RDS security group.
- In the AWS console, navigate to EC2 (type EC2 in the search bar).
- In the left navigation pane under Network & Security, click Security Groups.
- Click Create security group.
- For Security group name, enter dms-replication-sg.
- For Description, enter Security group for DMS replication instance.
- For VPC, select the same VPC as your EC2 and RDS instances.
- Leave inbound and outbound rules at defaults. Click Create security group.
Why create the DMS security group now?
In AWS, security group rules can reference other security groups by ID. By creating this group now, you can immediately add it as a trusted inbound source on the RDS security group. When you create the DMS replication instance in the next step, you will assign it this security group so it inherits the access.
Now modify the RDS database security group to allow inbound MySQL traffic from both the EC2 security group and the new DMS security group.
- Return to Security Groups in the EC2 console.
- Find and select the security group attached to your RDS instance. It will have the name or description referencing migration-target-db or the default RDS security group created during the wizard.
- Click the Inbound rules tab, then click Edit inbound rules.
- Click Add rule. For Type, select MYSQL/Aurora. For Source, search for and select your EC2 instance's security group.
- Click Add rule again. For Type, select MYSQL/Aurora. For Source, search for and select dms-replication-sg.
- Click Save rules.
You should see two new inbound rules allowing TCP port 3306 from the EC2 security group and the DMS security group.
Don't see your EC2 security group in the source dropdown?
Make sure you are editing the security group in the same VPC as your EC2 instance. Security groups only reference other groups within the same VPC. Start typing the name of your EC2 security group to filter the list.
Help me find my EC2 security group for the RDS inbound rule.
Connect to RDS and create the target database
Before DMS can migrate data, the target RDS instance needs an empty productdb database waiting to receive the tables. You will connect from your EC2 instance to verify network connectivity, then create the database.
- Return to the RDS console. Click Databases in the left pane and confirm migration-target-db shows Available status.
- Click the database name migration-target-db to open its details.
- In the Connectivity & security section, copy the Endpoint value. It looks like migration-target-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com.
Before you run the next command, what do you think will happen when you try to connect to RDS from the EC2 instance? Will the connection succeed given the security group rules you just configured?
- Switch to your SSH session on the EC2 instance and test connectivity by running this command:
mysql -h [[RDS_ENDPOINT="your RDS endpoint e.g. migration-target-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com"]] -u admin -p -e "SHOW DATABASES;"
Enter your RDS master password when prompted. You should see the default MySQL databases listed, confirming the connection works.
- Create the empty target database by running:
mysql -h [[RDS_ENDPOINT="your RDS endpoint e.g. migration-target-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com"]] -u admin -p -e "CREATE DATABASE productdb;"
- Verify the database was created by running:
mysql -h [[RDS_ENDPOINT="your RDS endpoint e.g. migration-target-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com"]] -u admin -p -e "SHOW DATABASES;"
You should see productdb in the list alongside the default system databases.
Connection timed out or refused?
If you see a timeout, the security group is likely blocking traffic. Confirm that the RDS security group has an inbound rule for MYSQL/Aurora (port 3306) with your EC2 security group as the source.
Also confirm that your EC2 instance and RDS instance are in the same VPC. You can check this in the RDS instance details under Connectivity & security.
Help me troubleshoot why I can't connect to my RDS instance from EC2.
Your migration target is ready. You have an empty productdb on RDS waiting for data, and the security groups are pre-configured so DMS can reach it. Next up, you will create the DMS replication instance and run the actual migration.
Migrate the Database with DMS
Your Flask app is running and serving 10 products from the local MySQL database. The RDS target is provisioned and waiting with an empty productdb.
Now you need to move the data from the EC2 MySQL to RDS without writing migration scripts. AWS Database Migration Service handles this through a managed pipeline: a replication instance that orchestrates the transfer, endpoints that define the source and target, and a migration task that moves the rows.
In this step, get ready to:
- Create a DMS replication instance and configure source and target endpoints.
- Run the migration task and diagnose a common DMS failure.
- Fix the source database configuration and complete the migration.
Create the DMS replication instance and endpoints
Before DMS can move data, it needs three components: a replication instance (a small server that runs the migration engine), a source endpoint (connection details for the EC2 MySQL), and a target endpoint (connection details for the RDS instance).
- In the AWS Management Console, search for DMS and open the AWS Database Migration Service console.
- In the left navigation pane, click Replication instances.
- Click Create replication instance.
- For Name, enter migration-instance.
- For Instance class, select dms.t3.micro (free tier eligible).
- For High Availability, select Single-AZ.
- For VPC, select the same VPC as your EC2 and RDS instances.
- Leave all other settings at their defaults and click Create replication instance.
The replication instance takes a few minutes to provision. Wait until the Status column shows Available before continuing.
What is a replication instance?
A replication instance is a managed compute resource that runs the DMS migration engine. It connects to both your source and target databases, reads data from one, and writes it to the other.
The dms.t3.micro class is included in the AWS Free Tier for 750 hours per month during your first year.
Now you need to find your EC2 instance's private IP address for the source endpoint.
- Open the EC2 console in a new tab.
- Click on your running instance and find the Private IPv4 address in the Details tab.
- Copy this IP address and save it somewhere you can paste from.
Return to the DMS console to create the source endpoint.
- In the left navigation pane, click Endpoints.
- Click Create endpoint.
- For Endpoint type, choose Source endpoint.
- For Endpoint identifier, enter ec2-mysql-source.
- For Source engine, choose MySQL.
- For Access to endpoint database, choose Provide access information manually.
- For Server name, paste the EC2 private IP address you copied.
- For Port, enter 3306.
- For User name, enter dms_user.
- For Password, enter the password you set for dms_user when you created it.
- Leave the Database name field empty.
Why leave the database name empty?
For MySQL endpoints, AWS recommends not specifying the database name on the endpoint itself. You select which schema to migrate in the migration task's table-mapping rules instead. This keeps the endpoint reusable across different schemas.
- Expand the Test endpoint connection (optional) section.
- For VPC, select your VPC.
- For Replication instance, select migration-instance.
- Click Run test.
Wait until the status shows successful. This confirms DMS can reach the MySQL database on your EC2 instance.
- Click Create endpoint to save the source endpoint.
Connection test failed?
- Verify the EC2 security group allows inbound traffic on port 3306 from the DMS replication instance's security group.
- Double-check the private IP address matches your EC2 instance (not the public IP).
- Confirm the dms_user password is correct by testing it via SSH on the EC2 instance.
Help me troubleshoot my DMS source endpoint connection test failure.
Now create the target endpoint for RDS.
- Click Create endpoint again.
- For Endpoint type, choose Target endpoint.
- Check the Select RDS DB instance box and choose migration-target-db.
- For Endpoint identifier, enter rds-mysql-target.
- The server name and port auto-populate from the RDS instance.
- For User name, enter admin.
- For Password, enter the password you set when creating the RDS instance.
- Expand Test endpoint connection, select your VPC and replication instance, then click Run test.
- Wait for the status to show successful, then click Create endpoint.
Both endpoints now show a successful connection test. DMS can reach the source MySQL on EC2 and the target RDS instance.
Create and start the migration task
With the replication instance and endpoints in place, you can now create the migration task that tells DMS exactly what to move and how.
- In the left navigation pane, click Database migration tasks.
- Click Create task.
- For Task identifier, enter migrate-productdb.
- For Replication instance, select migration-instance.
- For Source database endpoint, select ec2-mysql-source.
- For Target database endpoint, select rds-mysql-target.
- For Migration type, select Migrate existing data (full load).
What does Migrate existing data mean?
This migration type performs a one-time full load of all selected tables from source to target. It does not capture ongoing changes after the initial load completes.
The Secret Mission later upgrades this to include continuous replication using Change Data Capture.
- Scroll down to the Table mappings section.
- Click Add new selection rule.
- For Schema, choose Enter a schema and type productdb.
- For Table name, leave it as % (all tables).
- Leave all other settings at their defaults.
- Click Create task.
Before you check the result, think about what you expect to happen. DMS is trying to read data from your source MySQL and replicate it to RDS. Do you think the task will succeed on the first try?
Watch the Status column on the migration tasks page. After a minute or two, the task status changes to Error or shows a failure message referencing binary logging.
This failure is intentional. DMS validates that the source MySQL has binary logging enabled as part of its premigration checks. Your EC2 MySQL does not have it configured yet. This is the single most common DMS troubleshooting issue in production migrations.
Why does DMS need binary logging?
Binary logging records every change made to the database in a sequential log file. DMS uses this log to track data changes and ensure a consistent snapshot during migration.
Without binary logging, DMS cannot guarantee consistency if new writes occur while it reads the source data. The premigration assessment (mysql-check-binlog-enabled) catches this before any data moves.
Fix binary logging and complete the migration
The fix is straightforward: configure binary logging in MySQL's configuration file, restart the service, then re-run the DMS task.
- Switch back to your SSH terminal connected to the EC2 instance.
- Create the binary logging configuration by running this command:
sudo tee /etc/my.cnf.d/binlog.cnf << 'EOF'
[mysqld]
server-id=1
log-bin=/var/lib/mysql/mysql-bin
binlog_format=ROW
binlog_row_image=FULL
expire_logs_days=1
EOF
What do these settings do?
- server-id=1 gives this MySQL instance a unique identity for replication.
- log-bin enables binary logging and sets the file location.
- binlog_format=ROW logs each individual row change, which DMS requires for consistent replication.
- binlog_row_image=FULL logs all column values in each row event so DMS can reconstruct complete rows.
- expire_logs_days=1 prevents binary logs from filling up the disk by auto-deleting logs older than one day.
- Restart MySQL to apply the new configuration by running:
sudo systemctl restart mysqld
- Verify binary logging is now active by running:
mysql -u root -p -e "SHOW VARIABLES LIKE 'log_bin';"
You should see log_bin with a value of ON. Binary logging is now enabled.
Still seeing log_bin OFF?
- Verify the file was created correctly by running cat /etc/my.cnf.d/binlog.cnf and checking the contents match the settings above.
- Make sure MySQL restarted successfully by running sudo systemctl status mysqld and checking for active (running) status.
- If MySQL failed to start, check for typos in the config file path or parameter names.
Help me troubleshoot why binary logging is not enabled after restarting MySQL.
Now return to the DMS console to restart the migration task.
- In the DMS console, click Database migration tasks in the left navigation.
- Select the migrate-productdb task (its status shows Error or Failed).
- Click Actions, then choose Restart/Resume.
- Watch the Status column. This time the task should progress through the loading phase.
Before you check the table statistics, what do you expect to see? You have 10 products in your source productdb.products table.
- Wait until the task status shows Load complete.
- Click on the migrate-productdb task to open its details.
- Click the Table statistics tab.
You should see the products table listed with 10 rows in the Full load rows column. The migration succeeded.
Verify the data actually landed in RDS by switching to your SSH terminal and querying the target directly.
- Connect to the RDS instance from EC2 and check the row count by running:
mysql -h [[RDS_ENDPOINT="your-rds-endpoint.region.rds.amazonaws.com"]] -u admin -p -e "SELECT COUNT(*) FROM productdb.products;"
You should see a count of 10. All product records migrated successfully from the EC2 MySQL to RDS.
Task still failing after restart?
- Confirm MySQL is running by checking sudo systemctl status mysqld on the EC2 instance.
- Re-run the endpoint connection test in the DMS console to verify DMS can still reach the source after the MySQL restart.
- If the task shows a different error about permissions, verify dms_user has SELECT, REPLICATION CLIENT, and REPLICATION SLAVE privileges.
Help me troubleshoot my DMS migration task failure after enabling binary logging.
Your data is now safely replicated to RDS. Next up, you will cut the application over to point at the managed database and prove the migration works end-to-end.
Cut Over the Application
Your DMS migration task completed successfully. All 10 product records now sit in your RDS database, verified and ready to serve traffic.
The data is in RDS, but your Flask app still points at localhost. Until you change the connection string, the application reads from the old local MySQL. In this step, you will swap the connection details, restart the app, and prove the migration works by shutting down local MySQL entirely.
In this step, get ready to:
- Update the database connection string in app.py to point at the RDS endpoint.
- Verify the application returns the same 10 products from RDS, then stop local MySQL to prove the cutover is complete.
Update the database connection string
A connection string cutover is the simplest form of migration. You change four values in your code and the app talks to an entirely different database server.
- In the terminal where Flask is running, press Ctrl+C to stop the app.
- Open app.py in nano by running:
nano app.py
- Near the top of the file, find these four connection variables:
# Monolith state: points to localhost
DB_HOST = "localhost"
DB_USER = "root"
DB_PASSWORD = "your-local-mysql-password"
DB_NAME = "productdb"
- Replace those five lines (the comment and four variables) with the following. Use your own RDS endpoint and the password you set when creating the RDS instance:
# After cutover, this points to the RDS endpoint
DB_HOST = "[[RDS_ENDPOINT="your RDS endpoint (e.g. migration-target-db.xxxxx.us-east-1.rds.amazonaws.com)"]]"
DB_USER = "admin"
DB_PASSWORD = "your-rds-password-here"
DB_NAME = "productdb"
Where do I find my RDS endpoint?
Return to the AWS Console. Open the RDS service, click Databases, then click migration-target-db. The endpoint is listed under Connectivity & security.
It looks like migration-target-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com.
- Save and exit nano by pressing Ctrl+O, then Enter, then Ctrl+X.
- Restart the Flask app in the background by running:
python3 app.py &
You should see Flask print its startup message, confirming it is running on port 5000.
Why run with & at the end?
The & sends Flask to the background so you can continue typing commands in the same terminal session. Without it, the process holds the terminal and you cannot run curl.
Flask won't start?
If you see a connection error on startup, double-check that the DB_HOST value in app.py matches your actual RDS endpoint exactly. A single missing character will prevent the connection.
Also verify that DB_PASSWORD matches the master password you set when creating the RDS instance.
Help me troubleshoot my Flask connection to RDS
Prove the app reads from RDS
The app restarted. But is it actually pulling data from RDS, or could it still be reading from the local MySQL that is also running?
- Before you run the next command, predict: do you think the app will return the same 10 products it returned before?
- Test the app by running:
curl http://localhost:5000/products
You should see JSON output listing all 10 products. The data looks identical to before because DMS migrated every row.
Why does the output look the same?
A successful migration means the data is identical on both sides. The products JSON looks the same because the migration preserved every row and column exactly.
The difference is invisible at the application layer. It only shows up in WHERE the data lives.
- Now for the real proof. Stop the local MySQL service so it can no longer serve any data:
sudo systemctl stop mysqld
- Predict: what do you think will happen when you curl the app now? If the app still pointed at localhost, it would fail. If it truly reads from RDS, it will keep working.
- Run the same curl command again:
curl http://localhost:5000/products
All 10 products appear again. Local MySQL is completely stopped, and your app does not care. It reads from RDS now. The migration is complete.
Check the health endpoint too
Run curl http://localhost:5000/health to see the database field in the JSON response. It now shows your RDS endpoint instead of localhost.
Curl returns a connection error after stopping MySQL?
This means app.py still points at localhost. Open the file with nano app.py and confirm DB_HOST contains your RDS endpoint, not localhost. Save, restart Flask, and try again.
Help me verify my app points to RDS instead of localhost
You just completed a full database migration. Your Flask app started on a single EC2 instance talking to a local database, and now it reads from a managed RDS instance with automated backups and patching.
Next up, try the Secret Mission to add continuous replication with Change Data Capture.
Secret mission
Enable Change Data Capture (CDC)
In production, you cannot stop writes during a migration. Can you configure DMS to continuously replicate new changes from the source to the target, proving you can perform a zero-data-loss cutover?
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. All three AWS resources in this project (EC2, RDS, DMS) fit within the AWS Free Tier, so there are no ongoing costs as long as your free tier is still active.
Cost warning
If your AWS Free Tier expires (12 months after account creation), the EC2 instance, RDS instance, and DMS replication instance will begin incurring charges. Check your free tier expiration date in the AWS Billing Console.
If you are done with the project and have taken your screenshots, deleting all resources is the safest option.
Resources you used:
- 1x EC2 t3.micro instance (750 free hours/month).
- 1x RDS db.t3.micro instance migration-target-db (750 free hours/month).
- 1x DMS dms.t3.micro replication instance (750 free hours/month).
- 2x DMS migration tasks (full load and CDC).
- 2x DMS endpoints (source and target).
- 1x EC2 key pair and local .pem file.
- 2x Security groups (EC2 and RDS).
Keep everything running
No action needed. Choose this if you are still actively building or want to experiment further with DMS and CDC replication.
- All three services (EC2, RDS, DMS) fit within the AWS Free Tier at 750 hours/month each.
- Monitor your usage in the AWS Billing Console to ensure you stay within free tier limits.
- Your DMS CDC task will continue replicating changes from EC2 MySQL to RDS as long as it is running.
Pause - I'll come back to this later
Shut down what you can to reduce free tier consumption. You cannot stop RDS or DMS instances without deleting them, so this option only pauses EC2.
- In the EC2 console, select your instance, click Instance state, then Stop instance.
- Your RDS instance and DMS replication instance will continue consuming free tier hours. You cannot stop them without deleting.
- When you return, start the EC2 instance from the same menu and reconnect via SSH.
Delete - I don't want to use this again
Remove all project resources completely. Follow these steps in order because DMS resources have dependencies.
- Delete DMS migration tasks: In the DMS console, select Database migration tasks in the left navigation. Select each task, click Actions, choose Stop (if running), then Actions and Delete. Confirm when prompted.
- Delete DMS endpoints: In the DMS console, select Endpoints in the left navigation. Select each endpoint (source and target), click Actions, then Delete. Confirm when prompted.
- Delete DMS replication instance: In the DMS console, select Replication instances. Select your instance, click Actions, then Delete. Confirm when prompted.
- Delete RDS instance: In the RDS console, select Databases. Select migration-target-db, click Actions, then Delete. Uncheck Create final snapshot, acknowledge the checkbox, type delete me in the confirmation box, and click Delete.
- Terminate EC2 instance: In the EC2 console, select Instances. Select your instance, click Instance state, then Terminate (delete) instance. Confirm when prompted.
- Delete key pair: In the EC2 console, select Key Pairs in the left navigation under Network & Security. Select your key pair, click Actions, then Delete. Type Delete in the confirmation prompt and confirm.
- Delete local .pem file: On your local machine, delete the key file you downloaded earlier.
That's a Wrap!
That's a Wrap!
Nice work. You just migrated a running web application from a simulated on-premises monolith to a properly architected AWS setup using Database Migration Service.
You've learned how to:
- Configure AWS DMS with a replication instance, source and target endpoints, and a migration task to move a MySQL database from EC2 to Amazon RDS.
- Diagnose and fix the most common DMS error (binary logging not enabled) by configuring the MySQL source correctly.
- Cut over a running Flask application from a local database to a managed RDS instance with zero data loss.
- Secret Mission: Enabled Change Data Capture (CDC) for continuous replication, proving production-grade migration capability.
Ready to quiz yourself?