How to deploy a Nextjs app in GitLab CICD pipeline

Steve Mu
2 min readApr 10, 2023

In this article, I am going to explain how I have deployed a Nextjs app with docker, GitLab CICD pipeline, and AWS CDK.

Generally, the steps are:

  1. create two repo. One is the Nextjs app repo. The other one is the AWS CDK repo and host both of them on GitLab.
  2. In the Nextjs app repo, set up the .gitlab-ci.yml file such that it triggers the pipeline of the CDK repo, which is a “downstream pipeline.”

This is my nextjs app’s .gitlab-ci.yml file (with sensitive info redacted):

default:
image: registry.gitlab.com/my-org/my-runner-image

stages:
- getAwsLogin
- buildAndPushToEcr
- deploy

getAwsLogin:
stage: getAwsLogin
only:
- prod
script:
- aws ecr get-login-password --region us-east-1 >> ecrPassword.txt
artifacts:
paths:
- ecrPassword.txt

buildAndPushToEcr:
stage: buildAndPushToEcr
image: docker:20.10.22-dind
services:
- docker:dind
only:
- prod
dependencies:
- getAwsLogin
script:
- docker build -t my-nextjs-app .
- cat ecrPassword.txt | docker login --username AWS --password-stdin xxx.dkr.ecr.us-east-1.amazonaws.com
- docker tag fin-space-prod:latest xxx.dkr.ecr.us-east-1.amazonaws.com/my-nextjs-app:$CI_COMMIT_SHORT_SHA
- docker push xxx.dkr.ecr.us-east-1.amazonaws.com/my-nextjs-app:$CI_COMMIT_SHORT_SHA

triggerDeploy:
stage: deploy
only:
- prod
variables:
imageTag: $CI_COMMIT_SHORT_SHA
# imageTag: d41c99e0
trigger:
project: my-org/my-cdk-app
strategy: depend

What this is doing is: whenever someone pushes to to “prod” branch, first it gets the ECR password (aws credential should be set in the GitLab console so it is available in the environment), then it builds the docker image and tag it with the commit sha, and push the image to ECR, then it triggers the AWS CDK repo pipeline with the commit sha as image tag passed to the downstream pipeline.

The downstream pipeline will get the docker image from ECR with the image tag and update the AWS infrastructure (ECS with Fargate) to use the new docker image. For a detailed explanation of how this works, please refer to my article on creating the AWS CDK project.

This is my nextjs app dockerfile:

# Install dependencies only when needed

# using alpine3.16 because of https://github.com/prisma/prisma/issues/16834
FROM node:16-alpine3.16 AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* ./
RUN yarn --frozen-lockfile

COPY prisma ./prisma
RUN npx prisma generate

# Rebuild the source code only when needed
FROM node:16-alpine3.16 AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/arcgis-core-with-next-js-build-in-docker-fails-due/td-p/1046490 for fix if there is build error
RUN yarn build

# Production image, copy all the files and run next
FROM node:16-alpine3.16 AS runner
WORKDIR /app

ENV NODE_ENV production

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

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

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

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

--

--