Scenario
-
My teamate used my template to run an express application in zipped fashion.
-
One day we are asked to create
pdf
usingreact-pdf
andsharp
for resizing images. -
The
sharp
dependency must be built by linux machine, luckily we can find asharp-layer.zip
online and simply upload it to our lambda layer registry. -
Zip-based lambda function will combine lambda layer when finalizing the total size, unfortunately however hard we try we still exceed the 250MB limit by 6MB.
The next comes to the rescue!
Running Docker Image by Lambda Function
This approach solve our problem in two-fold:
- For one it bypasses the 250MB limit constriant;
- For second it helps solve the complicated dependencies problem because we install everything we need when building the image.
Serverless Framework Version
-
https://www.npmjs.com/package/serverless
Let's stay at
3.38.0
as higher version needs login-credentials.
package.json
{ "scripts": { "build": "tsc", ... }, ... }
serverless.yml
Be careful the highlighted lights must be the same:
service: some-pdf-generator provider: name: aws runtime: nodejs18.x stage: prod region: ap-southeast-2 timeout: 900 iam: role: name: ${self:service}-${self:provider.stage}-role environment: FILE_STORAGE: /tmp/files PORT: 3033 FRONTEND_URL: some-url ecr: # This allows Serverless to automatically handle image pushing images: nodejs-pdf-generator: path: ./ functions: api: image: name: nodejs-pdf-generator command: - dist/server.handler timeout: 900 events: - http: ANY / - http: ANY /{proxy+}
Dockerfile at the Project Root Directory
FROM public.ecr.aws/lambda/nodejs:18 RUN npm install --arch=x64 --platform=linux sharp COPY package*.json ./ RUN cat package.json | sed "/^ *\"sharp/d" > package.json.tmp && mv package.json.tmp package.json RUN npm install RUN npm install --arch=x64 --platform=linux sharp COPY . . # Compile TypeScript to JavaScript RUN npm run build # Set the CMD to your handler CMD [ "dist/server.handler" ]
Dependencies
{ ... "dependencies": { "@react-pdf/renderer": "^3.4.4", "@types/express": "^4.17.21", "@types/probe-image-size": "^7.2.4", "aws-sdk": "^2.1534.0", "axios": "^1.6.4", "express": "^4.18.2", "serverless": "^3.38.0", "serverless-http": "^3.2.0", "ts-node": "^10.9.2", "typescript": "^5.3.3" }, "devDependencies": { "@babel/core": "^7.25.2", "@types/node": "^20.11.0", } }
Entry Files
app.ts
// app.ts import express, { Request, Response } from "express"; export const app = express(); app.get("/", (req, res) => { res.json({ success: true, version: "0.3", }); }); app.post("/some-endpoint", some.controller); const PORT = Number(process.env?.PORT || "3000"); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); process.on("uncaughtException", function (err) { console.log(err.stack); console.log("Node NOT Exiting..."); });
server.ts
// server.ts import { app } from "./app"; import * as serverless from "serverless-http"; export const handler = serverless.default(app);