Skip to main content
A Vite single-page app builds to static files: vite build writes HTML, CSS and JavaScript to the dist folder, and the router in the browser renders each page. Sevalla publishes dist as a static site, served from Cloudflare’s edge and free up to 100 GB of bandwidth a month. When the app has to run as an application instead, for example next to an API in a pipeline, Ingot builds it and serves dist with Caddy.

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 project from Vite’s TypeScript template for your framework, and add its router:
--no-interactive skips the questions, so the command creates the project and stops. The project comes with a .gitignore and a build script that checks the TypeScript and runs vite build. Add an About page at /about, which shows a build-time variable, and the routes:
src/About.tsx
Replace src/main.tsx with this:
src/main.tsx
The router reads the path from the address bar, so /about exists only in the browser: dist has no about file. When someone opens /about directly or reloads it, the server must answer with index.html, and the router renders the page. Each way of deploying in this guide sets that up. Run npm run dev and open http://localhost:5173/about to check the routes locally. Then commit everything and push it to your Git provider:

Deploy as a static site

1

Choose the repository

In the sidebar, click Static sites, then click Create static site. Under Source, click Private repository, then choose the Git provider, the Repository and the Branch.
2

Check the build settings

Under Build settings, Sevalla reads package.json and fills in the build command and the publish directory. For the React template, it shows Framework detected: React + Vite. Check the values and set the Error file:
3

Add the build-time variable

Under Environment variables, enter VITE_API_URL as the Key and https://api.example.com as the Value.
4

Create the static site

Enter a Name under Details, then click Create static site. Sevalla starts the first deployment right away, and deploys every push to the branch while Auto-deploy is on.
The Error file makes the site answer every path that has no file with index.html, so the router gets /about and any other route. Static sites don’t read engines in package.json: the build uses the Node version from the build settings. See Build settings for every field.

Verify the static site

  1. On the Deployments tab, wait for the deployment’s status to change to Deployed. The site’s Status on the Overview tab changes to Live.
  2. On the Overview tab, click Visit site. The Vite starter page opens on the site’s *.sevalla.page domain.
  3. Add /about to the address and press Enter. The page shows About Storefront and Orders API: https://api.example.com.
The static site's Details card with its domain, Live status, source repository and the Visit site buttonThe static site's Details card with its domain, Live status, source repository and the Visit site button
To check from a terminal, request /about and the JavaScript it loads, which holds the value of VITE_API_URL:
If /about shows Sevalla’s 404 page, the Error file is empty: set it to index.html on the Settings tab with Update build strategy, then deploy again. If the deployment fails, open it on the Deployments tab and read its build log, then see Troubleshoot static sites.

Use build-time variables

Vite replaces each import.meta.env.VITE_* expression with the variable’s value while it builds, so the value ends up in the JavaScript files that every visitor downloads. Variables without the VITE_ prefix never reach the browser code.
  • Static site. Add the variable on the site’s Environment variables tab, then deploy the site again: a changed value applies only to the next build. See Environment variables.
  • Application. Add the variable on the application’s Environment variables tab with Available during buildtime selected, then deploy again. See Environment variables during the build.
Never put a secret, such as an API key that grants access to your data, in a VITE_ variable: anyone can read it in the browser.

Deploy as an application

An application built from the same repository serves the same files. A static site costs nothing up to the monthly allowances, so use an application only when the app needs something only applications have, such as promoting one build through the stages of a pipeline. Pin the Node.js version that builds the app:
Without it, Ingot builds on its default line, Node.js 24 today, which a new Ingot release can move. See Pin the Node.js version. Commit and push the change, then create the application. 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. 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 build-time variable

Click the Environment variables tab, then click Add environment variables. Enter VITE_API_URL as the key and https://api.example.com as the value, keep Available during buildtime selected, 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.

Verify the application

  1. On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: Vite from package.json, built on node:24-trixie and served from an alpine:3 image with Caddy.
    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 the application’s name, for example storefront-x7k2m.sevalla.app, then add /about to the address. The page shows About Storefront and Orders API: https://api.example.com.
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 Deploy with the API does, then request /about and the JavaScript it loads:
The image has no Node.js: Caddy serves dist on the port in PORT, answers paths that have no file with index.html, and marks the content-hashed files under /assets/ as cacheable for a year. To serve them from Cloudflare’s servers, turn on the CDN on the application’s Networking tab. See CDN and edge caching. If the deployment fails, read its build log, then see Troubleshoot Ingot builds.

Deploy with a Dockerfile

