> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Run Puppeteer

> Run a Puppeteer service on Sevalla that takes screenshots with headless Chrome, built from a Dockerfile that installs Chrome and its system libraries.

A Puppeteer service runs on Sevalla as an application built from a Dockerfile: the build installs Chrome for Testing and the Linux libraries it needs, and the web process runs a Node.js server that drives Chrome on the port that Sevalla sets in the `PORT` environment variable. This guide builds a service that returns a screenshot of a page.

Sevalla's default build strategy, Nixpacks, doesn't install the system libraries Chrome needs, so this guide uses a Dockerfile.

## Before you begin

* A Sevalla company with a payment method, and a GitHub, GitLab or Bitbucket account for the repository. See [Git providers](/git/overview).
* Node.js 24 or later on your computer.

## Create the project

Create a folder and install Puppeteer, which also downloads Chrome for Testing for local use:

```bash theme={null}
mkdir storefront && cd storefront
npm init -y
npm install puppeteer
npm pkg set type=module scripts.start="node server.js" engines.node="24.x"
```

Create the server. It starts Chrome once, opens a new page for each request and returns a PNG of `https://example.com`:

```js server.js theme={null}
import http from "node:http";
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  args: ["--no-sandbox", "--disable-dev-shm-usage"],
});

const server = http.createServer(async (req, res) => {
  if (req.url !== "/screenshot") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Open /screenshot to capture https://example.com");
    return;
  }

  const page = await browser.newPage();
  try {
    await page.goto("https://example.com");
    const image = await page.screenshot({ type: "png" });
    res.writeHead(200, { "Content-Type": "image/png" });
    res.end(image);
  } catch (error) {
    res.writeHead(500, { "Content-Type": "text/plain" });
    res.end(String(error));
  } finally {
    await page.close();
  }
});

server.listen(process.env.PORT || 3000);

process.on("SIGTERM", async () => {
  await browser.close();
  server.close(() => process.exit(0));
});
```

Keep installed packages out of the repository:

```text .gitignore theme={null}
node_modules/
```

## Prepare the project for Sevalla

The server reads its port from [`PORT`](/guides/javascript/overview#listen-on-the-port-environment-variable), which Sevalla sets to the web process's port (`8080` by default), and `server.listen()` listens on all network interfaces. The Chrome flags matter in a container:

| Flag | Why |
| - | - |
| `--no-sandbox` | Chrome's sandbox needs kernel privileges that containers don't usually get. Without the sandbox, open only pages you trust. |
| `--disable-dev-shm-usage` | Chrome writes shared memory to `/tmp` instead of `/dev/shm`, which is small in containers. |

Create a Dockerfile in the repository root:

```dockerfile Dockerfile theme={null}
FROM node:24-bookworm-slim

ENV PUPPETEER_CACHE_DIR=/app/.cache/puppeteer
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

RUN apt-get update \
  && npx puppeteer browsers install chrome --install-deps \
  && rm -rf /var/lib/apt/lists/*

COPY . .

USER node
CMD ["node", "server.js"]
```

* `PUPPETEER_CACHE_DIR` puts Chrome inside `/app`, so `npm ci` downloads it into the image instead of the build user's home folder.
* `npx puppeteer browsers install chrome --install-deps` installs the Debian packages Chrome needs. It needs root, which the build has before `USER node`.
* `USER node` runs the server as the image's unprivileged user.

Run `npm start` and open `http://localhost:3000/screenshot` to check the service locally. Then commit everything and push it to your Git provider:

```bash theme={null}
git init
git add .
git commit -m "Prepare for Sevalla"
git branch -M main
git remote add origin https://github.com/acme/storefront.git
git push -u origin main
```

## Deploy to Sevalla

<Steps>
  <Step title="Create the application">
    In the sidebar, click **Applications**, then click **Create** > **Application**. Under **Source**, click **Private repository**, then choose the **Git provider**, the **Repository** and the **Branch**. Under **Location**, choose a **Data center**. Choose **S1** (1 GB of memory) or a larger **Size**: Chrome usually needs more than the 0.3 GB of memory that **H1** has. Click **Create application**. The application opens on its **Overview** tab with the status **Not deployed yet**: creating an application doesn't deploy it.
  </Step>

  <Step title="Set the build strategy">
    Click the **Settings** tab, then click **Update build strategy**. Choose **Dockerfile**, keep the **Dockerfile path** at `Dockerfile` and the **Docker context** at `.`, and click **Update build strategy**.
  </Step>

  <Step title="Deploy">
    Click the **Overview** tab, then click **Deploy now** under **Recent deployments**. Check the **Branch** and click **Deploy**.
  </Step>

  <Step title="Turn on auto-deploy">
    To deploy every push to the branch automatically, click the **Settings** tab, then click **Update auto-deploy** in the **Auto-deploy** section. Turn on **Auto-deploy** and click **Update auto-deploy**. See [Deployments](/applications/deployments#deploy-automatically-on-push).
  </Step>
</Steps>

## Verify the deployment

1. On the **Deployments** tab, wait for the deployment's status to change to **Deployed**. The application's status next to its name changes to **Running**.
2. Click the domain under the application's name, for example `storefront-x7k2m.sevalla.app`, and add `/screenshot` to the address. The page shows a screenshot of `https://example.com`.

<Frame>
  <img className="block dark:hidden" src="https://assets.sevalla.com/screenshots/light/app-overview-address.png" alt="The application header with its name, Running status, repository, branch and the domain that opens the application" width="1192" />

  <img className="hidden dark:block" src="https://assets.sevalla.com/screenshots/dark/app-overview-address.png" alt="The application header with its name, Running status, repository, branch and the domain that opens the application" width="1192" />
</Frame>

If the deployment fails, open it on the **Deployments** tab and read its build log, then see [Troubleshoot builds and deployments](/applications/troubleshooting/builds-and-deployments). If the instance restarts or `/screenshot` fails, read the application's [runtime logs](/applications/logs):

* `Could not find Chrome` means Chrome isn't in `PUPPETEER_CACHE_DIR`: check that the variable is set before `npm ci` in the Dockerfile.
* A process that stops without an error usually ran out of memory: [scale](/applications/scaling) the web process to a larger size.

## Deploy changes

With **Auto-deploy** on, every push to the branch starts a deployment; otherwise, click **Deploy now** for each change. Each deployment builds the image, then replaces the web process's instances. See [Deployments](/applications/deployments).

## Run browser jobs in the background

Screenshots and PDFs of long pages can take longer than a visitor waits. Run them in a [background worker](/applications/processes) process that takes work from a queue in a Sevalla [Redis database](/guides/javascript/redis), and store the results in [object storage](/guides/javascript/object-storage) instead of the instance's disk.

## Add a custom domain

To serve the application from your own domain, such as `shop.example.com`, add the domain on the application's **Domains** tab and create the DNS records that Sevalla shows. See [Custom domains](/domains/overview).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.