Storing .NET API Data from AWS EC2 to AWS RDS: A Step-by-Step Guide

In this article, we configure an AWS RDS instance to store data for a .NET 8 API deployed on an AWS EC2 instance. We cover the creation of the RDS instance, ensuring it follows best security practices, and establishing a secure connection between the EC2 instance and RDS. Additionally, we demonstrate how to manually connect to the RDS instance using a bastion host and update the application to use the new database. This is the final part of a 4-part series on .NET 8 deployment on AWS.

This is Part 4 of an ongoing series I'm writing regarding .NET 8 deployment. Part 2 can be accessed here, or on my profile. You can access Part 3 here, but that's an optional step.

If you've followed along with Parts 1 and 2, you have now deployed a .NET 8 API onto an AWS EC2 instance. And in your local API development, there is a good chance you have been working with a SQL database.

In this article, we will configure an AWS RDS instance to save our EC2-hosted application data in a cloud-hosted SQL database.

Prerequisites

To begin this article, there are some things you should have:

  • An EC2 instance running a .NET 8 Web API

  • HTTPS configured on EC2 via NGINX and Certbot

  • A valid security group (should've been created when you created the EC2 instance)

  • A database management tool for accessing your selected SQL db (i.e PgAdmin 4 or MySQL Workbench)


Getting Started

What's RDS? Why are we using it?

Relational Database Service, or RDS, is a service that simplifies the setup, operation, and scaling of a relational database for use in cloud applications.

I use RDS over other cloud database solutions for 2 main reasons:

  1. AWS RDS has a very generous 1 year free tier

  2. Integration with our EC2 instance is stupid easy

Creating Our RDS Instance

Now, let's begin configuring our RDS instance. Navigate to the RDS console, then select the Create database button. To start off, we'll select Standard Create.

As I mentioned earlier, I will be selecting PostgreSQL as my engine type, then PostgreSQL 16.2-R2 as my Engine version.

For templates, make sure you select Free tier (unless you want to spend some hard earned $$$)

Give your database a unique name, then select your Master username and password. Make sure you write those down.

Select Connect to an EC2 compute resource and select your EC2 instance from the last part of this series. This will enable our EC2 instance and RDS database to communicate within the same Virtual Private Cloud (VPC) that was created in the last part of the series.

Let RDS create a new subnet. It's important to keep the subnets of the EC2 instance and RDS instance isolated from eachother.

Select Create new, which allows us to create an entirely new Security group within our VPC. This ensures the database is isolated from all other traffic rules.

Complete the setup wizard by hitting the Create database button. After a few minutes, your RDS instance will be ready to go.

RDS Security Best Practices

Now that our database has been created, I want to focus on the security of the RDS instance. Our priority with databases should always be best security practices.

The first thing to verify is that the RDS instance is not in a public subnet. We can verify this by checking the resource map of our VPC.

Notice how the RDS route table doesn't route to our internet gateway. That means it's private, and is not accessible via the internet.

The second thing to verify is that the RDS instance has it's own isolated security groups. We honestly don't really need the one you manually created. It's the auto-generated rds-ec2-1 and ec2-rds-1 security groups that are important. They were generated with these rules:

These rules allow our EC2 and RDS instances to securely communicate within the same VPC.

Configuring Manual Connection

Our database is in a private subnet, so the only thing that can connect to it is the API in our EC2 instance. But I'd like to manually connect to it, so that we can verify it's creation.

Go to the RDS console, and select your instance. Under the Connectivity and security tab, grab your Endpoint and Port.

Open your database management tool of choice (i.e PgAdmin for Postgres, MySQL Workbench for MySQL, etc.) then connect to your server using the Endpoint and port, along with the username and the password you created during the earlier setup.

Your connection should fail. By placing our RDS instance in a private subnet, that means it can't be connected to from the internet, which blocks off the majority of security threats. However, that also means that you can't connect to it from the Internet.

To be able to connect to our RDS instance, we need to establish a bastion host. A bastion host acts as a gatekeeper between an internal network and external networks, like the internet, ensuring only authorized traffic gets through.

To achieve this, we need something where:

  • inbound traffic to the bastion host is permitted from the Internet, and

  • the target resource permits incoming traffic from the bastion host

These conditions are satisfied by our EC2 instance, which already has a security group that allows all traffic on ports 80 and 443. Plus, the new rds-ec2-1 and ec2-rds-1 security groups allow the RDS instance to receive incoming traffic from the EC2 instance.

The best way to use our EC2 instance as a bastion host is to use SSH with port forwarding. On Windows, this can be done using an SSH client like PuTTY. For Mac/Linux, run this command:

ssh -i /path/to/your/key/file.pem -L 5151:<rds endpoint>:<rds port> ec2-user@<public IP address of EC2 instance>

Note that 5151 can be any port number, it just forwards traffic to the RDS instance.

By running this command, all traffic on localhost:5151 is encrypted and sent over the SSH connection to our EC2 bastion, which is forwarded to the RDS instance port. This is known as SSH tunneling.

While tunneled, try to reconnect to your server using localhost as your endpoint, 5151 (or whatever number you chose) as your port, and then your database username and password.

Your connection should be successful!

If at this point, you wanted to restore a database that you have dumped (i.e from local environment, another cloud provider, or from another RDS instance) you can do so by tunneling into the RDS instance, like before. Make sure to install the correct packages on your EC2 instance (i.e psql, mysql, etc.)

Connecting EC2 to RDS

With our RDS instance configured with best security practices in mind, let's hook it up to our EC2 instance.

If you're storing your database connection string in an appsettings.json file, like in this Microsoft example, all you have to do is update the existing connection string to the RDS instance-provided Endpoint , port, username and password. Then, like I covered in part 1, you can then:

  • Copy the updated appsettings.json into the Docker image via Dockerfile

  • Mount the updated appsettings.json as a volume when running the container

  • Use the -e flag when running the container to pass environment variables. Then use either colon (:) or double underscores (__) to represent JSON hierarchy. For instance, ConnectionStrings: { DefaultConnection: {"connection"} } becomes:

    • "ConnectionStrings:DefaultConnection=connection" or

    • "ConnectionStrings__DefaultConnection=connection"

Conclusion

After all these steps, you should now have a functioning RDS instance, that follows great security practices and is fully integrated into your EC2 instance.

That brings us to the end of my 4-part series on .NET 8 deployment on AWS!