Chromium pesa all’incirca 280 MB. Il limite di AWS Lambda per i pacchetti decompressi è 250 MB. Se hai mai provato a fare npm install puppeteer e a distribuire tutto direttamente su Lambda, sai già com’è andata: semplicemente non ci stai dentro.
Ho perso abbastanza tempo a fare debug di errori tipo "Failed to launch the browser process" alle 2 di notte per sapere che questo tema merita un confronto serio, non l’ennesimo tutorial che mostra un solo metodo e ti lascia intuire gli altri due. Quindi eccolo qui: Layers contro Container Image contro upload ZIP diretto, una matrice di compatibilità aggiornata al 2026 e una sezione di troubleshooting per i cinque errori che, statisticamente, hai più probabilità di incontrare.
Cos’è Puppeteer su AWS Lambda e perché usarlo
Puppeteer è una libreria Node.js che controlla Chromium in modalità headless tramite il Chrome DevTools Protocol. Lambda è il compute serverless di AWS: paghi per invocazione, scala da solo e non devi mai gestire un server. Mettendoli insieme ottieni una configurazione per l’automazione browser capace di far girare centinaia di esecuzioni in parallelo senza dover provisionare neppure una singola istanza EC2.
I casi d’uso sono quelli che tornano sempre nei team: web scraping, generazione di screenshot e PDF, monitoraggio sintetico, pre-rendering di applicazioni single-page per la SEO e test UI automatizzati. Il problema è sempre lo stesso, quello già citato sopra: le dimensioni di Chromium contro i limiti di package di Lambda. Ecco perché nessuno distribuisce puppeteer completo su Lambda, visto che include anche il download di Chromium. Al suo posto si usa puppeteer-core (senza browser incluso) insieme a un binario Chromium ottimizzato per Lambda, quasi sempre @sparticuz/chromium.
Questa sola sostituzione — puppeteer-core al posto di puppeteer — risolve l’80% del problema di dimensione ancora prima di scrivere una riga di configurazione per il deployment.
Layers, Container Image o ZIP: scegli il tuo approccio prima di tutto
C’è una cosa che mi ha dato fastidio mentre facevo ricerca: quasi tutte le guide esistenti coprono esattamente un solo metodo di distribuzione. I tutorial AWS SAM usano i Layers. Gli esempi CDK usano Docker. Un post qualunque su Substack usa uno ZIP grezzo con un binario Chromium ospitato su S3. Nessuno li mette davvero a confronto, e così la prima decisione importante — quale metodo di deployment si adatta al mio caso? — viene saltata del tutto.
Risolviamolo.
| Criterio | Lambda Layers | Container Image (Docker) | Upload ZIP diretto |
|---|---|---|---|
| Dimensione massima | 250 MB decompressi (tra tutti i layer) | Image da 10 GB | 250 MB decompressi |
| Complessità di deploy | Media (gestione ARN del layer) | Più alta (Dockerfile + push su ECR) | Più bassa (zip e upload) |
| Impatto sul cold start | Moderato | Leggermente più alto (pull dell’immagine più grande) | Moderato |
| Aggiornamento Chromium | Ripubblichi la versione del layer | Ricostruisci l’immagine | Ricarichi lo zip |
| Ideale per | Prototipi rapidi, utenti Serverless Framework | Carichi di produzione, team con CI Docker | Funzioni semplici e isolate |
| Supporto IaC | SAM, Serverless Framework | CDK, SAM, Terraform | Console, qualsiasi IaC |
I limiti da 250 MB e 10 GB arrivano direttamente dalla documentazione ufficiale dei limiti di AWS Lambda: non è un numero cambiato di recente, ma è il vincolo che decide fin da subito l’intera strategia di deployment.
Il mio criterio pratico, per quello che vale: se stai prototipando o usi già Serverless Framework, parti dai Layers. Se vai in produzione e il tuo team ha già una pipeline Docker CI/CD, scegli Container Image: il tetto dei 10 GB ti lascia molto più margine. Se ti serve solo una funzione che faccia screenshot ogni tanto, l’upload ZIP diretto è la strada più semplice.
Tutte e tre le strade usano la stessa combinazione di base: puppeteer-core + @sparticuz/chromium. Cambia il modo in cui impacchetti questa coppia, non ciò che impacchetti.

