Skip to main content
A VitePress site builds to static files: vitepress build renders every Markdown page to HTML in the .vitepress/dist folder, with the Vue code that makes it a single-page app in the browser. Sevalla publishes that folder as a static site, served from Cloudflare’s edge and free up to 100 GB of bandwidth a month. When the site has to run as an application instead, Ingot, Sevalla’s builder, builds it and serves .vitepress/dist with Caddy.

At a glance

Environment variables

The site needs no environment variables, as a static site or as an application.

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 VitePress 1, the current release, and add scripts that run it:
VitePress’s own npx vitepress init asks for the same settings in an interactive wizard. Here, the site lives at the repository root, so it builds to .vitepress/dist. Add the configuration with the default theme:
.vitepress/config.mts
Then a home page and a first guide:
index.md
guide.md
Keep the dependencies and VitePress’s output and cache out of the repository:
.gitignore
VitePress writes guide.md to guide.html and links to it as /guide.html. Leave its cleanUrls option off: Sevalla’s static sites serve /guide.html but answer /guide with 404.

Prepare the project for Sevalla

Pin the Node.js version for Ingot:
Run npm run build && npm run preview and open http://localhost:4173 to check the build 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

Set the build settings

Under Build settings, Sevalla fills in npm run build and dist from package.json. VitePress builds to .vitepress/dist, so change the Publish directory:
3

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.
Static sites don’t read engines in package.json: the build uses the Node version from the build settings, and npm only warns that 24.x doesn’t match. 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 home page opens on its *.sevalla.page domain, and Get started opens /guide.html.
  3. From a terminal, with your site’s domain:
    With the API, Get static site deployment returns the deployment’s status; see Deploy with the API.
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
A path with no page returns Sevalla’s 404 page. To show VitePress’s own 404.html instead, see Custom 404 pages. If the deployment fails, open it on the Deployments tab and read its build log, then see Troubleshoot.

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 site needs something only applications have, such as promoting one build through the stages of a pipeline. Ingot detects VitePress from package.json, runs the build script and serves the .vitepress/dist folder with Caddy. 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

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 application

  1. On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: VitePress 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. The home page opens. From a terminal:
    With the API, Get deployment returns the deployment’s status and Get application deployment logs its build log; see Deploy with the API.
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
Caddy serves .vitepress/dist on the port in PORT, answers missing pages with VitePress’s 404.html and status 404, 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 and 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, the commands in Verify the application print the same output. 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. See Static site deployments and Deployments.

Add a custom domain

To serve the site from your own domain, such as docs.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. See Custom domains.

Troubleshoot

For more causes, see Troubleshoot static sites and Troubleshoot Ingot builds.

Deploy with the API

This sequence creates the static site from Deploy as a static site and the application from Deploy as an application through the Sevalla API, so an agent or a script can run it as is. Run the first four steps for the static site, the rest for the application. Each step names the operation, shows the curl command, and gives the matching CLI command and MCP server tool. You need:
  • An API key with the Company developer role, in the SEVALLA_API_KEY environment variable. The CLI reads the same key from SEVALLA_API_TOKEN.
  • curl and jq, and the project pushed to GitHub as in Prepare the project for Sevalla.
  • A GitHub account that can read the repository, connected to Sevalla. See Git providers. The API can’t list connected accounts: when none can read the repository, Create static site and Create application fail with 400 and Cannot access repository https://github.com/acme/storefront: Repository not found or you don't have access.
A call that fails returns status and message instead of the resource, and jq -r .id prints null: stop and read the message.
1

Create the static site

Create static site (createStaticSiteV3) takes the build settings: node_version lts is the dashboard’s Latest LTS, and published_directory is the Publish directory.
CLI: sevalla static-sites create --name storefront-docs --repository https://github.com/acme/storefront --branch main --source privateGit --git-type github --build-command "npm run build" --publish-directory .vitepress/dist --node-version lts. MCP: create_static_site with the same fields.
2

Deploy the static site

Deploy static site (deployStaticSiteV3) starts the first deployment. Unlike the dashboard, creating a static site through the API doesn’t start one.
CLI: sevalla static-sites deployments trigger "$SITE_ID". MCP: trigger_deployment with service_type static_site.
3

Wait for the static site deployment

Get static site deployment (getStaticSiteDeploymentV3) returns the deployment’s status: waiting, then inProgress, then success or failed. The loop polls every 15 seconds for up to 30 minutes and keeps polling through a failed request.
If it’s failed, Get static site deployment logs (getStaticSiteDeploymentLogsV3) returns the build log. CLI: sevalla static-sites deployments get "$SITE_DEPLOYMENT_ID" --site-id "$SITE_ID" and sevalla static-sites deployments logs "$SITE_DEPLOYMENT_ID" --site-id "$SITE_ID". MCP: get_deployment and get_deployment_logs with service_type static_site.
4

Check the static site

Get static site (getStaticSiteV3) returns the site’s domain in hostname. Right after a deployment reports success, Cloudflare’s edge can answer with Sevalla’s 404 page for a short time, so the loop waits up to 2 minutes for 200 before the check:
CLI: sevalla static-sites get "$SITE_ID". MCP: get_static_site.
5

Look up the location and the size

List clusters (listClustersV3) returns every location your company can use, with its region ID. This guide uses Frankfurt, Germany, eu-frankfurt-1. List process resource types (listProcessResourceTypesV3) returns the process sizes.
CLI: sevalla resources clusters and sevalla resources process-types. MCP: get_platform_options.
6

Create the application

Create application (createApplicationV3) creates the application with 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.
7

Size the web process

The API creates the web process at S1, while the dashboard creates it at H1, which is enough for Caddy. List processes (listProcessesV3) returns the web process, and Update process (updateProcessV3) sets its size. Keep S1, and skip this step, to add a custom domain later.
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.
8

Choose the Dockerfile build strategy (optional)

To build from the Dockerfile in Deploy with a Dockerfile instead of with Ingot, Update application (updateApplicationV3) sets the build strategy:
CLI: sevalla apps update "$APP_ID" --data with the same body. MCP: sevalla_api_update with updateApplicationV3 and the same body.
9

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

Deploy

Trigger deployment (triggerDeploymentV3) builds the latest commit of the branch and rolls it out.
CLI: sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.
11

Wait for the deployment

Get deployment (getDeploymentV3) returns the deployment’s status: waiting, then inProgress, then success or failed. The loop polls every 15 seconds for up to 30 minutes and keeps polling through a failed request.
CLI: sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.
12

Check the application

Get application deployment logs (getApplicationDeploymentLogsV3) returns the build log, which starts with the build plan in Verify the application and, on a failed deployment, ends with the error. List application domains (listApplicationDomainsV3) returns the application’s domain. Right after a deployment reports success, the domain can answer 503 for a few seconds while traffic moves to the new instance, so the loop waits up to 1 minute for 200 before the checks:
After the build log, the output is the same title as for the static site, then 404 from Caddy for a missing page.CLI: sevalla apps deployments logs "$DEPLOYMENT_ID" --app-id "$APP_ID" and sevalla apps domains list --app-id "$APP_ID". MCP: get_deployment_logs and get_application.