How to Deploy a Next.Js App to AWS ECS with HashiCorp Waypoint
Tags: devops, react, aws
Published: 2020-10-20 14:21:19

---
title: How to Deploy a Next.Js App to AWS ECS with HashiCorp Waypoint
published: true
description: 
tags: #devops #react #aws
canonical_url: https://blog.kylegalbraith.com/2020/10/20/how-to-deploy-a-nextjs-app-to-aws-ecs-with-hashicorp-waypoint/
cover_image: https://blog.kylegalbraith.com/static/04ec994e4b4ac308b8d85e23f4714eed/e7401/how-to-deploy-a-nextjs-app-to-aws-ecs-with-hashicorp-waypoint.webp
---
Last week HashiCorp launched its [latest open source project, Waypoint](https://www.hashicorp.com/blog/announcing-waypoint). Waypoint is a tool to streamline the build, deploy, and release workflow for all kinds of applications. Where Terraform focuses on provisioning infrastructure, Waypoint focuses on application deployment.

It's an interesting new deployment tool. In this post, we are going to take `waypoint` for a spin and see what it is like to use it.

### Prerequisites
Before we can dive into `waypoint`, we need to have a few things set up. First, we are going to use Terraform to create our container image repository and ECS cluster in AWS. So make sure you have `terraform` configured, [see these docs for details](https://www.terraform.io/downloads.html).

Next, we need to have our AWS CLI configured. [These docs show you how to get your CLI configured](https://aws.amazon.com/cli/).

Finally, we need to install `waypoint`. This can be done a number of ways [as these docs mention](https://www.waypointproject.io/downloads). If you're on a Mac, you can run these two commands in `brew` to get it installed.

```bash
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/waypoint
```

### Setting up our Next.js demo application

Before we can focus on deployment, we need an actual application to deploy. Let's create a sample Next.js application that we can deploy via Waypoint.

We are going to make use of `create-next-app`. You can install it globally via `npm install -g create-next-app`. We will create our demo application based on the `with-typescript` example.

```bash
$ yarn create next-app --example with-typescript waypoint-demo
```

If we go into the `waypoint-demo` directory and run `yarn dev` we should have a functioning demo Next.js application living at `localhost:3000`.

![Next.js demo application](https://dev-to-uploads.s3.amazonaws.com/i/du6usc371iyny233dv3o.png)

### Setting up our Terraform & Waypoint structure

With our prerequisites out of the way and a demo application to deploy, let's set up our configuration for Terraform and Waypoint. From the `waypoint-demo` directory, run the `init` command via `waypoint`.

```bash
$ cd waypoint-demo
$ waypoint init
```

Awesome, we should now see a `waypoint.hcl` file inside of our `waypoint-demo` directory.

Waypoint also makes use of a server that you run locally in order for the CLI and GUI to work. This is a bit clunky at the moment but we need to do the following two things to run the Waypoint server locally.

```bash
$ docker pull hashicorp/waypoint:latest
$ waypoint install --platform=docker -accept-tos
✓ Server container started
Waypoint server successfully installed and configured!

The CLI has been configured to connect to the server automatically. This
connection information is saved in the CLI context named "install-1602801878".
Use the "waypoint context" CLI to manage CLI contexts.

The server has been configured to advertise the following address for
entrypoint communications. This must be a reachable address for all your
deployments. If this is incorrect, manually set it using the CLI command
"waypoint server config-set".

Advertise Address: waypoint-server:9701
HTTP UI Address: localhost:9702
```

The latter step launches the Waypoint server locally using Docker.

Next up, we need a directory to hold our Terraform configuration and state. Create a new folder in `waypoint-demo` called `infrastructure` and add a file called `versions.tf`.

```bash
$ cd waypoint-demo
$ mkdir infrastructure
$ cd infrastructure
$ touch versions.tf
```

Great, we now have a place to land both our Waypoint and Terraform configuration.

### Creating our infrastructure with Terraform

We are going to use Terraform to define the infrastructure for our application. In the context of this blog post, that means our AWS resources that our Next.js application is going to run on. For this post, we are going to use AWS Elastic Container Service (ECS) to run our application.

To do that we first need to provision a new ECS cluster in our AWS account. So we are going to add the following to our `versions.tf` file inside of our `infrastructure` folder.

```hcl
terraform {
  required_version = ">= 0.13"
}

provider "aws" {
  region = "us-west-2"
}

resource "aws_ecs_cluster" "nextjs-cluster" {
  name = "waypoint-nextjs-cluster"
}

```

This will use the AWS provider in Terraform to provision a new ECS cluster in our account with the name `waypoint-nextjs-cluster`. Let's go ahead and run `terraform apply` to provision our cluster.

```bash
$ cd waypoint-demo/infrastructure
$ terraform init
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_ecs_cluster.nextjs-cluster will be created
  + resource "aws_ecs_cluster" "nextjs-cluster" {
      + arn  = (known after apply)
      + id   = (known after apply)
      + name = "waypoint-nextjs-cluster"

      + setting {
          + name  = (known after apply)
          + value = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_ecs_cluster.nextjs-cluster: Creating...
aws_ecs_cluster.nextjs-cluster: Still creating... [10s elapsed]
aws_ecs_cluster.nextjs-cluster: Creation complete after 10s [id=arn:aws:ecs:us-west-2:<aws-id>:cluster/waypoint-nextjs-cluster]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

💪 We now have our AWS ECS cluster that we are going to run our application in. Next up we need a container image registry for Waypoint to publish to. We can use AWS Elastic Container Registry (ECR) for that.

We can add an ECR resource to our Terraform configuration in `versions.tf`. Add the following resource to the bottom of that file.

```hcl
resource "aws_ecr_repository" "nextjs-image-repo" {
  name = "nextjs-image"
}
```

We can run `terraform apply` one more time to create our image repository.

```bash
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_ecr_repository.nextjs-image-repo will be created
  + resource "aws_ecr_repository" "nextjs-image-repo" {
      + arn                  = (known after apply)
      + id                   = (known after apply)
      + image_tag_mutability = "MUTABLE"
      + name                 = "nextjs-image"
      + registry_id          = (known after apply)
      + repository_url       = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_ecr_repository.nextjs-image-repo: Creating...
aws_ecr_repository.nextjs-image-repo: Creation complete after 1s [id=nextjs-image]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

### Using Waypoint to deploy our application
Now that we have the AWS resources we need to run our Next.js app, let's deploy it using Waypoint!

To leverage `waypoint` we need to add our configuration to `waypoint.hcl`. We are going to focus on building our Next.js app as a container image and deploying it to our running ECS cluster.

Let's go ahead and add the following to our `waypoint.hcl` file.

```hcl
project = "example-next-ecs"

app "next-ecs" {
  build {
    use "pack" {}
    # Use ECR docker registry provisioned via infrastructure/versions.tf
    registry {
      use "aws-ecr" {
        region     = "us-west-2"
        repository = "nextjs-image"
        tag        = "latest"
      }
    }

  }

  # Deploy to ECS
  deploy {
    use "aws-ecs" {
      cluster = "waypoint-nextjs-cluster"
      region  = "us-west-2"
      memory  = "512"
    }
  }
}

```

What in the world are we doing here? 

First, we define an `app` for Waypoint, `next-ecs`. Inside of our app, we then define a build step and a deploy step. With our `build` step we are making use of [Cloud Native Buildpacks](https://buildpacks.io/) plugin via the `use "pack" {}` block. This is in favor of using Docker, but Docker is supported inside of Waypoint as well. We then define our `registry` block. This is used by the `build` step in Waypoint to push the built container image to a remote repository. This remote repository is the ECR repository we provisioned via Terraform.

Inside of the `deploy` block we make use of the `aws-ecs` plugin for Waypoint. Here we specify the ECS cluster we want to deploy our container to. Again, this is the cluster we provisioned earlier via Terraform. We also specify the `memory` we want our container to have, `512 MB`.

Now that we understand what is going on, let's deploy it. We first need to initialize Waypoint and then we can run the `up` command.

```bash
$ cd waypoint-demo
$ waypoint init
$ waypoint up
» Building...
Creating new buildpack-based image using builder: heroku/buildpacks:18
✓ Creating pack client
✓ Building image
 │ [exporter] Reusing layer 'config'
 │ [exporter] Adding label 'io.buildpacks.lifecycle.metadata'
 │ [exporter] Adding label 'io.buildpacks.build.metadata'
 │ [exporter] Adding label 'io.buildpacks.project.metadata'
 │ [exporter] *** Images (fa42ccc82d85):
 │ [exporter]       index.docker.io/library/next-ecs:latest
 │ [exporter] Reusing cache layer 'heroku/nodejs-engine:nodejs'
 │ [exporter] Reusing cache layer 'heroku/nodejs-engine:toolbox'
 │ [exporter] Reusing cache layer 'heroku/nodejs-engine:yarn'
 │ [exporter] Reusing cache layer 'heroku/nodejs-yarn:node_modules'
✓ Injecting entrypoint binary to image

Generated new Docker image: next-ecs:latest
Creating new buildpack-based image using builder: heroku/buildpacks:18✓ Creating pack client
✓ Building image
 │ [exporter] Reusing layer 'config'
 │ [exporter] Adding label 'io.buildpacks.lifecycle.metadata'
 │ [exporter] Adding label 'io.buildpacks.build.metadata'
 │ [exporter] Adding label 'io.buildpacks.project.metadata'
 │ [exporter] *** Images (fa42ccc82d85):
 │ [exporter]       index.docker.io/library/next-ecs:latest
 │ [exporter] Reusing cache layer 'heroku/nodejs-engine:nodejs'
 │ [exporter] Reusing cache layer 'heroku/nodejs-engine:toolbox'
 │ [exporter] Reusing cache layer 'heroku/nodejs-engine:yarn'
 │ [exporter] Reusing cache layer 'heroku/nodejs-yarn:node_modules'
✓ Injecting entrypoint binary to image

Generated new Docker image: next-ecs:latest
Tagging Docker image: next-ecs:latest => <aws-id>.dkr.ecr.us-west-2.amazonaws.com/nextjs-image:latest
Docker image pushed: <aws-id>.dkr.ecr.us-west-2.amazonaws.com/nextjs-image:latest

» Deploying...
✓ Found existing ECS cluster: waypoint-nextjs-cluster
✓ Found existing IAM role to use: ecr-next-ecs
✓ Created ALB target group
✓ Modified ALB Listener to introduce target group
✓ Configured security group: next-ecs-inbound-internal
✓ Created ECS Service (next-ecs-N345T9YF471RDNX395EXZE4, cluster-name: waypoint-nextjs-cluster)

» Releasing...

The deploy was successful! A Waypoint deployment URL is shown below. This
can be used internally to check your deployment and is not meant for external
traffic. You can manage this hostname using "waypoint hostname."

   Release URL: http://waypoint-ecs-next-ecs-708892391.us-west-2.elb.amazonaws.com
Deployment URL: https://violently-comic-wolf--v7.waypoint.run
```

After running `up` we should be able to hit the Release URL provided by Waypoint to see our running application. 

![Running app after Waypoint deployment](https://dev-to-uploads.s3.amazonaws.com/i/oi99hoj0h0qa3mk0tz4v.png)

### Cleanup

Now that we have things running and we know how to get a Next.js running in AWS via Waypoint, let's clean up all our resources. This is important to do so that we avoid spending unnecessary $$$ for a simple demo such as this.

![Cleanup](https://media.giphy.com/media/MC3jH6fgDZy92/giphy.gif)

To clean everything up we need to run two commands, one for Terraform and one for Waypoint.

First, we run `waypoint destroy` to clean up all our app related resources. Waypoint will remove the service that got created inside of the ECS cluster and nothing else.

```bash
$ cd waypoint-demo
$ waypoint destroy
» Destroying deployments...
Destroy successful!
```

Then we need to run `terraform destroy` from our `infrastructure` folder. This removes the ECS cluster and ECR repository that we created earlier.

```bash
$ cd waypoint-demo/infrastructure
$ terraform destroy
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_ecr_repository.nextjs-image-repo will be destroyed
  - resource "aws_ecr_repository" "nextjs-image-repo" {
      - arn                  = "arn:aws:ecr:us-west-2:249704159252:repository/nextjs-image" -> null
      - id                   = "nextjs-image" -> null
      - image_tag_mutability = "MUTABLE" -> null
      - name                 = "nextjs-image" -> null
      - registry_id          = "249704159252" -> null
      - repository_url       = "249704159252.dkr.ecr.us-west-2.amazonaws.com/nextjs-image" -> null
      - tags                 = {} -> null

      - encryption_configuration {
          - encryption_type = "AES256" -> null
        }

      - image_scanning_configuration {
          - scan_on_push = false -> null
        }
    }

  # aws_ecs_cluster.nextjs-cluster will be destroyed
  - resource "aws_ecs_cluster" "nextjs-cluster" {
      - arn                = "arn:aws:ecs:us-west-2:249704159252:cluster/waypoint-nextjs-cluster" -> null
      - capacity_providers = [] -> null
      - id                 = "arn:aws:ecs:us-west-2:249704159252:cluster/waypoint-nextjs-cluster" -> null
      - name               = "waypoint-nextjs-cluster" -> null
      - tags               = {} -> null

      - setting {
          - name  = "containerInsights" -> null
          - value = "disabled" -> null
        }
    }

Plan: 0 to add, 0 to change, 2 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

Destroy complete! Resources: 2 destroyed.
```

### Conclusion

In this post, we took Waypoint through a quick shakedown. We provisioned our infrastructure via Terraform. Then via a quick `waypoint.hcl` file we were able to deploy our application. Next.js was one example of an application that we can deploy but the options are rather limitless either.

We could define a Java application and deploy that as a container. We could deploy our applications to Kubernetes instead of an ECS cluster. We could build container images via Docker instead Cloud Native Buildpacks.

The moral of the story is that Waypoint is leaving the door open for flexibility. You can use any application or underlying infrastructure with it, in theory.

It's early days for Waypoint (it did just get released this week with a 0.1.1 release). That means there are some rough edges. 

For instance, while writing this blog post [I noticed that Waypoint did **not** work with existing ECS clusters](https://github.com/hashicorp/waypoint/issues/513) even though the documentation said it did.

In theory, Waypoint will support any type of application or underlying infrastructure. In its current form, that's not quite true. There are only a small handful of plugins for building and deploying applications. So things are a bit limited.

But, Waypoint is built on the same plugin concept as Terraform. This means that 3rd party providers will be able to create their own plugins for Waypoint. So if providers add their plugins, you will be able to deploy any application to any underlying infrastructure.

As a fan of HashiCorp offerings, I am excited to see what Waypoint holds for the future. There are many ways to deploy your applications today to wherever your compute lives. Waypoint is interesting. It is providing an opinionated approach that looks to provide structure while also leaving room for flexibility.

### Want to check out my other projects?
I am a huge fan of the DEV community. If you have any questions or want to chat about different ideas relating to refactoring, [reach out on Twitter](https://twitter.com/kylegalbraith) or drop a comment below.

Outside of blogging, I created a [Learn AWS By Using It course](https://kylegalbraith.com/learn-aws/). In the course, we focus on learning Amazon Web Services by actually using it to host, secure, and deliver static websites. It's a simple problem, with many solutions, but it's perfect for ramping up your understanding of AWS. I recently added two new bonus chapters to the course that focus on Infrastructure as Code and Continuous Deployment.