La matrice di compatibilità 2026 (basta andare a tentativi)
Questa è la parte che, in pratica, fa perdere mesi e non ore. La lamentela più frequente su Stack Overflow e nei ticket GitHub non è "come faccio a distribuirlo", ma "perché il deployment che funzionava si è rotto in silenzio dopo un aggiornamento npm". Il colpevole, quasi sempre, è un disallineamento tra @sparticuz/chromium, puppeteer-core e il runtime Node.js.
Prima, una nota fondamentale: chrome-aws-lambda (il pacchetto originale di alixaxel) è deprecato. Non funziona bene su Node 18+ e non è rimasto al passo con le release di Chromium. Se trovi una guida che lo cita, chiudi la scheda: è materiale vecchio. Ogni guida aggiornata dovrebbe indirizzarti a @sparticuz/chromium.
Usa questa regola di compatibilità invece di abbinare i major a occhio:
| Componente | Regola di versione | Cosa verificare prima del deploy |
|---|---|---|
puppeteer-core | Scegli la versione di Puppeteer richiesta dalla tua applicazione | Controlla quale build di Chromium è supportata da quella release di Puppeteer |
@sparticuz/chromium | Il suo major segue il major di Chromium, non quello di Puppeteer | Allinealo alla build di Chromium indicata nella tabella di supporto di Puppeteer e leggi le note di release di Sparticuz |
| Runtime Node.js di AWS Lambda | Usa un runtime Lambda ancora supportato | Esegui un test di invocazione dopo ogni aggiornamento del runtime o del pacchetto |
| Architettura | Il pacchetto npm include binari x64; il supporto arm64 parte con Chromium v135 tramite un layer o remote pack arm64 | Fai combaciare esattamente architettura Lambda, artifact del layer/pack e versione di Chromium |
Non imposto qui una coppia di pacchetti precisa di proposito, perché @sparticuz/chromium segue il ciclo di release di Chromium e non usa il classico semantic versioning. Parti dalla pagina ufficiale Puppeteer Chromium Support, annota il major di Chromium supportato dalla versione di Puppeteer che hai scelto e poi seleziona il major corrispondente di @sparticuz/chromium. Infine, leggi le release notes di Sparticuz per eventuali breaking change a livello di patch e per i dettagli sull’architettura. Non installare lo stesso numero major per entrambi i pacchetti, a meno che quella corrispondenza non sia confermata da quelle due fonti.

