Skip to main content
A Turborepo monorepo deploys to Sevalla as one application per app: each application builds from the same Git repository, and its INGOT_APP environment variable tells Ingot, Sevalla’s builder, which app to build with turbo and start. This guide deploys the web and docs Next.js apps of Turborepo’s starter.

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 and pnpm on your computer.

Create the project

Create a monorepo from Turborepo’s basic example:
The monorepo is a pnpm workspace with two Next.js apps, apps/web and apps/docs, and shared packages in packages/, such as @repo/ui. Each app’s start script runs next start. create-turbo installs the dependencies and creates a Git repository. The starter pins Next.js 16.3.8, which has security advisories fixed in Next.js 16.4.1. Update both apps to the latest Next.js release:

Prepare the project for Sevalla

Pin the Node.js version in the root package.json:
The starter sets engines.node to >=24, which lets a new Ingot release move the build to a newer Node.js line. 24.x keeps it on Node.js 24. See Pin the Node.js version. Build each app as a standalone server. Set output in apps/web/next.config.js, and the same in apps/docs/next.config.js:
apps/web/next.config.js
With output: "standalone", next build copies the server and only the files it needs from the workspace into the app’s .next/standalone folder. The image runs that server, without the workspace’s other apps or its devDependencies, with Ingot and with a Dockerfile alike. Each Sevalla application builds one app, named by the INGOT_APP environment variable: With INGOT_APP, Ingot, the default build strategy, builds the app from the repository root, as described in Build an application in a monorepo:
  • It installs the whole workspace at the root with pnpm install --frozen-lockfile, using the pnpm release in the root’s packageManager field. Commit pnpm-lock.yaml.
  • Because the repository has a turbo.json, it builds with turbo run build --filter='web...', which builds the web app and the workspace packages it depends on, such as @repo/ui.
  • It detects the standalone output, copies it into the image and starts the app with node server.js.
The standalone server takes its port from PORT, which Sevalla sets to the web process’s port (8080 by default), so the ports that the dev scripts use don’t matter on Sevalla, and Ingot sets HOSTNAME to 0.0.0.0 so that it listens on all network interfaces. It handles SIGTERM itself: when Sevalla replaces an instance during a deployment, it stops accepting connections and finishes the requests in progress before it exits. Run pnpm build to check that the monorepo builds locally. Then commit everything and push it to your Git provider:

Deploy to Sevalla

These steps build with Ingot. To build from your own Dockerfiles 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. Enter storefront-web as the Name. Under Location, choose a Data center. Keep the Size at H1 or choose a larger one, then click Create application. The application opens on its Overview tab with the status Not deployed yet: creating an application doesn’t deploy it.
2

Add the INGOT_APP variable

Click the Environment variables tab, then click Add environment variables. Enter INGOT_APP as the Key and apps/web as the Value. Keep Available during buildtime selected, because Ingot reads the variable during the build, and click Add environment variables.
3

Deploy

Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
4

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.
5

Deploy the docs app

Repeat steps 1 to 4 for a second application named storefront-docs, from the same repository and branch, with INGOT_APP set to apps/docs.

Verify the deployment

  1. On each application’s Deployments tab, click the deployment. Its Build log shows Ingot’s build plan. For storefront-web, the plan shows apps/web from INGOT_APP, the turbo build of web and the workspace packages it depends on, and the standalone server as the start command:
    Build log
  2. Wait for the deployment’s status to change to Deployed. The application’s status next to its name changes to Running.
  3. Click the domain under each application’s name, for example storefront-web-x7k2m.sevalla.app. Each one opens its own Next.js starter page, which says Get started by editing apps/web/app/page.tsx for storefront-web and apps/docs/app/page.tsx for storefront-docs.
