DEV Community

Cover image for Creating a free-tier AWS RDS PostgreSQL instance using Terraform
Eric Santana
Eric Santana

Posted on

Creating a free-tier AWS RDS PostgreSQL instance using Terraform

One of the most powerful tools to use as a DevOps engineer is Terraform. Simply put, Terraform is

an infrastructure as code (IaC) tool that lets you define both cloud and on-prem resources in human-readable configuration files that you can version, reuse, and share.

This means that you can actually create a whole infrastructure using declarative code. In other words, you are able to manage resources like storages as well as databases using only code.

One of the main benefits in using IaC is to have a better control of your resources and make them scalable. Even more: if you are interested in using multiple cloud services, you can control all the resources from one place, saving you time from clicking and opening multiple tabs in your browser to access each cloud service console.

In this article, I will provide a guide on how to create a free-tier AWS RDS database instance using Terraform. I write for you who want to create a RDS database instance but don't want to deal too much with AWS console. Or if you just want to learn a bit the basics of Terraform. Let's get started!


First of all, you need to have:

Selecting and pointing to AWS provider

First of all, let's create a folder to initialize a Terraform template:

mkdir terraform-aws-free-rds
cd terraform-aws-free-rds
Enter fullscreen mode Exit fullscreen mode

In this folder, create a that is going to be our entry point to declare which provider are we going to use. This is where we define For now, its content will be:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.43.0"

provider "aws" {}
Enter fullscreen mode Exit fullscreen mode

This will tell Terraform: "I want to use the AWS provider to start my infrastructure!"

But still Terraform don't know to which region we are going to create our resources or even how to access our AWS account to make these changes. In order to do so, we are going to create an AWS access key and secret key.

We also do not want to use it explicitly in our file. So we are going to create a file and put this:

