Run Docker containers with Fargate CLI + Terraform in AWS
Tags: aws, docker, terraform, tutorial
Published: 2020-05-04 08:40:17

---
title: Run Docker containers with Fargate CLI + Terraform in AWS
published: true
description: 
tags: aws, docker, terraform, tutorial
---

I am running applications in production with AWS ECS Fargate provisioning with Terraform. I was suffering that Terraform scripts (HCL) tends to be very long because it defines infrastracture atomically and manually, so I was looking for some tools to reduce them.

This time I found Fargate CLI very useful to keep provisioning scripts very simple. Let me introduce it!

Demo project: https://github.com/acro5piano/terraform-fargate-example

# Summary

- Create basic AWS ECS environment using  [Terraform](https://www.terraform.io)
- Create fargate services and task definitions using [Fargate CLI](https://github.com/awslabs/fargatecli)
- Deploy applications with Fargate CLI, without downtime

# Motivation

There are some options to create an production ECS Fargate environment:

1. Using the AWS management console
2. AWS CLI
3. ECS CLI
4. Terraform
5. Fargate CLI

Each of them has cons:

1. makes the environment quite implicit, finally nobody understands the infrastructure
2. is hard to configure, and it does not re-create & reverse the environment, so almost same as (1)
3. does not create everything; Security Groups and ALB, and so on
4. enables to provision everything as code, but makes the scripts very long and difficult to update the service
5. does not create everything; Security Groups and ALB, and so on (same as (3))

So I would like to suggest mixing (4) and (5) and take good parts of them.

Please take a look at the complete code here:

https://github.com/acro5piano/terraform-fargate-example

# Step-by-Step guide

Assuming we are creating a simple web app.

### Dockerfile

Anything is okay if it listen port 3000.

```docker
# Dockerfile

FROM python:3.7.7-alpine3.11

CMD python -m http.server 3000
```

### Terraform

Terraform has the following responsibilities:

- Creating an ECS cluster (which runs the Fargate service)
- Creating two Security Groups (which is like firewall)
    - ALB 
    - ECS (which is allowed to communicate only with ALB)

Please replace the content or the file path for `aws-credentials.ini` depends on your environment.

```ruby
# main.tf

# depends on your environment
provider "aws" {
  region                  = "ap-northeast-1"
  shared_credentials_file = "./aws-credentials.ini"
  profile                 = "default"
}

resource "aws_ecs_cluster" "webapp" {
  name = "webapp"
}

resource "aws_security_group" "webapp" {
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port       = 3000
    to_port         = 3000
    protocol        = "tcp"
    security_groups = [aws_security_group.webapp_lb.id]
  }
}

resource "aws_security_group" "webapp_lb" {
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "webapp_ecs_security_group" {
  value = aws_security_group.webapp.id
}

output "webapp_lb_security_group" {
  value = aws_security_group.webapp_lb.id
}
```

Note: We set `output` to gettings the value easily later.

### Fargate CLI

Next, let's create a Fargate Service with Fargate CLI.

To install Fargate CLI, please visit the following link:

https://github.com/awslabs/fargatecli/releases

And run the following script.

Please replace the content or the file path for `aws-credentials.ini` depends on your environment.

```bash
#!/bin/bash

# create.sh

export AWS_SHARED_CREDENTIALS_FILE=$PWD/aws-credentials.ini
export AWS_PROFILE=default
export AWS_REGION=us-east-1

# First, create ALB
fargate lb create webapp \
    --cluster webapp \
    --port HTTP:80 \
    --security-group-id `terraform output webapp_lb_security_group`

# Next, create Fargate Service
fargate service create webapp \
    --cluster webapp \
    --lb webapp \
    --num 1 \
    --port HTTP:3000 \
    --cpu 256 \
    --memory 512 \
    --security-group-id `terraform output webapp_ecs_security_group`
```

The `fargate service create` command can take `--image` argument explicitly, but without the argument Fargate CLI will:

- Build `Dockerfile` under the current directory 
- Push the docker image to ECR
- Create a service based on the image

### Confirm

Confirm the service has been created:

```bash
$ fargate lb list --cluster webapp

NAME    TYPE            STATUS  DNS NAME                                        PORTS
webapp  Application     Active  webapp-xxxxxxxxxx.us-east-1.elb.amazonaws.com   HTTP:80
```

Open `webapp-xxxxxxxxxx.us-east-1.elb.amazonaws.com` and your service is running!

### What is created?

By Terraform:

- ECR Repository
- ECS Cluser
- EC2 Security Group

By Fargate CLI:

- ECS Task Definition
- ECS Service
- ECS Task
- ECS Task Execution Role
- ALB
- ALB Target Group
- CloudWatch Log Group

### Update service

We can update service (deploy) without downtime thanks to Fargate:e

```sh
$ fargate service deploy webapp --cluster webapp
```

### Delete

Unfortunately Fargate CLI doesn't have idempotency, but deleting is supported:

```sh
$ fargate service scale webapp 0 --cluster webapp
$ fargate service destroy webapp --cluster webapp
$ fargate lb destroy webapp
$ terrform destroy
```

Note: ECS Execution Role will not be deleted.

# Conclusion

I'm glad if you find Fargate CLI very useful tool to create ECS Fargate service.

You can write further DevOps tools for your project based on these code. The demo repository contains `Makefile` wrapper script of fargate, which make things more simpler. Please take a look:

https://github.com/acro5piano/terraform-fargate-example