The application header with its name, Running status, repository, branch and the domain that opens the applicationThe application header with its name, Running status, repository, branch and the domain that opens the application
To check from a terminal, read the deployment’s status with Get deployment and its build log with Get application deployment logs, as the last steps of Deploy with the API do, then request the domain:
If a deployment fails, read its build log, then see Troubleshoot Ingot builds. The error This workspace has several applications and no start script at its root: apps/docs (docs), apps/web (web) means INGOT_APP is missing or isn’t Available during buildtime.

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. Each app gets its own Dockerfile, which uses turbo prune to build only that app and the workspace packages it depends on. Add this one as apps/web/Dockerfile:
apps/web/Dockerfile
Add apps/docs/Dockerfile with the same content, with docs in place of every web. Both Dockerfiles share one .dockerignore, at the root of the repository:
.dockerignore
Commit and push the three files. Ingot doesn’t use the Dockerfiles, so you can keep them 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 each application to the Dockerfile build strategy:
1

Choose the Dockerfile build strategy

On the storefront-web application’s Settings tab, in the Build strategy section, click Update build strategy. Click Dockerfile, enter apps/web/Dockerfile as the Dockerfile path, keep the Docker context ., then click Update build strategy. The build needs the whole repository as its context, because turbo prune reads the root’s lock file and every workspace package.
2

Deploy

Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
3

Switch the docs app

Repeat steps 1 and 2 for storefront-docs, with apps/docs/Dockerfile as the Dockerfile path.
The Update build strategy sheet with Dockerfile selected, the Dockerfile path, the Docker context and the Docker registry credentialThe Update build strategy sheet with Dockerfile selected, the Dockerfile path, the Docker context and the Docker registry credential
The storefront-web deployment’s Build log shows 📦 Building and pushing docker image using apps/web/Dockerfile... instead of Ingot’s build plan, and its Dockerfile tab shows your Dockerfile. When the deployments are Deployed, each domain opens its own starter page again. INGOT_APP has no effect on Dockerfile builds, so you can keep it. See Dockerfile for build arguments and private base images.

Deploy changes

With Auto-deploy on, every push to the branch starts a deployment of both applications; otherwise, click Deploy now on each application for each change. Each deployment builds the image, then replaces the web process’s instances. See Deployments.

Deploy only the apps that changed

To deploy an application only when its own files change, set its deploy paths:
1

Open the deploy paths

Click the application’s Settings tab, then click Update deploy paths.
2

Allow the app's files

