puppeteer in your dependencies and installs Chrome for Testing and the Linux libraries it needs into the image, 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.
At a glance
Environment variables
Before you begin
- A Sevalla company with a payment method, and a GitHub, GitLab or Bitbucket account for the repository. See Git providers.
- Node.js 24 or later on your computer.
Create the project
Create a folder, install Puppeteer and download Chrome for Testing for local use:npx puppeteer browsers install chrome downloads the release your Puppeteer version expects.
Create the server. It starts Chrome once, opens a new page for each request and returns a PNG of https://example.com:
server.js
.gitignore
Prepare the project for Sevalla
Thenpm pkg set command already set what Ingot, the default build strategy, reads from package.json:
Because
puppeteer is in dependencies, Ingot also installs Chrome, with no Dockerfile:
npm ciruns withPUPPETEER_SKIP_DOWNLOADset, so the install doesn’t download Chrome into the build.- The final image runs
puppeteer browsers install chrome --install-deps, which installs the Chrome release your Puppeteer version expects into/usr/local/share/puppeteer, with the Debian packages Chrome needs. - The image sets
PUPPETEER_CACHE_DIRto/usr/local/share/puppeteer, sopuppeteer.launch()finds that Chrome.
puppeteer in dependencies. In devDependencies, it treats Puppeteer as a test tool and installs no browser.
The server reads its port from PORT, 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:
Run
npm start and open http://localhost:3000/screenshot to check the service locally. Then commit everything and push it to your Git provider:
Deploy to Sevalla
These steps build with Ingot. To build from your own Dockerfile instead, see Deploy with a Dockerfile.1
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 and every page it opens share the instance’s memory, and H1 has 0.3 GB. Click Create application. The application opens on its Overview tab with the status Not deployed yet: creating an application doesn’t deploy it.
2
Deploy
Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
3
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.
Verify the deployment
-
On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: Node.js 24 from
engines.node, npm frompackage-lock.jsonand the start command from thestartscript.Further down, the step that installs Chrome prints the release and its path, such asBuild logchrome@156.0.8078.47 /usr/local/share/puppeteer/chrome/linux-156.0.8078.47/chrome-linux64/chrome. - Wait for the deployment’s status to change to Deployed. The application’s status next to its name changes to Running.
-
Click the domain under the application’s name, for example
storefront-x7k2m.sevalla.app, and add/screenshotto the address. The page shows a screenshot ofhttps://example.com. -
Check the same address from a terminal:


getDeploymentV3) returns the deployment’s status, success once it’s deployed, and Get application deployment logs (getApplicationDeploymentLogsV3) returns its build log, which ends with Web has been deployed successfully. See Deploy with the API.
If the deployment fails, read its build log, then see Troubleshoot Ingot builds. If the instance restarts or /screenshot fails, read the application’s runtime logs:
Could not find Chromemeans the image has no Chrome: check thatpuppeteeris independencies, notdevDependencies.- A process that stops without an error usually ran out of memory: scale the web process to a larger size.
Add fonts for other languages
The image has only the Liberation fonts that Chrome’s packages install, so text in scripts such as Arabic, Chinese, Japanese or Korean shows up as boxes in screenshots and PDFs. Install more fonts with Ingot’sINGOT_RUN_PACKAGES variable, which adds Debian packages to the final image:
1
Add the variable
Click the application’s Environment variables tab, then click Add environment variables. Enter
INGOT_RUN_PACKAGES as the Key and fonts-noto-core fonts-noto-cjk as the Value. Keep Available during buildtime selected, because Ingot reads the variable during the build, and click Add environment variables.2
Deploy
Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
/screenshot shows the Arabic and Chinese text on https://example.com in Noto fonts. To keep the packages in the repository instead, set packages.run in an ingot.json file. See Configure the build. With your own Dockerfile, add fonts-noto-core fonts-noto-cjk to the apt-get install line in Deploy with a Dockerfile instead. To add the variable with the API, see Add fonts with the API.
Deploy with a Dockerfile
Ingot needs no Dockerfile. Write your own when you want full control of the image: the base image, the system packages and every layer. This one installs Debian’s Chromium package instead of Chrome for Testing. Add aDockerfile and a .dockerignore to the root of the repository:
Dockerfile
server.js launches Chrome with the same --no-sandbox and --disable-dev-shm-usage flags on both build strategies.
.dockerignore
Dockerfile, so you can keep it in a repository that builds with Ingot, but it adds the .dockerignore patterns to its own, and its build plan lists them in an Ignored row. Then switch the application to the Dockerfile build strategy:
1
Choose the Dockerfile build strategy
On the application’s Settings tab, in the Build strategy section, click Update build strategy. Click Dockerfile, keep the Dockerfile path
Dockerfile and the Docker context ., then click Update build strategy.2
Deploy
Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.