Come distribuire Puppeteer su AWS Lambda con i Lambda Layers
Un Lambda Layer ti permette di impacchettare Chromium separatamente dal codice della funzione. In questo modo il tuo handler resta leggero e puoi riutilizzare lo stesso layer Chromium su più funzioni. È la cosa più vicina a un "quick start" in tutto questo ecosistema.
Passo 1: installa puppeteer-core e il pacchetto -min
Quando i file di Chromium vivono in un Lambda Layer, tieni piccolo il package della funzione usando @sparticuz/chromium-min. Sostituisci i placeholder con le versioni compatibili che hai verificato sopra:
npm install puppeteer-core@$PUPPETEER_VERSION \
@sparticuz/chromium-min@$CHROMIUM_VERSION
Stai installando puppeteer-core — non puppeteer — perché salta il download automatico del browser. Il pacchetto -min fornisce gli helper di avvio, mentre il layer fornisce i file Chromium compressi con Brotli sotto /opt/chromium.
Passo 2: crea o usa un Lambda Layer per Chromium
Usa l’archivio del layer specifico per architettura allegato a una release ufficiale di Sparticuz, oppure costruisci l’archivio dal repository ufficiale. Per Lambda x86_64, il build documentato è:
git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip
Questo produce chromium.x64.zip. Caricalo su S3 e pubblicalo come Lambda Layer con runtime e architettura effettivamente in uso. Per arm64, usa l’artifact arm64 corrispondente o il target di build dedicato; non associare un archivio x64 a una funzione arm64.
Se usi SAM, collega direttamente l’ARN del layer nel tuo template.yaml:
Resources:
PuppeteerFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1
Passo 3: scrivi l’handler Lambda
Ecco un pattern di handler funzionante che visita una URL e restituisce il titolo della pagina:
import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium-min";
export const handler = async () => {
const browser = await puppeteer.launch({
args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath("/opt/chromium"),
headless: "shell",
});
try {
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
return { title: await page.title() };
} finally {
await browser.close();
}
};
Nota il blocco finally. Chiudi sempre il browser lì: se non lo fai, gli ambienti Lambda warm accumulano processi zombie tra un’invocazione e l’altra, e prima o poi ti ritrovi con errori di memoria apparentemente inspiegabili che non c’entrano nulla con il codice.
Passo 4: configura memoria, timeout e architettura
Imposta la memoria ad almeno 1024 MB — per tutto ciò che va oltre uno screenshot banale consiglierei 1536–2048 MB. Imposta il timeout ad almeno 60 secondi. Blocca l’architettura su x86_64, a meno che tu non abbia verificato in modo specifico il supporto arm64 per la tua precisa versione di Chromium (questa varia da release a release).
Passo 5: distribuisci e testa
sam build && sam deploy --guided
Invocalo con un evento di test e, se qualcosa va storto, controlla subito i log CloudWatch: il 90% degli errori nella sezione di troubleshooting qui sotto compare in modo chiaro lì dentro.
Come distribuire Puppeteer su AWS Lambda con Container Image (Docker)
Le Container Image eliminano del tutto il problema dei 250 MB, portando il limite a 10 GB. In genere sono la scelta migliore per i carichi di produzione, soprattutto se il tuo team usa già Docker nella pipeline CI.
Passo 1: crea il Dockerfile
Parti da una base image ufficiale AWS Lambda per Node.js, installa le dipendenze e definisci l’handler:
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["index.handler"]
A seconda del pacchetto Chromium che usi, potrebbe essere necessario fare un yum install di alcune librerie condivise (ne parliamo meglio nella sezione troubleshooting). @sparticuz/chromium include già gran parte di ciò che serve, quindi è molto meno macchinoso rispetto all’installazione manuale di Chrome completo.
Passo 2: builda e fai push su Amazon ECR
aws ecr create-repository --repository-name puppeteer-lambda
docker build -t puppeteer-lambda .
docker tag puppeteer-lambda:latest <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
Tieni l’immagine nella stessa regione della funzione Lambda: i pull cross-region aggiungono latenza inutile.
Passo 3: crea la funzione Lambda dalla Container Image
Punta la funzione all’URI dell’immagine ECR via CLI o CDK e imposta memoria (1536–2048 MB) e timeout (60–120 secondi) nello stesso modo in cui faresti per un deployment basato su layer.
Passo 4: distribuisci e testa
Invoca con un evento di test e verifica l’output. Il compromesso principale rispetto ai Layers: cold start leggermente più lenti a causa del pull dell’immagine più grande, ma con molto più margine per le dipendenze.
Come distribuire Puppeteer su AWS Lambda con upload ZIP diretto
Questa è l’opzione senza fronzoli: niente layer da gestire, niente Docker da costruire. È adatta ai prototipi o a una singola funzione che non deve scalare in una piattaforma completa di automazione browser.
Passo 1: installa le dipendenze in locale
Per uno ZIP autosufficiente, usa puppeteer-core + @sparticuz/chromium e blocca entrambe le versioni. Il pacchetto completo contiene i file Chromium compressi e li estrae in /tmp al runtime. Usa @sparticuz/chromium-min solo quando quei file vengono forniti separatamente tramite un Lambda Layer o una URL remota veloce; il pacchetto -min non include i file Brotli al suo interno.
Passo 2: impacchetta e crea lo ZIP della funzione
npm install --production
zip -r function.zip . -x "*.git*"
Qui il flag --production conta davvero: le dipendenze di sviluppo consumano il budget dei 250 MB senza alcun motivo.
Passo 3: carica e configura la funzione Lambda
aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip
Se lo ZIP supera i 50 MB, non puoi caricarlo direttamente dalla console o con un semplice comando CLI: dovrai prima caricarlo su S3 e poi usare l’URI S3. Imposta memoria, timeout e architettura come nei due metodi precedenti.
Passo 4: distribuisci e testa
Usa lo stesso flusso invoca-e-controlla-i-log. Con il pacchetto completo, chromium.executablePath() non richiede argomenti. Con chromium-min, passa la directory esatta del layer o la URL del remote pack, per esempio chromium.executablePath("/opt/chromium") per il layout del layer visto sopra. Un remote pack aggiunge il download al primo cold start, quindi ospitalo vicino alla funzione e verifica bene versione dell’artifact e architettura.
Gli argomenti di puppeteer.launch() che funzionano davvero su Lambda
Questo è lo snippet che tutti copiano e incollano, quindi facciamolo bene. L’ambiente di esecuzione di Lambda non ha /dev/shm, non dà accesso alla GPU e gira con permessi limitati: perciò la chiamata predefinita a puppeteer.launch() che funziona benissimo sul laptop qui... semplicemente non funziona.
const viewport = {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: true,
};
const browser = await puppeteer.launch({
args: await puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath(),
headless: "shell",
defaultViewport: viewport,
});
L’array chromium.args di @sparticuz/chromium include già i flag importanti per un ambiente serverless: --no-sandbox, --disable-gpu, --disable-dev-shm-usage e simili. È questo il senso del pacchetto: seguire i requisiti di Chromium al posto tuo, così non devi costruirti a mano la lista dei flag.

