<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: jonasgroendahl</title>
    <description>The latest articles on Forem by jonasgroendahl (@jonasgroendahl_).</description>
    <link>https://forem.com/jonasgroendahl_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1443882%2F5ea922c0-9386-48dd-832a-f20f5b731b2d.jpeg</url>
      <title>Forem: jonasgroendahl</title>
      <link>https://forem.com/jonasgroendahl_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jonasgroendahl_"/>
    <language>en</language>
    <item>
      <title>How to: Puppeteer in AWS Docker Lambda</title>
      <dc:creator>jonasgroendahl</dc:creator>
      <pubDate>Thu, 28 Nov 2024 11:31:14 +0000</pubDate>
      <link>https://forem.com/jonasgroendahl_/puppeteer-in-docker-lambda-1b47</link>
      <guid>https://forem.com/jonasgroendahl_/puppeteer-in-docker-lambda-1b47</guid>
      <description>&lt;p&gt;I was using another great library called &lt;a href="https://github.com/measuredco/puck" rel="noopener noreferrer"&gt;puck&lt;/a&gt;, which is basically a very customisable editor, that can create sites/newsletters/pdfs (something visual) and came to the next step which was turning the output of the editor into a PDF.&lt;/p&gt;

&lt;p&gt;The best option out there seemed to puppeteer for PDF generation based on HTML. Now I didn't have a server running anywhere, so far I was mostly creating small lambdas for any server side functionality for my app, so I wanted to leverage lambda for this use case also. &lt;/p&gt;

&lt;p&gt;It turned out to be a bit more trial and error than expected since puppeteer relies on chromium and that doesn't work great out of the box with serverless environments but here is how I got it working.&lt;/p&gt;

&lt;p&gt;First install required dependencies in a new Node.js project (initialised using &lt;code&gt;npm init -y&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install puppeteer-core@23.9.0 
npm install @sparticuz/chromium@131.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we're going to be installing this into a lambda environment, the regular &lt;code&gt;puppeteer&lt;/code&gt; package will not work (at least for me) so we are installing the core library together with a version of chromium that works better in lambda.&lt;/p&gt;

&lt;p&gt;Here the versions are quite important so I'm locking those in. &lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;index.js&lt;/code&gt; file with following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import puppeteer from "puppeteer-core";
const chromium = require("@sparticuz/chromium"); // this looks a bit odd with 'import' and 'require' statements but I was using TypeScript so everything compiled down to commonjs in the end. @sparticuz/chromium didn't support ESM

chromium.setHeadlessMode = true;
chromium.setGraphicsMode = false;

const browser = await puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
});
const page = await browser.newPage();

await page.setContent("&amp;lt;html&amp;gt;&amp;lt;p&amp;gt;Hello world&amp;lt;/p&amp;gt;&amp;lt;/html&amp;gt;");

const pdf = await page.pdf({ format: "A4" });
await browser.close();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running your &lt;code&gt;index.js&lt;/code&gt; locally should yield a positive result. Now putting this code into AWS lambda, we need to create &lt;code&gt;Dockerfile&lt;/code&gt; since we'll create a docker lambda which should make things simpler. One benefit is that we don't need to put Chromium code in lambda layer but other than that it's really up to you as the developer whether you prefer to go with a vanilla lambda or lambda docker approach.&lt;/p&gt;

&lt;p&gt;Here's our simple Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use the official Amazon Linux image for Lambda
FROM public.ecr.aws/lambda/nodejs:18

# Set working directory
WORKDIR ${LAMBDA_TASK_ROOT}

COPY . .

RUN npm install
RUN npm run build # i was using typescript so here i run the build command 'tsc', you can omit it if you're doing JS


# Your CMD or ENTRYPOINT command
CMD ["index.handler"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a Dockerfile and &lt;code&gt;index.js&lt;/code&gt; file at the ready, we can build our Docker image using command below (make sure Docker is installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build --platform linux/amd64 -t &amp;lt;name&amp;gt; .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we are targeting &lt;code&gt;linux/amd64&lt;/code&gt; which is the default lambda environment. &lt;br&gt;
Also to note is that I'm on a Macbook Pro M1 locally so this is a requirement. If you are on Linux, perhaps you dont need to do this but just to be sure.&lt;/p&gt;

&lt;p&gt;The next steps require you to have AWS lambda setup in place and have knowledge of setting up ECR &amp;amp; Lambda which means this guide could become lengthy so I'll leave with a few words of advice on next steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;After building the docker image, tag docker image using command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker tag &amp;lt;name&amp;gt;:latest &amp;lt;aws_account&amp;gt;.dkr.ecr.&amp;lt;region&amp;gt;.com/&amp;lt;ecr_repo&amp;gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Push docker image
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push &amp;lt;aws_account&amp;gt;.dkr.ecr.&amp;lt;aws_region&amp;gt;.amazonaws.com/&amp;lt;ecr_repo&amp;gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(These commands are also visible in AWS console when creating your ECR repo)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update lambda with image using following AWS CLI command (assumes the AWS CLI is installed on your machine). You can also manually update in the console if you prefer:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws lambda update-function-code \
  --function-name &amp;lt;lambda_name&amp;gt; \
  --image-uri &amp;lt;aws_account&amp;gt;.dkr.ecr.&amp;lt;aws_region&amp;gt;.amazonaws.com/&amp;lt;ecr_repo&amp;gt;:latest \
  --profile default \
  --region &amp;lt;aws_region&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Ensure lambda has 1024 MB memory at least and timeout of 30 seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hope this was useful!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>docker</category>
      <category>lambda</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
