Skip to content

Commit

Permalink
replace open-next with aws lambda-adapter extension
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanstitt committed Sep 24, 2024
1 parent cec6e9d commit 5389a82
Show file tree
Hide file tree
Showing 6 changed files with 1,615 additions and 279 deletions.
66 changes: 29 additions & 37 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
# mostly following:
# https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
FROM public.ecr.aws/docker/library/node:20.9.0-slim AS base

FROM node:20 as base
RUN mkdir /app
WORKDIR /app

FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.

# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci

# build the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps ./node_modules ./node_modules
FROM base as builder

COPY . .
COPY --from=deps /app/node_modules ./node_modules

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# the following line disables telemetry during the build.
# declare the sharp path to be /tmp/node_modules/sharp
ENV NEXT_SHARP_PATH=/tmp/node_modules/sharp
# install the dependencies and build the app
# disables nextjs telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED=1

RUN npx open-next@latest build

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
FROM public.ecr.aws/docker/library/node:20.9.0-slim AS runner
# install aws-lambda-adapter extension
# https://aws.amazon.com/blogs/compute/working-with-lambda-layers-and-extensions-in-container-images/
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
# expose port 3000 and set env variables
ENV PORT=3000 NODE_ENV=production
ENV AWS_LWA_ENABLE_COMPRESSION=true
WORKDIR /app

ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1
FROM base as release

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY ./bin/lambda-server.sh ./run.sh

# copy static files and images from build
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
RUN ln -s /tmp/cache ./.next/cache

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# 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

ENV PORT=3000
EXPOSE 8080

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
ENV HOSTNAME="0.0.0.0"
CMD ["node", ".next/standalone/server.js"]
CMD exec ./run.sh
90 changes: 90 additions & 0 deletions bin/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { execSync } from 'child_process';
import { ECRClient, DescribeImagesCommand } from '@aws-sdk/client-ecr';
import { LambdaClient, UpdateFunctionCodeCommand } from '@aws-sdk/client-lambda';
import { fromIni } from "@aws-sdk/credential-providers"
import dayjs from 'dayjs';


// Helper function to execute shell commands
// eslint-disable-next-line
const exec = (command: string) => execSync(command, { stdio: 'inherit' });

// Get todays ISO date for tagging
const getCurrentDate = () => dayjs().format('YYYY-MM-DD');

// Function to build and tag the Docker image
const buildAndTagDockerImage = async (repository: string, tag: string) => {
console.log(`Building Docker image and tagging with ${tag}`);
exec(`docker build --platform='linux/amd64' -t ${repository}:${tag} .`);
};

// Check if the tag exists on ECR
const tagExistsOnECR = async (ecrClient: ECRClient, repositoryName: string, tag: string): Promise<boolean> => {
try {
const data = await ecrClient.send(new DescribeImagesCommand({
repositoryName,
imageIds: [{ imageTag: tag }]
}));
return (data.imageDetails?.length || 0) > 0
} catch (error) {
return false;
}
};

// Function to push the image to ECR
const pushDockerImageToECR = (repository: string, tag: string) => {
console.log(`Pushing image ${repository}:${tag} to ECR`);
exec(`docker push ${repository}:${tag}`);
};

// Update Lambda function to use the new image
const updateLambdaFunction = async (lambdaClient: LambdaClient, functionName: string, imageUri: string) => {
console.log(`Updating Lambda function ${functionName} to use image ${imageUri}`);
await lambdaClient.send(new UpdateFunctionCodeCommand({
FunctionName: functionName,
ImageUri: imageUri
}));
};

// Main function
const main = async () => {
const profile = process.argv.includes('--profile') ? process.argv[process.argv.indexOf('--profile') + 1] : 'default';
if (profile == 'default') {
console.log('Using default profile, you may need to specify --profile');
}
const region = 'us-east-1';

const repository = '905418271997.dkr.ecr.us-east-1.amazonaws.com/mgmt-app-worker';

exec(`aws --profile ${profile} ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${repository.split('/')[0]}`)

const functionName = 'ManagementApp';

const credentials = fromIni({ profile });

// Configure AWS SDK to use the given profile and region
const ecrClient = new ECRClient({ region: region, credentials })
const lambdaClient = new LambdaClient({ region: region, credentials })

let tag = getCurrentDate();
let suffix = 'a';

// Check if the image tag already exists in ECR and increment if necessary
while (await tagExistsOnECR(ecrClient, 'mgmt-app-worker', tag)) {
tag = `${getCurrentDate()}-${suffix}`;
suffix = String.fromCharCode(suffix.charCodeAt(0) + 1); // Increment suffix (a -> b -> c ...)
}


buildAndTagDockerImage(repository, tag);
pushDockerImageToECR(repository, tag);

await updateLambdaFunction(lambdaClient, functionName, `${repository}:${tag}`);

console.log('Lambda function updated successfully.');
};

main().catch((error) => {
process.stderr.write(`Error: ${error.message}\n`);
process.exit(1);
})
9 changes: 9 additions & 0 deletions bin/lambda-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

set -e

[ ! -d '/tmp/cache' ] && mkdir -p /tmp/cache

export PORT=8080

exec node server.js
4 changes: 3 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin'
const withVanillaExtract = createVanillaExtractPlugin()

/** @type {import('next').NextConfig} */
const nextConfig = {}
const nextConfig = {
output: 'standalone',
}

export default withVanillaExtract(nextConfig)
Loading

0 comments on commit 5389a82

Please sign in to comment.