Troubleshooting: 5 errori che incontrano tutti
Nessuna guida che ho trovato include una vera sezione di troubleshooting, il che è un po’ strano se pensi che gli errori sono quasi certamente il motivo per cui stai leggendo questo articolo.
"Failed to launch the browser process"
Causa principale: librerie condivise mancanti (libnss3.so, libatk, ecc.) oppure executablePath errato.
Soluzione: @sparticuz/chromium include la maggior parte delle dipendenze richieste, ed è proprio per questo che è consigliato rispetto al costruirsi un binario Chromium da soli. Nei deployment Docker, se succede ancora, installa esplicitamente le librerie mancanti nel Dockerfile con yum install.
"Unzipped size must be smaller than 262144000 bytes"
Causa principale: hai installato il pacchetto completo puppeteer, che include il suo download di Chromium (~400 MB).
Soluzione: passa a puppeteer-core + @sparticuz/chromium. Se ti serve davvero più spazio, spostati sull’approccio Container Image e sul tetto dei 10 GB.
"Browser disconnected" oppure timeout su browser.newPage()
Causa principale: memoria Lambda insufficiente, oppure mancano flag come --disable-gpu negli argomenti di avvio.
Soluzione: imposta almeno 1024 MB di memoria (meglio ancora di più, vedi i benchmark sotto) e assicurati di passare chromium.args invece di una lista custom troppo ridotta.
Il codice che funzionava smette di funzionare dopo un aggiornamento del runtime Lambda
Causa principale: AWS applica periodicamente patch al runtime sottostante, e questo può cambiare versioni di librerie condivise o patch di Node.js senza avvisarti in modo esplicito.
Soluzione: blocca esplicitamente la versione di @sparticuz/chromium, blocca la versione del runtime Node nella configurazione della funzione e — questo è il passaggio che molti saltano — rifai i test dopo ogni annuncio di aggiornamento del runtime AWS, non solo quando qualcosa si rompe.
"Protocol error: Connection closed" dopo circa 30 secondi
Causa principale: il timeout della Lambda è più breve del tempo necessario alla pagina per caricarsi e renderizzarsi.
Soluzione: alza il timeout a 60–120 secondi, imposta in modo esplicito page.setDefaultNavigationTimeout(), e usa waitUntil: 'domcontentloaded' al posto di waitUntil: 'networkidle0' se non hai bisogno che ogni singola richiesta di rete sia terminata prima di andare avanti.
Hardening per la produzione: memoria, cold start e costi
Molte guide si fermano a "aumenta la memoria". Non è un consiglio utile: ecco cosa cambia davvero quando aumenti la memoria.
Memoria vs performance
Lambda assegna CPU in modo proporzionale alla memoria, ed è questo il dettaglio che confonde molti. Più memoria non significa solo "più RAM disponibile", ma anche CPU più veloce, che accelera direttamente il rendering di Chromium. In pratica, i team che fanno benchmark con Puppeteer riportano tempi di esecuzione sensibilmente migliori passando da 512 MB fino all’intervallo 1536–2048 MB, anche se i numeri precisi dipendono molto dalle pagine che stai renderizzando. Invece di citare una tabella di benchmark che sarà già vecchia quando la leggerai, fai un test tuo a 512 MB, 1024 MB, 1536 MB e 2048 MB sulle pagine che ti interessano davvero: ti bastano dieci minuti per capire esattamente qual è il punto migliore tra costo e performance.
Provisioned Concurrency per i cold start
Se lavori su qualcosa di sensibile alla latenza — monitoraggio sintetico, API di screenshot in tempo reale — i cold start sono il tuo nemico. La Provisioned Concurrency mantiene un certo numero di ambienti di esecuzione già pronti, eliminando il costo del cold start al prezzo di pagare capacità inattiva. Conviene soprattutto quando la latenza conta più dell’efficienza economica pura.
arm64 (Graviton) per risparmiare
Le funzioni Lambda basate su Graviton costano circa il 20% in meno rispetto alle equivalenti x86_64. Il punto critico: il supporto arm64 di @sparticuz/chromium è stato storicamente più limitato rispetto a x86_64, quindi verifica esplicitamente la tua versione prima di puntare su Graviton in produzione.
VPC vs no-VPC
Mettere la funzione in una VPC aggiungeva una latenza di cold start significativa; AWS negli ultimi anni ha ridotto molto questo gap, ma non l’ha azzerato. Metti la funzione in una VPC solo se deve davvero raggiungere risorse private come RDS o ElastiCache; altrimenti, evita la VPC.
Quando uscire del tutto da Lambda
Se i tuoi task browser superano regolarmente i 15 minuti, richiedono più di 10 GB di memoria o hanno bisogno di sessioni browser persistenti tra una richiesta e l’altra, a quel punto Lambda ti sta ostacolando. ECS Fargate è pensato proprio per questo: compute a lunga durata, risorse configurabili, pagamento al secondo. Lambda è eccellente per task browser brevi, esplosivi e parallelizzabili; è lo strumento sbagliato quando il carico comincia a sembrare un servizio persistente.
Quando distribuire Puppeteer su Lambda è la scelta sbagliata
Vale la pena dirlo con sincerità: una grossa fetta degli sviluppatori che arriva alle guide "Puppeteer + Lambda" in realtà sta cercando di risolvere un problema di estrazione dati, non di automazione browser. Se ciò che ti serve davvero sono dati strutturati da pagine web — liste prodotti, contatti, contenuti di pagina — tutto l’impacchettamento di Chromium, il version pinning e la gestione dei layer qui sopra è overhead che probabilmente non ti serve.
Resta su Lambda + Puppeteer se ti serve un vero controllo del browser: interazioni con form personalizzati, pipeline per screenshot/PDF, monitoraggio sintetico o test basati su browser in cui manipoli davvero il DOM via codice.
Valuta una scraping API se il tuo obiettivo finale è ottenere JSON strutturato da una pagina web, non gestire tu stesso una sessione browser. La Open API di Thunderbit gestisce rendering JS, misure anti-bot e CAPTCHA con una sola chiamata HTTP: POST /extract con uno schema JSON restituisce dati strutturati, POST /distill produce Markdown pulito. C’è anche un server MCP (thunderbit_extract, thunderbit_distill) se stai costruendo un agent AI che deve recuperare dati durante il workflow senza avviare un browser proprio.
| Fattore | Lambda + Puppeteer (fai da te) | API di estrazione (es. Thunderbit) |
|---|---|---|
| Tempo di setup | Ore (packaging, layer, debug) | Minuti (API key + chiamata HTTP) |
| Manutenzione | Continua (version pinning, aggiornamenti runtime) | Gestita dal provider |
| Gestione anti-bot | Manuale (stealth plugin, proxy) | Integrata |
| Formato di output | HTML grezzo/screenshot da parsare | JSON strutturato via schema |
| Ideale per | Automazione browser completa, test, flussi custom | Estrazione dati, scraping, ingestion di contenuti |
Lo dico chiaro: se stai passando ore a fare debug di binari Chromium solo per estrarre JSON da pagine prodotto, probabilmente stai risolvendo il problema sbagliato. Tieni il percorso Lambda fai-da-te per i casi in cui ti serve davvero pilotare un browser; per l’estrazione, esiste una strada più diretta. Se stai valutando questo trade-off per un progetto specifico, la nostra guida agli AI web scraper approfondisce meglio il panorama, e la Thunderbit Chrome Extension vale la pena provarla se vuoi testare l’approccio "extract-first" prima di scegliere una delle due strade.
Conclusione
Tre metodi di deployment, un tema ricorrente: blocca le versioni, assegna a Chromium memoria sufficiente per respirare e scegli il metodo di deployment in base ai tuoi vincoli reali, non in base al primo tutorial che hai trovato. Layers per iterare velocemente, Container Image per la scala in produzione, ZIP per il singolo caso semplice. E se in realtà stai facendo estrazione dati più che automazione browser, forse vale la pena capire se una API di estrazione dedicata ti evita del tutto il problema del packaging.
FAQ
Si può eseguire Puppeteer su AWS Lambda nel 2026?
Sì — usando puppeteer-core insieme a @sparticuz/chromium, distribuiti tramite Layers, Container Image o ZIP diretto. Il pacchetto completo puppeteer e il pacchetto deprecato chrome-aws-lambda non sono più affidabili sui runtime Lambda attuali.
Qual è la dimensione massima di un package per AWS Lambda? 250 MB decompressi per deployment via Layers e ZIP; 10 GB per i deployment via Container Image, secondo i quota AWS Lambda.
chrome-aws-lambda è ancora mantenuto?
No. Il pacchetto originale chrome-aws-lambda (di alixaxel) è deprecato e non funziona su Node 18+. Usa invece @sparticuz/chromium: è lo standard attivamente mantenuto oggi.
Quanta memoria serve a Puppeteer su AWS Lambda? 1024 MB è il minimo pratico; 1536–2048 MB è l’intervallo in cui le prestazioni diventano davvero comode. Sotto i 1024 MB, aspettati esecuzioni nettamente più lente, perché Lambda lega l’allocazione CPU alla memoria.
Come riduco i cold start di Puppeteer su Lambda? Alloca più memoria (che ti dà anche più CPU), valuta la Provisioned Concurrency se la latenza è critica nel tuo caso d’uso e mantieni il package di deployment il più leggero possibile: ogni dipendenza in più aumenta il tempo di cold start.