Ingot needs no Dockerfile. Write one when you want full control of the image: the base image, the web server and every layer. Add a Dockerfile, a Caddyfile and a .dockerignore to the root of the repository:
Dockerfile
Caddyfile
.dockerignore
Commit and push the three files. Ingot doesn’t use the Dockerfile and adds your .dockerignore patterns to its own, so you can keep the files in a repository that builds with Ingot. 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.
The deployment’s build log shows 📦 Building and pushing docker image using Dockerfile... instead of Ingot’s build plan, and its Dockerfile tab shows your file. When the status changes to Deployed, /about on the application’s domain shows Orders API: https://api.example.com. See Dockerfile for build arguments and private base images.

Deploy changes

With Auto-deploy on, every push to the branch starts a deployment of the static site or the application; otherwise, deploy each change from the Deployments tab. A static site’s deployment builds the app and switches the site to the new files; an application’s deployment builds a new image and replaces the web process’s instances. See Static site deployments and Deployments.

Add a custom domain

To serve the app from your own domain, such as shop.example.com, add the domain on the static site’s Domains tab and create the DNS records that Sevalla shows. Applications have a Domains tab too, but 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 this guide 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. This section has two sequences, one for each way to deploy: as a static site and as an application. You need:
  • An API key with the Company developer role, or the APP:CREATE, APP:READ and APP:UPDATE permissions for the application and the STATIC_SITE:CREATE, STATIC_SITE:READ and STATIC_SITE:UPDATE permissions for the static site, 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 or the static site 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.

Deploy the static site

1

Create the static site

Create static site (createStaticSiteV3) creates the static site with the build settings of the dashboard steps. node_version lts is the dashboard’s Latest LTS. Static sites don’t read engines in package.json.
CLI: sevalla static-sites create --name storefront --repository https://github.com/acme/storefront --branch main --git-type github --build-command "npm run build" --node-version lts --publish-directory dist. The CLI has no options for the error file and auto-deploy, so set error_file and auto_deploy with the API. MCP: create_static_site with the same fields.
2

Add the build-time variable

Create static site environment variable (createStaticSiteEnvVarV3) adds VITE_API_URL, which vite build writes into the JavaScript. It applies to production and preview deployments by default.
CLI: sevalla static-sites env-vars create "$SITE_ID" --key VITE_API_URL --value https://api.example.com. MCP: set_environment_variables with scope set to static_site.
3

Deploy the static site

Deploy static site (deployStaticSiteV3) builds and publishes the latest commit of the branch. Unlike the dashboard, the API doesn’t start the first deployment when it creates the site.
CLI: sevalla static-sites deployments trigger "$SITE_ID". MCP: trigger_deployment with service_type set to static_site.
4

Wait for the static site deployment

Get static site deployment (getStaticSiteDeploymentV3) returns the deployment’s status. Poll it until the deployment ends:
success means the site is live. If it’s failed, Get static site deployment logs (getStaticSiteDeploymentLogsV3) returns the build log. CLI: sevalla static-sites deployments get "$DEPLOYMENT_ID" --site-id "$SITE_ID". MCP: get_deployment, and get_deployment_logs for the log.
5

Check the static site

Get static site (getStaticSiteV3) returns the site’s domain in hostname, such as storefront-x7k2m.sevalla.page. /about has no file, so the site answers it with index.html, the error file. The JavaScript that index.html loads holds the value of VITE_API_URL:
CLI: sevalla static-sites get "$SITE_ID". MCP: get_static_site.

Deploy the application

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

Create the application

Create application (createApplicationV3) creates the application with the Ingot build strategy and a web process, and doesn’t deploy it.
CLI: 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.
3

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

Find the H1 size

The API creates the 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.
5

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

Add the VITE_API_URL variable

Create environment variable (createEnvironmentVariableV3) adds VITE_API_URL. Vite reads it during the build, so it needs to be available during the build only.
CLI: sevalla apps env-vars create "$APP_ID" --key VITE_API_URL --value "https://api.example.com", 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.
7

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

Choose the Dockerfile build strategy

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

Deploy

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

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

Read the build log

Get application deployment logs (getApplicationDeploymentLogsV3) returns the build log, one line per entry. With Ingot, the build plan shows Vite, and the alpine:3 runtime image that serves dist with Caddy:
With a Dockerfile, the log shows 📦 Building and pushing docker image using 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.
12

Check the application

List application domains (listApplicationDomainsV3) returns the application’s domains. Request /about on the application’s domain, then the JavaScript that it loads:
CLI: sevalla apps domains list --app-id "$APP_ID". MCP: get_application, in domains.