---
layout: docs
page_title: Installation with Terraform - AWS ECS
description: >-
  Install Consul Service Mesh on AWS ECS with Terraform (Elastic Container Service).
---

# Installation with Terraform

This will describe how to use the [`mesh-task` module](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/mesh-task) Terraform module to launch your application in AWS ECS as part of Consul service mesh.

## Pre-requisites

* This pages assumes you are familiar with Terraform. If you are new to Terraform, see the [Terraform documentation](https://www.terraform.io/docs) to
  learn about infrastructure as code and how to get started with Terraform.
* This page assumes you are familiar with AWS ECS. See [What is Amazon Elastic Container Service](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) for more details.
* This page does not show how to create all necessary AWS resources, such as a VPC or the ECS Cluster.
  For complete runnable examples, see the links in the [Getting Started](/docs/ecs#getting-started) section.

## Using the Mesh Task Module

To run an application in ECS with Consul service mesh, you must create an ECS task definition which includes your application container(s)
and additional sidecar containers, such as the Consul agent container and the Envoy sidecar proxy container.

The [`mesh-task` module](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/mesh-task) will automatically include the necessary sidecar containers.

Here is an example Terraform configuration file which defines a task definition with an application container called `example-client-app`.

```hcl
module "my_task" {
  source  = "hashicorp/consul-ecs/aws//modules/mesh-task"
  version = "<latest version>"

  family                = "my_task"
  container_definitions = [
    {
      name         = "example-client-app"
      image        = "docker.io/org/my_task:v0.0.1"
      essential    = true
      portMappings = [
        {
          containerPort = 9090
          hostPort      = 9090
          protocol      = "tcp"
        }
      ]
      cpu         = 0
      mountPoints = []
      volumesFrom = []
    }
  ]

  port       = "9090"
  retry_join = ["<address of the Consul server>"]
}
```

All possible inputs are documented in the [module reference documentation](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/mesh-task?tab=inputs),
however there are some important inputs worth highlighting:

| Input Variable          | Type     | Description                                                                                                                                                                                                                                    |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source` and `version`  | string   | This specifies the source location and version of the `mesh-task` module.                                                                                                                                                                      |
| `family`                | string   | The [ECS task definition family](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#family). The family is also used as the Consul service name, by default.                                          |
| `container_definitions` | list     | This is the list of [container definitions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions) for the task definition. This is where you include your application containers. |
| `port`                  | number   | The port that your application listens on, if any. If your application does not listen on a port, set `outbound_only = true`.                                                                                                                  |
| `retry_join`            | list     | The is the [`retry_join`](/docs/agent/options#_retry_join) option for the Consul agent, which specifies the locations of your Consul servers.                                                                                                  |

## ECS Service

[ECS services](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_services.html) are one of the most common
ways to start tasks using a task definition..

To define an ECS Service, reference the mesh-task module's `task_definition_arn` output value
in your `aws_ecs_service` resource:

```hcl
resource "aws_ecs_service" "my_task" {
  name            = "my_task_service"
  task_definition = module.my_task.task_definition_arn
  launch_type     = "FARGATE"
  propagate_tags  = "TASK_DEFINITION"
  ...
}
```

This is a partial configuration to highlight some important fields.
See the [`aws_ecs_service`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) documentation for a complete reference.

| Input Variable    | Type    | Description                                                                                                         |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `name`            | string  | The name of the ECS service. This is required by AWS but is not used by Consul service mesh.                        |
| `task_definition` | string  | The task definition used to start tasks. Set this to the task definition ARN returned by the `mesh-task` module.    |
| `launch_type`     | string  | The launch type. Consul on ECS supports the `FARGATE` and `EC2` launch types.                                       |
| `propagate_tags`  | string  | This must be set to `TASK_DEFINITION` so that tags added by `mesh-task` to the task definition are copied to tasks. |

After defining the Terraform configuration for both the `mesh-task` and ECS service,
run `terraform apply` to create the ECS task definition and service resources. The ECS
service will soon start your application in a task. The task will automatically
register itself into the Consul service catalog.

-> **NOTE:** If your tasks run in a public subnet, they must have `assign_public_ip = true`
in their [`network_configuration`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service#network_configuration) block so that ECS can pull the Docker images.

## Routing

Now that your tasks are registered in the mesh, you're able to use the service
mesh to route between them.

In order to make calls through the service mesh, you must configure the sidecar
proxy to listen on a different port for each upstream service your application
needs to call. You then must modify your application to make requests to the sidecar
proxy on that port.

For example, if your application `web` makes calls to another application called `backend`, then you would first configure the `mesh-task` module's upstream(s):

```hcl
module "web" {
  family = "web"
  upstreams = [
    {
      destinationName = "backend"
      localBindPort = 8080
    }
  ]
}
```

| Input Variable    | Type    | Description                                                                                                                                                      |
| ----------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `destinationName` | string  | The name of the upstream service, as it is registered in the Consul service catalog.                                                                             |
| `localBindPort`   | number  | Requests to this port will be forwarded by the proxy to the upstream service. This must be an unused port, but does not need to match the upstream service port. |

If you have multiple upstream services they each need to be listed here.

Next, configure your application to make requests to `localhost:8080` when
it wants to call the `backend` service.

For example, if your service allows configuring the URL for `backend` via the
`BACKEND_URL` environment variable, you would set:

```hcl
module "web" {
  family = "web"
  upstreams = [
    {
      destinationName = "backend"
      localBindPort = 8080
    }
  ]
  container_definitions = [
    {
      name        = "web"
      environment = [
        {
          name  = "BACKEND_URL"
          value = "http://localhost:8080"
        }
      ]
      ...
    }
  ]
  ...
}
```

## Bind Address

To ensure that your application only receives traffic through the service mesh,
you must change the address that your application is listening on to only the loopback address
(also known as `localhost`, `lo`, and `127.0.0.1`)
so that only the sidecar proxy running in the same task can make requests to it.

If your application is listening on all interfaces, e.g. `0.0.0.0`, then other
applications can call it directly, bypassing its sidecar proxy.

Changing the listening address is specific to the language and framework you're
using in your application. Regardless of which language/framework you're using,
it's a good practice to make the address configurable via environment variable.

For example in Go, you would use:

```go
s := &http.Server{
	Addr:           "127.0.0.1:8080",
  ...
}
log.Fatal(s.ListenAndServe())
```

In Django you'd use:

```bash
python manage.py runserver "127.0.0.1:8080"
```

## Next Steps

- Configure a secure [Production Installation](/docs/ecs/production-installation).
- Now that your applications are running in the service mesh, read about
  other [Service Mesh features](/docs/connect).
- View the [Architecture](/docs/ecs/architecture) documentation to understand
  what's going on under the hood.