Choose Allow paths and add the files the app is built from, as glob patterns. For storefront-web, add apps/web/**, packages/** and pnpm-lock.yaml. Click Update deploy paths.
Pushes that change none of these files no longer deploy the application. Deploy paths need GitHub or GitLab: Bitbucket pushes don’t list the changed files, so with deploy paths set, every push from a Bitbucket repository is skipped.

Connect the apps to each other

Applications in the same location can reach each other over the private network without going through the internet. On one application’s Networking tab, under Connected services, add an internal connection to the other with Add environment variables to the application turned on: Sevalla adds APP_HOST, the other application’s internal hostname. See Networking.

Add a custom domain

To serve an 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. Custom domains need a web process larger than H1, so scale the web process first. See Custom domains.

Deploy with the API

The Sevalla API makes the same deployment as Deploy to Sevalla, for both apps, without the dashboard. Run the steps in order in one shell: each step is one API call, and saves the IDs that later steps need in shell variables. Each step also names the CLI command and the MCP server tool that do the same. A variable that holds null means the call failed: run the curl command without its jq part to read the error. You need:
  • An API key with the Company developer role, or the APP:CREATE, APP:READ and APP:UPDATE permissions, in the SEVALLA_API_KEY environment variable. The CLI reads the key from SEVALLA_API_TOKEN.
  • The repository on GitHub, GitLab or Bitbucket, and a Git account that can read it connected to your Sevalla user, which only the dashboard can do. See Git providers. Creating the application checks the access: if Sevalla can’t read the repository, the call returns 400 with Cannot access repository https://github.com/acme/storefront: Repository not found or you don't have access., followed by where to grant the access.
  • curl and jq.
1

Find the location

List clusters (listClustersV3) returns every location with its id, its name in display_name and its region ID in location. Pick one by its region ID, here eu-frankfurt-1 for Frankfurt, Germany. Locations lists them all.
CLI: sevalla resources clusters. MCP: get_platform_options.
2

Find the H1 size

The API creates each web process with the S1 size, where the dashboard picks H1. List process resource types (listProcessResourceTypesV3) returns the ID of each size:
CLI: sevalla resources process-types. MCP: get_platform_options.
3

Choose the app

The next steps deploy one app of the monorepo, named by APP. Run them for web first, then again from this step for docs:
Each pass creates one application, storefront-web then storefront-docs.
4

Create the application

Create application (createApplicationV3) creates storefront-$APP from the repository, with the Ingot build strategy and a web process, and doesn’t deploy it. Both applications use the same repository and branch.
CLI: sevalla apps create --name "storefront-$APP" --cluster "$CLUSTER_ID" --source privateGit --git-type github --repository https://github.com/acme/storefront --branch main. MCP: create_application with deploy set to false.
5

Find the web process

List processes (listProcessesV3) returns the web process that Create application added:
CLI: sevalla apps processes list --app-id "$APP_ID". MCP: get_application, in processes.
6

Set the web process size

Update process (updateProcessV3) sets the size, which applies at the first deployment:
CLI: sevalla apps processes update "$WEB_ID" --app-id "$APP_ID" --resource-type "$H1_ID". MCP: scale_process with size_id, and apply_now set to false.
7

Add the INGOT_APP variable

Create environment variable (createEnvironmentVariableV3) adds INGOT_APP. Ingot reads it during the build to pick the app, so it needs to be available during the build only.
CLI: sevalla apps env-vars create "$APP_ID" --key INGOT_APP --value "apps/$APP", which makes the variable available during both the build and runtime. MCP: set_environment_variables with runtime set to false and buildtime set to true.
8

Turn on auto-deploy

Update application (updateApplicationV3) with auto_deploy deploys every push to the branch:
CLI: sevalla apps update "$APP_ID" --data '{"auto_deploy":true}'. MCP: sevalla_api_update with updateApplicationV3, the application ID as path_params.id and the same body.
9

Choose the Dockerfile build strategy

Skip this step to build with Ingot. To build from the app’s Dockerfile instead, Update application (updateApplicationV3) sets the build strategy:
CLI: sevalla apps update "$APP_ID" --data "{\"build_type\":\"dockerfile\",\"dockerfile_path\":\"apps/$APP/Dockerfile\",\"docker_context\":\".\"}". MCP: sevalla_api_update with updateApplicationV3 and the same body.
10

Deploy

Trigger deployment (triggerDeploymentV3) deploys the latest commit of the branch:
CLI: sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.
11

Wait for the deployment

Get deployment (getDeploymentV3) returns the deployment’s status: waiting, inProgress, then success, failed, cancelled or skipped. Poll it until the deployment ends:
The loop asks every 15 seconds, for up to 30 minutes, and keeps asking through an empty or failed reply. success means the application is deployed. CLI: sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.
12

Read the build log

Get application deployment logs (getApplicationDeploymentLogsV3) returns the build log, one line per entry. With Ingot, the App row of the build plan shows the app that INGOT_APP picked, and the Start row the start command:
With a Dockerfile, the log shows 📦 Building and pushing docker image using apps/web/Dockerfile... (or apps/docs/Dockerfile) in place of the build plan. When a deployment fails, the end of this log says why. CLI: sevalla apps deployments logs "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment_logs.
13

Check the application

List application domains (listApplicationDomainsV3) returns the application’s domains. Request the application on its domain. Its page names the file to edit, which differs between the two apps:
For docs, the output is <code>apps/docs/app/page.tsx</code>. After the web pass, run the steps again from the step “Choose the app”, with APP=docs.CLI: sevalla apps domains list --app-id "$APP_ID". MCP: get_application, in domains.