📦 Building and pushing docker image using Dockerfile... instead of Ingot’s build plan, then installs chromium. Its Dockerfile tab shows your Dockerfile. When the deployment is Deployed, /screenshot shows a screenshot of https://example.com again, and the two curl commands in Verify the deployment print the same lines. See Dockerfile for build arguments and private base images, and Deploy with the API to switch the build strategy with the API.
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.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 process that takes work from a queue in a Sevalla Redis database, and store the results in object storage instead of the instance’s disk. The worker runs from the same image as the web process, so it has Chrome too.Add a custom domain
To serve the application from your own domain, such asshop.example.com, add the domain on the application’s Domains tab and create the DNS records that Sevalla shows. See Custom domains.
Deploy with the API
These calls make the same deployment as Deploy to Sevalla, in an order that a script or an AI agent runs as it is. Each step names the Sevalla API operation, then the CLI command and the MCP server tool that do the same. You need:- An API key with the Company developer role, or the
APP:CREATE,APP:READandAPP:UPDATEpermissions, in theSEVALLA_API_KEYenvironment variable. The CLI reads the key fromSEVALLA_API_TOKEN. - The repository on a Git provider that’s connected to your Sevalla account. See Git providers. Creating the application checks it: when Sevalla can’t read the repository, the call returns
400withCannot access repository https://github.com/acme/storefront: Repository not found or you don't have access., followed by where to grant access. curlandjq.
1
Choose the location
List clusters (CLI:
listClustersV3) returns the locations your company can use, each with its id, its display_name and its region ID in location (see Locations). Keep the ID of the one you want, here Frankfurt, Germany:sevalla resources clusters. MCP: get_platform_options.2
Create the application
Create application (CLI:
createApplicationV3) creates the application with a web process at S1, the size this guide needs, and auto-deploy off. It builds with Ingot (build_type is ingot) and isn’t deployed yet:sevalla apps create --name storefront --cluster $CLUSTER_ID --source privateGit --git-type github --repository https://github.com/acme/storefront --branch main. MCP: create_application with deploy set to false, because it deploys by default.3
Turn on auto-deploy
Update application (CLI:
updateApplicationV3) turns on automatic deployments for every push to the branch:sevalla apps update $APP_ID --data '{"auto_deploy":true}'. MCP: sevalla_api_update with updateApplicationV3.4
Deploy
Trigger deployment (CLI:
triggerDeploymentV3) deploys the latest commit on the application’s branch:sevalla apps deployments trigger $APP_ID. MCP: trigger_deployment.5
Wait for the deployment
Get deployment (CLI:
getDeploymentV3) returns the deployment’s status: waiting and inProgress while it runs, then success or failed. Poll it every 15 seconds, for up to 30 minutes, until the status is final:sevalla apps deployments get $DEPLOYMENT_ID --app-id $APP_ID. MCP: get_deployment.6
Read the build log
Get application deployment logs (CLI:
getApplicationDeploymentLogsV3) returns the build log, oldest line first: the build plan from Verify the deployment, the build and the rollout, which ends with Web has been deployed successfully. When the status is failed, the last lines say why. Without limit, the call returns the latest 1,000 lines, so a longer log loses its start; 5000 is the most it allows.sevalla apps deployments logs $DEPLOYMENT_ID --app-id $APP_ID. MCP: get_deployment_logs.7
Check the application
List application domains (CLI:
listApplicationDomainsV3) returns the application’s domain. Request the home page and a screenshot from it:sevalla apps domains list --app-id $APP_ID. MCP: get_application.Build from the Dockerfile with the API
After you push theDockerfile from Deploy with a Dockerfile, Update application (updateApplicationV3) switches the build strategy. Then repeat the Deploy, Wait for the deployment and Check the application steps:
sevalla apps update $APP_ID --data '{"build_type":"dockerfile","dockerfile_path":"Dockerfile","docker_context":"."}'. MCP: sevalla_api_update with updateApplicationV3.
Add fonts with the API
To add fonts for other languages, Create environment variable (createEnvironmentVariableV3) adds INGOT_RUN_PACKAGES. Without is_runtime and is_buildtime, the variable is available during the build and at runtime, and Ingot reads it during the build. Then repeat the Deploy, Wait for the deployment and Check the application steps:
sevalla apps env-vars create $APP_ID --key INGOT_RUN_PACKAGES --value "fonts-noto-core fonts-noto-cjk". MCP: set_environment_variables.
The build plan doesn’t list the packages. The new deployment’s build log shows Debian installing them: