Guide to deploying NextJS on AWS ECS

Introduction

I already have an article guiding the deployment of a project using the NestJS framework on AWS ECS, while in this article we will go through how to deploy a frontend project using the NextJS framework also deploying on AWS ECS, the difference is mostly concentrated in building the docker image, as long as you can build the docker image and push successfully to AWS ECR, the next steps are almost identical

Prerequisites

Because there was a previous article providing quite comprehensive guidance on AWS ECS, in this article I might not instruct in detail about it much more, if there is any information that is not clear you can review previous articles to understand better

The goal of the article is to concentrate on the deployment so I will not dive deep into coding NextJS but will reuse from previous articles, you can also use a similar project according to your own needs


Detail

First of all, let's update the next.config.ts file to support standalone mode

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
output: 'standalone',
};

export default nextConfig;

The standalone mode helps NextJS automatically bundle only the truly necessary files to run the application (including node_modules) into a separate folder. This helps significantly reduce the Docker image size and optimize performance when running on container environments like AWS ECS.


Create a Dockerfile as follows

FROM node:22-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN npm ci

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]

The above code utilizes Multi-stage build technique to optimize the Docker image:

  • Stage 1 (deps): Install necessary libraries.
  • Stage 2 (builder): Build the application source code.
  • Stage 3 (runner): Create the final ultra-lightweight image, copying only built files and the server.js file to run the application, helping to speed up deployment and enhance security.


After that you should push this NextJS project's docker image to AWS ECR, you can do it manually or use AWS CDK that I have guided before

Next, in the AWS CDK project, let's create the file lib/ecs-fargate-fe-stack.ts

import * as cdk from "aws-cdk-lib"
import * as cloudfront from "aws-cdk-lib/aws-cloudfront"
import * as origins from "aws-cdk-lib/aws-cloudfront-origins"
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 iam from "aws-cdk-lib/aws-iam"
import { Construct } from "constructs"

export class EcsFargateFEStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)

const vpc = new ec2.Vpc(this, "NextVpc", {
maxAzs: 2,
natGateways: 0,
subnetConfiguration: [
{ name: "Public", subnetType: ec2.SubnetType.PUBLIC },
],
})

const imageUri = process.env.IMAGE || ""

const fargateService =
new ecs_patterns.ApplicationLoadBalancedFargateService(
this,
"NextService",
{
vpc,
cpu: 256,
memoryLimitMiB: 512,
assignPublicIp: true,
circuitBreaker: { rollback: true },
capacityProviderStrategies: [
{
capacityProvider: "FARGATE_SPOT",
weight: 1,
},
],
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry(imageUri),
containerPort: 3000,
},
},
)

fargateService.taskDefinition.addToExecutionRolePolicy(
new iam.PolicyStatement({
actions: [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
],
resources: ["*"],
}),
)

const distribution = new cloudfront.Distribution(this, "NextDist", {
defaultBehavior: {
origin: new origins.LoadBalancerV2Origin(fargateService.loadBalancer, {
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
}),
compress: true,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
},
})

new cdk.CfnOutput(this, "URL", {
value: `https://${distribution.distributionDomainName}`,
})

new cdk.CfnOutput(this, "DistributionId", {
value: distribution.distributionId,
description: "Cloudfront ID",
})

new cdk.CfnOutput(this, "ECSClusterName", {
value: fargateService.cluster.clusterName,
description: "The name of the ECS Cluster",
})

new cdk.CfnOutput(this, "ECSServiceName", {
value: fargateService.service.serviceName,
description: "The name of the ECS Service",
})
}
}

This CDK code performs the following steps to deploy the infrastructure:

  • Initialize cost-optimized VPC (no NAT Gateway used).
  • Deploy the application to AWS ECS Fargate using Fargate Spot mechanism to save up to 70% in costs.
  • Automatically create a Load Balancer to receive traffic.
  • Establish CloudFront as a CDN in front of the Load Balancer to accelerate access speed and support HTTPS.


Update file bin/aws-cdk.ts

#!/usr/bin/env node
import 'dotenv/config';
import * as cdk from "aws-cdk-lib/core"
import { EcsFargateFEStack } from '../lib/ecs-fargate/ecs-fargate-fe-stack';

const app = new cdk.App()
new EcsFargateFEStack(app, "EcsFargateFEStack")


Results when deploying

EcsFargateFEStack
Deployment time: 396.35s

Outputs:
EcsFargateFEStack.DistributionId = E2A6C5K3O5XFM9
EcsFargateFEStack.ECSClusterName = EcsFargateFEStack-EcsDefaultClusterMnL3mNNYNNextVpc981CFC80-rsVxhuv10rNn
EcsFargateFEStack.ECSServiceName = EcsFargateFEStack-NextServiceC767247E-ucZUk4CS2uyi
EcsFargateFEStack.NextServiceLoadBalancerDNS24BDD878 = EcsFar-NextS-I8dbPDVYDJxa-32810513.ap-southeast-1.elb.amazonaws.com
EcsFargateFEStack.NextServiceServiceURL4EADA39C = http://EcsFar-NextS-I8dbPDVYDJxa-32810513.ap-southeast-1.elb.amazonaws.com
EcsFargateFEStack.URL = https://d39v2olpbp7m6s.cloudfront.net

Total time: 413.94s


Verify results as follows


Happy coding!

See more articles here.

Comments

Popular posts from this blog

All Practice Series

Deploying a NodeJS Server on Google Kubernetes Engine

Kubernetes Deployment for Zero Downtime

Setting up Kubernetes Dashboard with Kind

Using Kafka with Docker and NodeJS

Monitoring with cAdvisor, Prometheus and Grafana on Docker

Practicing with Google Cloud Platform - Google Kubernetes Engine to deploy nginx

Kubernetes Practice Series

NodeJS Practice Series

Sitemap