Headless browsers work well on ECS, but three things differ from a laptop: there is no display, the container runs as a process that Chromium’s sandbox does not trust, and Chromium needs memory and shared-memory space that the defaults do not provide.
Install Chromium in the image
Puppeteer and Playwright download a browser at install time, but that browser needs system libraries a plain Node image lacks. The simplest route is the image each project publishes, which ships the browser and its dependencies pre-installed. Both are based on Ubuntu 24.04 (noble).
Playwright
Puppeteer
Distro Chromium
The image contains the browsers but not the playwright npm package — npm ci installs it.
Pin the image tag to the exact Playwright version in package.json; a mismatch fails at
launch because the package looks for a browser build the image does not have. See the
Playwright Docker guide. The image includes Chrome for Testing, the matching puppeteer version, and runs as
pptruser. Pin the tag to the Puppeteer version in package.json. See the
Puppeteer Docker guide.If you build on a plain Node image instead, note that npm 11, pnpm, Yarn, and Bun block
dependency install scripts by default, so Puppeteer’s browser download does not run on
npm ci. Run npx puppeteer browsers install chrome as an explicit build step, and install
the system libraries Chrome needs. Use Debian’s chromium package with puppeteer-core (or puppeteer with the download
skipped) when you need an arm64 image or a smaller one. The package pulls in every library
Chromium needs, and there is no second browser download during the build. You get Debian’s
Chromium release cadence rather than the version Puppeteer was tested against.
Launch options
Chromium’s sandbox relies on user namespaces that containers usually do not grant, and /dev/shm inside a Fargate task is 64 MB — too small for a real page. Disable both:
Headless is the default in current Puppeteer and Playwright, so there is nothing to set for it. Playwright takes the same flags via chromium.launch({ args: [...] }); on ECS you cannot use the --ipc=host alternative the Playwright docs suggest for /dev/shm, because Fargate does not support host IPC mode.
Only render content you trust with the sandbox disabled. A page that exploits Chromium gains the
permissions of the task role. If you render untrusted pages, keep the sandbox on: run as the
non-root user the official images provide (pwuser, pptruser) and give Chromium the
SYS_ADMIN capability or a seccomp profile that allows user namespaces. Fargate permits
neither, so put those tasks on EC2 capacity (capacity_provider: ec2).
Task sizing
- Memory. Budget at least 1 GB per concurrent browser page plus your app. The default
fargate_size of 2 vCPU / 4 GB handles a couple of pages; raise it before scaling out.
- Ephemeral storage. Chromium writes profiles and caches to disk. If you screenshot or download at volume, increase
task_ephemeral_storage_size_gib above the 20 GiB Fargate default.
- Architecture. Chrome for Testing, the build Puppeteer downloads, has no Linux
arm64 binaries, so a Puppeteer image built on it runs on X86_64 only. For cpu_architecture: ARM64 (Graviton) use the Playwright image, which is published for both architectures, or Debian’s chromium package.
Isolate browser work in a worker
Run the browser in a separate rvn-ecs-worker that pulls jobs from a queue, rather than in the web service. That keeps a crashed or leaked Chromium from taking down request handling, lets you scale renderers independently, and means the web service does not need the larger image or --no-sandbox at all.
Related pages