Deploy Node.js + Express on Amazon ECS Fargate with AWS Copilot
Tags: aws, ecs, node, typescript
Published: 2022-01-06 14:13:47

Learn how to deploy Node.js [Express](https://expressjs.com/) to [Amazon ECS](https://aws.amazon.com/jp/ecs/) Fargate using [AWS Copilot](https://aws.amazon.com/jp/containers/copilot/).

---

## Enviroments

AWS CLI and Copilot CLI are already installed and configured, and Docker Desktop for Mac is used for local testing.

- AWS CLI

```
$ aws --version
aws-cli/2.2.35 Python/3.8.0 Darwin/20.5.0 source/x86_64 prompt/off
```

- Copilot CLI

```shell
$ copilot --version
Copilot version: v1.13.0
```

- Docker Engine

```
$ docker --version
Docker version 20.10.11, build dea9396
```

- maOS 11.4

---

## Setup Express

Create a working directory and set up Express to work with TypeScript.

```shell
$ mkdir copilot-express && cd copilot-express

$ npm init -y

$ npm install -D typescript ts-node @types/node @types/node @types/express

$ npm install express

$ npx tsc --init
```

### Create `index.ts`

```shell
$ touch index.ts
```

In the created `index.ts`, we will start Express and define a GET request handler for `/`.

```typescript
import express from 'express';
import { Request, Response } from 'express';

const app = express();
const port = 3000;

app.get('/', (req: Request, res: Response) => res.send('Express app works!'));

app.listen(port, () => console.info(`Express listening on port ${port}!`));
```

### Add to startup script

Add `"start": "ts-node index.ts"` to scripts in package.json.

```json
{
  "scripts": {
    "start": "ts-node index.ts"
  }
}
```

### Create Dockerfile for Express

```shell
$ touch Dockerfile
```

```dockerfile
FROM node:16

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]
```

### Docker image build & run

```shell
$ docker build -t copilot-express .

$ docker images | grep copilot-express

$ docker run -p 3000:3000 -d copilot-express
```

Access to http://localhost:3000 and if you see `Express app works!` you're ready to go.

---

## Deploy to ECS with Copilot

### Copilot concepts

Copilot has three main concepts: Service, Environment, and Application.

![Copilot concepts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nvd8u80iv5x9knv8ljfg.png)

"Service" is the application that runs in the container. In this case, we will create a Service named "api".

"Environment" is an environment such as test/staging/production. In this case, we will create an Environment named "test".

"Application" is a concept that combines Service and Environment. In this case, we will create an Application with the name "copilot-express".

In this case, we will have one Service, one Environment, and one Application.

Learn more: https://aws.github.io/copilot-cli/docs/concepts/environments/

### Run `copilot init`

Run `copilot init` to initialize & deploy.
You will be asked several questions to answer.

#### Type `copilot-express` when asked for the Application name.

```shell
$ copilot init

Note: It's best to run this command in the root of your Git repository.
Welcome to the Copilot CLI! We're going to walk you through some questions
To help you get set up with a containerized application on AWS. An application is a collection of
An application is a collection of containerized services that operate together.

What would you like to name your application? for help] copilot-express
```

#### Select `Load Balanced Web Service` when asked for the workload type.

```shell
Which workload type best represents your architecture? for more help].
  Request-Driven Web Service (App Runner)
> Load Balanced Web Service (Internet to ECS on Fargate)
  Backend Service (ECS on Fargate)
  Worker Service (Events to SQS to ECS on Fargate)
  Scheduled Job (Scheduled event to State Machine to Fargate)
```

#### Type `api` when asked for the Service name.

```shell
What do you want to name this service? for help] api
```

#### Select `./Dockerfile` when asked which Dockerfile to use.

```shell
Which Dockerfile would you like to use for api? [Use arrows to move, type to filter, ? for more help]
  > ./Dockerfile
    Enter custom path for your Dockerfile
    Use an existing image instead
```

#### This time, we're deploying to a test environment, so enter `y`.

```shell
All right, you're all set for local development.

Would you like to deploy a test environment? for help] (y/N) y
```

After answering the questions, it will start to build the environment with IAM Role, Subnet, VPC, etc. Wait for a while.

After the environment is built, the Docker image will be pushed to ECR and deployment to ECS will start.

```shell
✔ Linked account AWS_ACCOUNT_ID and region ap-northeast-1 to application copilot-express..

✔ Proposing infrastructure changes for the copilot-express-test environment.

✔ Created environment test in region ap-northeast-1 under application copilot-express.

✔ Deployed service api.
Recommended follow-up action:
  - You can access your service at http://xxxxx.ap-northeast-1.elb.amazonaws.com over the internet.
```

Access the URL displayed and if you see `Express app works!` as you did locally, deployment is complete!

### Remove the Copilot Application

After confirming the operation and the status of the application actually being deployed to ECS.
Delete the Application created this time.

```shell
$ copilot app delete
```

---

## Summary

Once I learned the concept of Copilot (similar to Beanstalk) and the steps, I found it to be a great way to deploy containers to ECS!

In actual production, Copilot will also include CI/CD and [Sidecar pattern](https://aws.github.io/copilot-cli/docs/developing/sidecars/), so I'll be experimenting with that as well.