Deploy Rails in Amazon ECS: Part 3 - Create the RDS database, Task Definition, and Load Balancer
Tags: aws, docker, ruby, devops
Published: 2019-08-08 14:05:27

---
title: Deploy Rails in Amazon ECS: Part 3 - Create the RDS database, Task Definition, and Load Balancer
published: true
description: 
tags: aws, docker, ruby, devops
---
This is the third part of the *Deploy  Rails in Amazon ECS* post. It's part of a broader series called *More than 'hello world' in Docker*. The series will get you from *hello world* in Docker to having your application deployed in AWS.

- [Build Rails + Sidekiq web apps in Docker](https://dev.to/jamby1100/more-than-hello-world-in-docker-run-rails-sidekiq-web-apps-in-docker-1b37)
- **Deploy Rails in Amazon ECS**
  - [Concepts](https://dev.to/jamby1100/deploy-rails-in-amazon-ecs-part-1-concepts-26nl)
  - [Push an image to ECR](https://dev.to/jamby1100/deploy-rails-in-amazon-ecs-part-2-push-an-image-to-ecr-43fl)
  - **Create the RDS database, Task Definition, and Load Balancer - we are here**
  - [Create the ECS Cluster and connect it all together](https://dev.to/jamby1100/deploy-rails-in-amazon-ecs-part-4-create-an-ecs-cluster-338f)
  - [Configure Sidekiq](https://dev.to/jamby1100/deploy-rails-in-amazon-ecs-part-5-integrate-sidekiq-and-redis-for-background-processing-150f)
- [Automate Deploys with AWS CodeDeploy](https://dev.to/raphael_jambalos/automate-deployments-with-aws-codepipeline-52f5)
  - [Deep Dive on AWS CodeBuild](https://dev.to/jamby1100/automate-docker-build-with-aws-codebuild-9om)
- Advanced ECS Concepts:
  - Service Discovery and Auto Scaling - coming soon
  - Fargate - coming soon

In this post, we will set up three AWS services we need to run a containerized applications on AWS: a database, a task definition, and a load balancer.

# 6 | Creating an RDS database

Docker containers should be stateless: they can be turned off and on because they don't hold any data or *states* (or at least not hold states that cannot be retrieved from another source). Databases, by definition, are *stateful*: they contain data and are considered to be the source of truth. If the data disappears from the database, there's no other trusted source we can count on to get it back. Since containers should be stateless, we should not host our databases in containers.

In a [previous post](https://dev.to/jamby1100/more-than-hello-world-in-docker-run-rails-sidekiq-web-apps-in-docker-1b37), we use `docker-compose` to set up a PostgreSQL server in a Docker container. That's okay for development since the data there can easily be reproduced. We cannot do that on a production database because if the container disappears, the data goes with it. 

*Database-on-containers proponents* argue that you should use docker volumes instead so when the container dies, the data does not disappear. *Well, you can*, but that's added complexity you have to manage. Better to just use a managed database service and not worry about engineering reliability into a solution you can just pay someone else to do.

For the database, we will create an RDS PostgreSQL database. 

**(6.1)** On the services tab, search for RDS and click it. Then, click "Create Database".

![](https://thepracticaldev.s3.amazonaws.com/i/u9a2me25jkx4wsbmwdwx.png)

**(6.2)** On the next page, choose PostgreSQL as your database of choice and Free Tier from the template. On the Settings tile, add the DB instance identifier of *ruby-docker-database*, and the password of your choice. Leave the DB instance size as t2.micro. For connectivity, leave the VPC as the default VPC (take note of the id of this VPC for section 8).

![](https://thepracticaldev.s3.amazonaws.com/i/vvrs0enw4zu8lvj04xql.png)

Then, click "Create Database". You will see your database is being created on the next screen:

![](https://thepracticaldev.s3.amazonaws.com/i/r8f5593i90bxkursoovv.png)

**(6.3)** When the database is done creating, click on the database name. On the next page, take note of the endpoint. We will use it to access the database later on.

![](https://thepracticaldev.s3.amazonaws.com/i/4hnw4smlcdamok66gg2j.png)

# 7 | Creating a Task Definition

A task definition is required for us to run a task in ECS. The task definition defines the resources available to a task. It also holds information about containers that are part of the task. In OOP terms, think of a task definition as the *class* and a task as an *instance of a class*: to instantiate a task, you must get information from the task definition.

**(7.1)** On the services tab, search for ECS and click it. Then, click "Create new Task Definition".

![](https://thepracticaldev.s3.amazonaws.com/i/2lhjb8gvorj3r1a079gu.png)

**(7.2)** Then, choose the EC2 launch type compatibility and click next.  

**(7.3)** Let's name our task definition as *docker-rails-app*. For the task size, let's give it 512mb of memory and 512 units of CPU. CPU units are a simplified way of quantifying compute capacity. This gives 512mb and 512 CPU units per task spawn. The containers inside one task share this resource. 

![](https://thepracticaldev.s3.amazonaws.com/i/zriluos9d9lu72wprdej.png)

**(7.4)** Scroll down and click "Add container". A task definition defines a single task. A task can contain many containers inside it. You can add as many containers as you like, but I recommend just having one per task.

![](https://thepracticaldev.s3.amazonaws.com/i/pncsp7wwyrg4yf9h56p2.png)

**(7.5)** Next, add the URI of the Docker image we just uploaded in section 5.4 to the *Image* field, and "web" in the *name* field. Also, make sure to add host port as 0 and container port as 8080. Setting the host port as zero allows many containers to reside in one EC2 instance. 

Each container inside the host instance has its own port range, and the host instance also has its own port range. On its own, the container and its port range cannot be accessed from outside the instance. By setting the host port as 0 and the container port as 8080, we are exposing the port 8080 of each container to *any, random* port in the host instance. So imagine if we had 3 containers in the host instance with the IP *10.0.0.123*. Each instance will have its own port 8080 but they will be mapped to a different port on the host instance:
- `container_one:8080` --> `10.0.0.123:16217`
- `container_two:8080` --> `10.0.0.123:11394`
- `container_three:8080` --> `10.0.0.123:2390`

This feature is called *dynamic port mapping*. It makes each container accessible via a unique `ip_address:port` combination. Hence, we can register it to the load balancer and have traffic directed to it. AWS handles it all in the background for us. All we have to do is put the host port as zero. 

In the example, it adds `10.0.0.123:16217`, `10.0.0.123:11394`, and `10.0.0.123:2390` to the load balancer so traffic can be directed to each container.

The load balancer performs health checks on each container to test if they're still functioning as they should. If a container fails a certain number of health checks, the load balancer deregisters the container so traffic won't flow to it anymore. ECS kills the problematic container and starts a new one. 


![](https://thepracticaldev.s3.amazonaws.com/i/bc3q8o74hrfmo86znkch.png)

**(7.6)** Scroll down to the environments section. For the command, put `puma,-C,config/docker_puma.rb,-p,8080`. This is the command that Docker will run when starting your container. The command starts Rails' default application server, Puma and routes traffic to the port 8080.

Add the following environment variables: 

- `POSTGRESQL_HOST`: << the endpoint you took note of in 5.3>>
- `POSTGRESQL_USER_NAME`: postgres
- `POSTGRESQL_PASSWORD`: << password you entered on 5.2 >>
- `POSTGRESQL_DB`: ruby_docker_database
- `RAILS_ENV`: staging
- `RAILS_MASTER_KEY`: << if you cloned my repo, use `e451d94494f606afa27a4cae3dea3948`. if you made your own, use the value in `config/master.key`>>
- `RAILS_LOG_TO_STDOUT` - ENABLE

The `POSTGRESQL_` prepended variables will be used in the database.yml we made in step 4.1. The `RAILS_ENV` variable is to ensure we are using the staging environment. The `RAILS_MASTER_KEY` is required for Rails 5 to run.  

![](https://thepracticaldev.s3.amazonaws.com/i/xauvo3363w5j058qt4lg.png)

**(7.7)** In the storage and logging section, click on `Auto-configure Cloudwatch Logs`. This will set up Cloudwatch logs to collect application logs from Rails, but the logs have to come from standard out. Enabling `RAILS_LOG_TO_STDOUT` in 7.6 outputs the rails logs to [standard out](http://www.linfo.org/standard_output.html). 

![](https://thepracticaldev.s3.amazonaws.com/i/palwpvmye0zj18oedcpv.png)

**(7.8)** Then, click "Add" to close the container definition window. And then, click "Create". You should see something like this:

![](https://thepracticaldev.s3.amazonaws.com/i/6uar9rv9vg0v46j9ht22.png)

Since this is the first time we have created a task definition, this is going to be the first version. If you click "Create new revision" from the image below, you will have an opportunity to change any of the settings we made earlier. When you save your changes, it's going to be placed on a new version, version 2.

# 8 | Create an Application Load Balancer

We talked extensively about *dynamic port mapping* in section 7.5. Essentially, each container in a host instance will be assigned a random port on the host instance. Host `10.0.0.123` can contain three containers accessible via `10.0.0.123:1911`, `10.0.0.123:7421`, `10.0.0.123:5819`. AWS ECS will take care of registering these ip_address:port combinations to the load balancer. Once registered, the load balancer will distribute incoming traffic to the containers registered to it.

**(8.1)** On the services tab, search for EC2 and click it. Then, click *Load Balancers* on the left-hand side menu and click "Create Load Balancer". On the next page, click *Application Load Balancer*. 

![](https://thepracticaldev.s3.amazonaws.com/i/x39krita2t5goa1lldxs.png)


**(8.2)** Let's name our load balancer, *rails-docker-alb*. Next, choose the VPC we chose in 6.2, and select all of its subnets. Then, click *next*. Skip "Step 2: Configure Security Settings" by clicking *next*.

![](https://thepracticaldev.s3.amazonaws.com/i/swgua89d2t3bgkte5qw3.png)

**(8.3)** Let's create a security group on the next page with the name *rails-docker-alb-sg*. This security group defines what traffic can enter our load balancer.

Make sure that port 80 is opened for 0.0.0.0/0 (everyone). This will make sure our app will be accessible to the open internet. Then, click *next*.

![](https://thepracticaldev.s3.amazonaws.com/i/8wum33dncbtda14u9arl.png)

**(8.4)** Create a new target group called *default-target*. Then, click next. Skip "Step 5: Register Targets" by clicking on *next*. 

![](https://thepracticaldev.s3.amazonaws.com/i/jbmsg00a9xb58x1tdasx.png)

**(8.5)** Review your load balancer, then click *create*.

![](https://thepracticaldev.s3.amazonaws.com/i/hst9413dxw3g9s12ajzq.png)

That's it! You've created a database, a task definition and a load balancer.

# What's next?

On the next post, you will create the ECS cluster and we will deploy our Rails app there. We will finally be able to see our Rails app deployed!