variable "aws_access_key" {
  type        = string
  description = "AWS access key"

variable "aws_secret_key" {
  type        = string
  description = "AWS secret key"
Enter fullscreen mode Exit fullscreen mode

This will declare to Terraform that we have these two variables that can be accessed using var.aws_secret_key and var.aws_access_key and set using a terraform.tfvars file.

Create your terraform.tfvars and put your credentials:

aws_access_key = "your-access-key"
aws_secret_key = "your-secret-keys"
Enter fullscreen mode Exit fullscreen mode

This way, Terraform will know which values these variables have and use it on our file. Terraform will able to create, update or delete resources using this account. One more step and our provider will be completely set:

# on ``

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.43.0"

provider "aws" {
  region     = "us-east-1"
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
Enter fullscreen mode Exit fullscreen mode

Now Terraform will indeed know to which region and which account the resources are going to be created.

Declare your RDS resource

Now we just need to tell Terraform to create a RDS resource. As we are using aws provider to Terraform, you may check this documentation to understand a bit more of what we are going to write on the file.

resource "aws_db_instance" "my-free-rds-db-instance" {
  allocated_storage        = 20
  engine                   = "postgres"
  engine_version           = "16.1"
  identifier               = "my-free-rds-db-instance"
  instance_class           = "db.t3.micro"
  storage_encrypted        = false
  publicly_accessible      = true
  delete_automated_backups = true
  skip_final_snapshot      = true
  db_name                  = "database_name"
  username                 = "database_username"
  password                 = "database_password"
  apply_immediately        = true
  multi_az                 = false
Enter fullscreen mode Exit fullscreen mode

Basically, what is happening here is that:

  • We tell Terraform to use AWS provider, using the credentials we set in terraform.tfvars
  • We declare a aws_db_instance called my-free-rds-db-instance and specify all the instance configuration that would be also specified via AWS console if we were creating using it.

This configuration is known to be a free tier in RDS for 12 months. There are other configurations but for the PostgreSQL is this one:

750 hours of Amazon RDS Single-AZ db.t2.micro, db.t3.micro, and db.t4g.micro Instances usage running MySQL, MariaDB, PostgreSQL databases each month. ... 20 GB of General Purpose SSD (gp2) storage per month.

If you check in the file, we define this configuration in a declarative way.

Spinning up your Terraform resources

Now run

terraform init # to initialize the dependency lock file

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "5.43.0"...
- Installing hashicorp/aws v5.43.0...
- Installed hashicorp/aws v5.43.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

Enter fullscreen mode Exit fullscreen mode

Now we have our AWS provider set in our Terraform context, which will enable us to tell our local Terraform CLI commands which version of the provider to use.

We may see the execution plan of the resources we have declared:

terraform plan
Enter fullscreen mode Exit fullscreen mode

The output should be something like this:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # will be created
  + resource "aws_db_instance" "my-free-rds-db-instance" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = true
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = (known after apply)
      + backup_retention_period               = (known after apply)
      + backup_target                         = (known after apply)
      + backup_window                         = (known after apply)
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = false
      + db_name                               = "database_name"
      + db_subnet_group_name                  = (known after apply)
      + delete_automated_backups              = true
      + domain_fqdn                           = (known after apply)
      + endpoint                              = (known after apply)
      + engine                                = "postgres"
      + engine_version                        = "16.1"
      + engine_version_actual                 = (known after apply)
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = "my-free-rds-db-instance"
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t3.micro"
      + iops                                  = (known after apply)
      + kms_key_id                            = (known after apply)
      + latest_restorable_time                = (known after apply)
      + license_model                         = (known after apply)
      + listener_endpoint                     = (known after apply)
      + maintenance_window                    = (known after apply)
      + master_user_secret                    = (known after apply)
      + master_user_secret_kms_key_id         = (known after apply)
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = false
      + nchar_character_set_name              = (known after apply)
      + network_type                          = (known after apply)
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = (known after apply)
      + password                              = (sensitive value)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = true
      + replica_mode                          = (known after apply)
      + replicas                              = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = true
      + snapshot_identifier                   = (known after apply)
      + status                                = (known after apply)
      + storage_encrypted                     = false
      + storage_throughput                    = (known after apply)
      + storage_type                          = (known after apply)
      + tags_all                              = (known after apply)
      + timezone                              = (known after apply)
      + username                              = "database_username"
      + vpc_security_group_ids                = (known after apply)

Plan: 1 to add, 0 to change, 0 to destroy.
Enter fullscreen mode Exit fullscreen mode

This tells us which resources we declared and that are going to be created, changed or destroyed using the code we wrote.

As Bane said in The Dark Knight Rises: "It doesn't matter who we are, what matters is our plan", after reviewing and check the plan Terraform showed us, it is time to apply it:

terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

This will apply the plan and should take like three minutes to AWS make the health check of the instance available. Creating... Still creating... [10s elapsed] Still creating... [20s elapsed]
Enter fullscreen mode Exit fullscreen mode

If you log in to your AWS account and go to RDS, you'll see that the instance is going to be in the creating state:

my-free-rds-db-instance showing the creating state in AWS

After a few minutes, it will be available and Terraform will tell us:

... Still creating... [3m30s elapsed] Still creating... [3m40s elapsed] Creation complete after 3m49s [id=db-UZADWSYL266LYV2W6D6H7CUM4U]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Enter fullscreen mode Exit fullscreen mode

my-free-rds-db-instance showing the available state in AWS

You may check the endpoint to the instance via AWS Console or just accessing your terraform.tfstate and looking for the address. To connect, you just connect like it was a usual database.

Remember to use SSL to make the connection, otherwise you'll get an error. You could also add a parameter group in your Terraform files to make the force_ssl to 0 in your db instance.

Connection test of my-free-rds-db-instance working


This was a simple guide to introduce the readers on Terraform and also help them to create a free PostgreSQL RDS instance in AWS.

I hope this helps you to spin up a free PostgresQL to simulate a production-ready application and learn a bit more about using Terraform.

This project code is available on Github: terraform-aws-free-rds

GitHub logo ericbsantana / terraform-aws-free-rds

💰 A PostgreSQL free tier RDS instance using Terraform

Top comments (0)