2022 Guide to deploy nextJS containers on Fargate with CDK

2022 Guide to deploy nextJS containers on Fargate with CDK

Ultimate CDK deployment guide for Fargate. It works for any nodeJS Container too.

Few days back I was struggling to publish my nextJS application on Fargate with CDK. It was pretty confusing, since AWS CDK versions have changed and there are no latest blogs on that. AWS has done a pretty awesome job in documenting, but has not done any practical examples that may be useful.

So, here's what I wanted to do.

  1. I have a nextJS application. I want to containerize it.
  2. I want to push the container image to ECR.
  3. I want to use CDK to
    1. create a VPC,
    2. create a ecr cluster
    3. launch a load balanced fargate service, and deploy the container images there.

Now, I do not wan't to do a CI/CD. I want to keep the build and deployment seperate. So, I will be creating the nextJS app, build a docker image and push it to ECR first, and then I'll deploy it.

So, lets begin.

Assumptions

  1. You already have latest nodeJS LTS version installed. I have nodeJS v16.x installed.
  2. You already have a working AWS account.
    1. You'll need to install aws cli in your system, and configure it (Follow docs.aws.amazon.com/cli/latest/userguide/cl..)
    2. You'll need to install aws cdk (npm install -g aws-cdk)
  3. You already have Docker installed in your system.
  4. VSCode, of course.

Create a nextJS application and containerize it

Lets create a nextJS app

creating a nextJS app

It should be easy. In your console, just type

$ npx create-next-app

and go with the defaults. You'll get the output like

✔ What is your project named? … my-app
Creating a new Next.js app in /home/pluto/GET_RECYCLED/test/my-app.

Using npm.
....

Success! Created my-app at /home/my-app
Inside that directory, you can run several commands:

  npm run dev
    Starts the development server.

  npm run build
    Builds the app for production.

  npm start
    Runs the built app in production mode.

We suggest that you begin by typing:

  cd my-app
  npm run dev

give it a test run.

$ cd my-app
$ npm run dev

and open your browser with http://localhost:3000

You'll get the following in your browser

image.png

Lets containerize this nextJS app

In your console, create Dockerfile

~/my-app$ touch Dockerfile

Open the Dockerfile in vscode and paste the following

FROM node:16.14-alpine3.14
# Setting working directory. All the path will be relative to WORKDIR
WORKDIR /usr/src/app
# Installing dependencies
COPY package*.json ./
RUN npm install
# Copying source files
COPY . .
# Building app
RUN npm run build
EXPOSE 3000
# Running the app
CMD [ "npm", "start" ]

Also, create a .dockerignore file

touch .dockerignore

and in your .dockerignore file, put the following

node_modules
npm-debug.log

NextJS has a official containerization documentation, but the image becomes pretty heavy. We used a simplified Dockerfile here.

Ok, now lets build this image and run it.

$ docker build -t testimage .

After the build is complete, check the image in the local repository

~/my-app$ docker images
REPOSITORY                                                    TAG       IMAGE ID       CREATED          SIZE
testimage                                                     latest    2a8c89ce7dd8   59 seconds ago   375MB

Give it a test run in your system

~/my-app$ docker run -p 8000:3000 -d testimage

and open http://localhost:8000 in your browser. you'll find your app is running.

Ok, so far so good. lets push tag this, and push this to ECR.

Pushing the image to ECR

prerequisites for this step

  1. Install aws cli
  2. Configure aws cli. Follow docs.aws.amazon.com/cli/latest/userguide/cl..

Now log in to aws console and go to ECR

Click on Create Repository image.png

Name the repo as test-repository and click on Create Repository button. Leave everything else as it was. image.png

In the Private Repositories Page, click on the repository name. image.png

You'll see that there are no images. Click on View push commands image.png

execute these one after another to push the locally built image to ECR. image.png

Now that the image is pushed to the repository, we need the ARN of the repository Creating the ARN of the repo is easy run this on your console

$ aws ecr describe-repositories --repository-names test-repository
{
    "repositories": [
        {
            "repositoryArn": "arn:aws:ecr:ap-south-1:112233445566:repository/test-repository",
            ...
        }
    ]
}

Make a note of this ARN(arn:aws:ecr:ap-south-1:112233445566:repository/test-repository), Youll need this.

Now use CDK to pull this image and deploy in fargate

Install CDK

npm install -g aws-cdk

Do CDK Init and create the CDK application

$ mkdir test-cdk && cd test-cdk
$ cdk init --language typescript

Now, open lib/cdk_part-stack.ts

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';

export class TestCdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here

    // example resource
    // const queue = new sqs.Queue(this, 'TestCdkQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

Change it to following

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ecs_patterns from "aws-cdk-lib/aws-ecs-patterns";
import * as ecr from 'aws-cdk-lib/aws-ecr';


export class TestCdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here

    const vpc = new ec2.Vpc(this, "MyVpc", {
      maxAzs: 3 // Default is all AZs in region
    });

    const cluster = new ecs.Cluster(this, "MyCluster", {
      vpc: vpc
    });

//use the ARN you noted down earlier

    const repository = ecr.Repository.fromRepositoryArn(this, "MyImage", "arn:aws:ecr:ap-south-1:112233445566:repository/test-repository")

    // Create a load-balanced Fargate service and make it public
    new ecs_patterns.ApplicationLoadBalancedFargateService(this, "MyFargateService", {
      cluster: cluster, // Required
      cpu: 512, // Default is 256
      desiredCount: 2, // Default is 1
      taskImageOptions: { image: ecs.ContainerImage.fromEcrRepository(repository, "latest"), containerPort: 3000 },
      memoryLimitMiB: 2048, // Default is 512
      publicLoadBalancer: true // Default is false
    });
  }
}

Now save this, and run in your console

$ cdk synth
$ cdk bootstrap
$ cdk deploy

It'll take a while here, about 2-3 mins, and if all goes well, you'll get the following output on your console.


 ✅  TestCdkStack

✨  Deployment time: 320.94s

Outputs:
TestCdkStack.MyFargateServiceLoadBalancerDNS123D1234 = TestC-MyFar-AAAABBBBCCCC-123412341234.ap-south-1.elb.amazonaws.com
TestCdkStack.MyFargateServiceServiceURL4CF8398A = http://TestC-MyFar-AAAABBBBCCCC-123412341234.ap-south-1.elb.amazonaws.com
Stack ARN:
arn:aws:cloudformation:ap-south-1:112233445566:stack/TestCdkStack/5abc2fe0-d72f-11ex-82b7-02fda60e87d4

✨  Total time: 332.45s

You can use this http://TestC-MyFar-AAAABBBBCCCC-123412341234.ap-south-1.elb.amazonaws.com on your browser and you'll find your nextJS application running.

Alternatively, you can go to EC2 and go to load balancers in AWS Console image.png

Copy this DNS Name image.png

And put it in your browser. You'll see your nextJS application running just fine.

Did you find this article valuable?

Support Tirtha Guha by becoming a sponsor. Any amount is appreciated!