Last week HashiCorp launched its latest open source project, 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.
Next, we need to have our AWS CLI configured. These docs show you how to get your CLI configured.
Finally, we need to install waypoint
. This can be done a number of ways as these docs mention. If you're on a Mac, you can run these two commands in brew
to get it installed.
$ 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.
$ 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
.
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
.
$ 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.
$ 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
.
$ 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.
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.
$ 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.
resource "aws_ecr_repository" "nextjs-image-repo" {
name = "nextjs-image"
}
We can run terraform apply
one more time to create our image repository.
$ 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.
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 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.
$ 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.
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.
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.
$ 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.
$ 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 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 or drop a comment below.
Outside of blogging, I created a Learn AWS By Using It course. 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.