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:- React
- Vue
--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:
- React
- Vue
src/main.tsx with this:/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
Choose the repository
Check the build settings
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:Add the build-time variable
VITE_API_URL as the Key and https://api.example.com as the Value.Create the static site
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
- 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.
- On the Overview tab, click Visit site. The Vite starter page opens on the site’s
*.sevalla.pagedomain. - Add
/aboutto the address and press Enter. The page shows About Storefront andOrders API: https://api.example.com.


/about and the JavaScript it loads, which holds the value of VITE_API_URL:
/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 eachimport.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.
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:Create the application
Add the build-time variable
VITE_API_URL as the key and https://api.example.com as the value, keep Available during buildtime selected, and click Add environment variables.Deploy
Turn on auto-deploy
Verify the application
-
On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: Vite from
package.json, built onnode:24-trixieand served from analpine:3image with Caddy.Build log - 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, then add/aboutto the address. The page shows About Storefront andOrders API: https://api.example.com.


/about and the JavaScript it loads:
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 aDockerfile, a Caddyfile and a .dockerignore to the root of the repository:
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:
Choose the Dockerfile build strategy
Dockerfile and the Docker context ., then click Update build strategy.Deploy
📦 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 asshop.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 holdsnull 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:READandAPP:UPDATEpermissions for the application and theSTATIC_SITE:CREATE,STATIC_SITE:READandSTATIC_SITE:UPDATEpermissions for the static site, in theSEVALLA_API_KEYenvironment variable. The CLI reads the key fromSEVALLA_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
400withCannot access repository https://github.com/acme/storefront: Repository not found or you don't have access., followed by where to grant the access. curlandjq.
Deploy the static site
Create the 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.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.Add the build-time variable
createStaticSiteEnvVarV3) adds VITE_API_URL, which vite build writes into the JavaScript. It applies to production and preview deployments by default.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.Deploy the 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.sevalla static-sites deployments trigger "$SITE_ID". MCP: trigger_deployment with service_type set to static_site.Wait for the 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.Check the 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:sevalla static-sites get "$SITE_ID". MCP: get_static_site.Deploy the application
Find the location
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.sevalla resources clusters. MCP: get_platform_options.Create the application
createApplicationV3) creates the application with the Ingot build strategy and a web process, and doesn’t deploy it.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.Find the web process
listProcessesV3) returns the web process that Create application added:sevalla apps processes list --app-id "$APP_ID". MCP: get_application, in processes.Find the H1 size
listProcessResourceTypesV3) returns the ID of each size:sevalla resources process-types. MCP: get_platform_options.Set the web process size
updateProcessV3) sets the size, which applies at the first deployment: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.Add the VITE_API_URL variable
createEnvironmentVariableV3) adds VITE_API_URL. Vite reads it during the build, so it needs to be available during the build only.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.Turn on auto-deploy
updateApplicationV3) with auto_deploy deploys every push to the branch: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.Choose the Dockerfile build strategy
updateApplicationV3) sets the build strategy:sevalla apps update "$APP_ID" --data '{"build_type":"dockerfile","dockerfile_path":"Dockerfile","docker_context":"."}'. MCP: sevalla_api_update with updateApplicationV3 and the same body.Deploy
triggerDeploymentV3) deploys the latest commit of the branch:sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.Wait for the deployment
getDeploymentV3) returns the deployment’s status: waiting, inProgress, then success, failed, cancelled or skipped. Poll it until the deployment ends:success means the application is deployed. CLI: sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.Read the build log
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:📦 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.Check the application
listApplicationDomainsV3) returns the application’s domains. Request /about on the application’s domain, then the JavaScript that it loads:sevalla apps domains list --app-id "$APP_ID". MCP: get_application, in domains.