> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Deploy a Jekyll site

> Deploy a Jekyll 4 site to Sevalla as a free static site that installs Ruby with rv during the build, or as an application built from a Dockerfile that serves it with Caddy.

A Jekyll site builds to static files: `jekyll build` turns Markdown, Liquid templates and the theme into HTML in the `_site` folder. Sevalla publishes that folder as a static site, served from Cloudflare's edge and free up to 100 GB of bandwidth a month. The static site build runs on Node.js and has no Ruby, so the project's build script installs Ruby and the gems with [rv](https://rv.dev), a Ruby version and gem manager, before it runs Jekyll. To run the site as an [application](#deploy-with-a-dockerfile) instead, build it from a Dockerfile.

## At a glance

| | |
| - | - |
| Source | A Git repository |
| Build | Static site: `npm run build`, which runs `build.sh`: rv installs Ruby 4.0 and the gems in `Gemfile.lock`, then `jekyll build` writes `_site`. Application: the [Dockerfile](#deploy-with-a-dockerfile) only; Ingot can't serve a Jekyll site |
| Start command | Application: `caddy run --config /etc/caddy/Caddyfile --adapter caddyfile`, the `Dockerfile`'s `CMD` |
| Port | Application: the `PORT` environment variable, `8080` by default |
| Processes | Application: web process |
| Disk | None |
| Size | Static site: none to choose. Application: **H1** for the web process |

### Environment variables

The site needs no environment variables, as a static site or as an application. The build sets `JEKYLL_ENV=production` itself.

## Before you begin

* A Sevalla company with a payment method, and a GitHub, GitLab or Bitbucket account for the repository. See [Git providers](https://docs.sevalla.com/git/overview.md).
* Ruby 4.0 and Git on your computer. See [Install Jekyll](https://jekyllrb.com/docs/installation/).
* Node.js 24 or later on your computer, to write `package-lock.json`.

## Create the project

Install Jekyll and Bundler, then create a site with Jekyll's default theme, Minima:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
gem install jekyll bundler
jekyll new storefront
cd storefront
```

`jekyll new` writes `_config.yml`, a `Gemfile` and `Gemfile.lock` (Jekyll 4.4.1, Minima 2.5), a home page, an about page, a 404 page and a first post, and runs `bundle install`. Name the site: in `_config.yml`, change `title` and `description`:

```yaml _config.yml theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
title: Storefront
description: News from the Storefront team.
```

Run `bundle exec jekyll serve` and open `http://localhost:4000` to check the site locally.

## Prepare the project for Sevalla

A static site's build always installs npm packages first and stops when there's no `package.json`, so the project gets one, with a `build` script that runs `build.sh`. Pin the Ruby version, and add the npm files:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
echo "4.0" > .ruby-version
npm init -y
npm pkg set scripts.build="bash build.sh"
npm install
```

Add `build.sh`, which installs rv, a pinned release whose checksum it checks, then Ruby, the gems and the site:

```bash build.sh theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
#!/usr/bin/env bash
# Installs rv, which installs Ruby and the gems in Gemfile.lock, then builds the site.
set -euo pipefail
RV_VERSION=0.7.2
RV_SHA256=b8279cd38eef705af1b8b273b356ec0a478e6d1023e1e89883a7249f177fd3e0
curl -fsSL -o /tmp/rv.tar.xz "https://github.com/spinel-coop/rv/releases/download/v$RV_VERSION/rv-x86_64-unknown-linux-gnu.tar.xz"
echo "$RV_SHA256  /tmp/rv.tar.xz" | sha256sum -c -
tar -xJf /tmp/rv.tar.xz -C /tmp
export PATH="/tmp/rv-x86_64-unknown-linux-gnu:$PATH"
rv ruby install
rv ci
JEKYLL_ENV=production rv run bundle exec jekyll build
```

Keep the new files out of the site: add them to the end of `_config.yml`. Jekyll still leaves out the `Gemfile`, `Gemfile.lock` and `node_modules` by default.

```yaml _config.yml theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
exclude:
  - build.sh
  - package.json
  - package-lock.json
```

| File | Why Sevalla needs it |
| - | - |
| `package.json` `scripts.build` | The static site's **Build command**, `npm run build`, runs it. Without a `package.json`, the deployment fails before the build. |
| `package-lock.json` | Sevalla installs with the package manager whose lock file it finds; without one, the build log warns `There is no lock file.` The project has no npm packages, so the install takes less than a second. |
| `build.sh` | The static site's build container has Node.js but no Ruby. rv downloads a precompiled Ruby for the version in `.ruby-version` and installs the exact gems in `Gemfile.lock` (`rv ci`). The script checks the SHA-256 of the rv release it downloads; to update rv, change `RV_VERSION` and `RV_SHA256` to the values on its [releases page](https://github.com/spinel-coop/rv/releases). |
| `.ruby-version` | rv installs the newest Ruby 4.0 release, 4.0.9 at the time of writing. |
| `Gemfile.lock` | rv and the [Dockerfile](#deploy-with-a-dockerfile) install exactly these gems on Linux, so check that it lists `x86_64-linux-gnu` under `PLATFORMS`, for the precompiled `sass-embedded` and `google-protobuf` gems. If it doesn't, run `bundle lock --add-platform x86_64-linux`. |

Commit everything and push it to your Git provider. `jekyll new` doesn't create a Git repository, and its `.gitignore` already leaves out `_site` and `.jekyll-cache`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
git init
git add .
git commit -m "Create the storefront blog"
git branch -M main
git remote add origin https://github.com/acme/storefront.git
git push -u origin main
```

## 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`. Jekyll builds to `_site`, so change the **Publish directory**:

| Field | Value |
| - | - |
| **Build site before publishing** | On |
| **Node version** | **Latest LTS** |
| **Build command** | `npm run build` |
| **Root directory** | Empty, for the repository root |
| **Publish directory** | `_site` |

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

The build log shows `/tmp/rv.tar.xz: OK`, then `Installed Ruby version 4.0.9`, the gems rv installed, and Jekyll's summary ending in `done in`. The build takes about 30 seconds. rv also logs warnings about `/etc/pki/tls/certs` and `ERROR rv::update: Self-update failed: Unable to load receipt for app rv`: it can't update itself in the build container, and the build goes on. See [Build settings](https://docs.sevalla.com/static-sites/build-settings.md) 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 Minima home page opens with the title **Storefront** and the post **Welcome to Jekyll!**.
3. From a terminal, with your site's domain:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s https://storefront-blog-x7k2m.sevalla.page/ | grep -o '<title>[^<]*</title>'
   curl -s https://storefront-blog-x7k2m.sevalla.page/about/ | grep -o '<title>[^<]*</title>'
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   <title>Storefront | News from the Storefront team.</title>
   <title>About | Storefront</title>
   ```

   With the API, [Get static site deployment](https://api-docs.sevalla.com/v3/static-sites/get-static-site-deployment) returns the deployment's status; see [Deploy with the API](#deploy-with-the-api).

![The static site's Details card with its domain, Live status, source repository and the Visit site button](https://assets.sevalla.com/screenshots/light/static-site-overview-visit.png)

A path with no page returns Sevalla's 404 page. To show Jekyll's `404.html` instead, see [Custom 404 pages](https://docs.sevalla.com/static-sites/redirects.md#custom-404-pages). If the deployment fails, open it on the **Deployments** tab and read its build log, then see [Troubleshoot](#troubleshoot).

## Deploy with a Dockerfile

Ingot, the default build strategy for applications, can't serve a Jekyll site: it finds no server to start and stops with `✗ No start command found`. A Jekyll site runs as an application only from a Dockerfile. Use one when the site needs something only applications have, such as promoting one build through the stages of a [pipeline](https://docs.sevalla.com/pipelines/overview.md). Add a `Dockerfile`, a `Caddyfile` and a `.dockerignore` to the root of the repository:

```dockerfile Dockerfile theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
FROM ruby:4.0-trixie AS build
WORKDIR /site
ENV BUNDLE_DEPLOYMENT=1 JEKYLL_ENV=production
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
RUN bundle exec jekyll build

FROM caddy:2-alpine AS caddy
RUN setcap -r /usr/bin/caddy

FROM alpine:3
COPY --from=caddy /usr/bin/caddy /usr/bin/caddy
COPY --from=caddy /etc/mime.types /etc/mime.types
COPY Caddyfile /etc/caddy/Caddyfile
COPY --from=build /site/_site /srv
ENV XDG_CONFIG_HOME=/tmp XDG_DATA_HOME=/tmp
USER 1000:1000
EXPOSE 8080
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
```

```text Caddyfile theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
{
	admin off
	auto_https off
	persist_config off
}

:{$PORT:8080} {
	root * /srv
	encode zstd gzip
	file_server

	handle_errors 404 {
		rewrite * /404.html
		file_server
	}
}
```

| Line or choice | Why |
| - | - |
| `ruby:4.0-trixie` | Ruby 4.0, the line `.ruby-version` pins, on Debian 13, for the build only. The full image has the compiler that the `json` and `bigdecimal` gems need. A line tag, not a digest, so each build picks up Ruby and Debian security fixes. |
| `BUNDLE_DEPLOYMENT=1` | Bundler installs exactly what `Gemfile.lock` lists and fails if the lock file doesn't match the `Gemfile`. `JEKYLL_ENV=production` builds the site for production, as the static site's `build.sh` does. |
| `Gemfile` and `Gemfile.lock` before the source | The [build cache](https://docs.sevalla.com/applications/builds/overview.md#build-cache) reuses the `bundle install` layer until the lock file changes, so a new post doesn't reinstall the gems. |
| Three stages | The `build` stage runs Jekyll. The `caddy` stage takes Caddy from its official image and removes the file capability that lets it bind ports below 1024, which a server running as a regular user on port 8080 doesn't need. The final image is Alpine Linux with Caddy and `_site`: no Ruby, no gems and no source. |
| `USER 1000:1000` | Caddy runs as a regular user, not root. The copied files stay owned by root, so the server can read them but not change them. Caddy keeps its state in `/tmp`, which `XDG_CONFIG_HOME` and `XDG_DATA_HOME` point to. |
| `handle_errors 404` | Answers missing pages with Jekyll's `404.html` and status `404`. Folder URLs such as `/about` redirect to `/about/`, which serves `index.html`. |
| `:{$PORT:8080}` | Caddy listens on `PORT`, which Sevalla sets to the web process's port, or `8080` when it isn't set. Caddy stops gracefully on `SIGTERM` by itself, so the image needs no init process. |

```text .dockerignore theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
.git
.jekyll-cache
.sass-cache
_site
vendor
.env*
*.log
Dockerfile
.dockerignore
```

The image doesn't use `build.sh` or rv, so the site builds the same way with or without the static site setup. Commit and push the three files, then create the application:

**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. Choose the Dockerfile build strategy**

On the **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**.

**3. 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...`, then Bundler's install and Jekyll's summary, and its **Dockerfile** tab shows your file. When the status changes to **Deployed**, click the domain under the application's name: the Minima home page opens. From a terminal:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s https://storefront-x7k2m.sevalla.app/about/ | grep -o '<title>[^<]*</title>'
curl -s -o /dev/null -w '%{http_code}\n' https://storefront-x7k2m.sevalla.app/no-such-page
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
<title>About | Storefront</title>
404
```

If you deploy before you switch the build strategy, Ingot stops with `✗ No start command found`: switch to **Dockerfile** and deploy again. See [Dockerfile](https://docs.sevalla.com/applications/builds/dockerfile.md).

![The Update build strategy sheet with Dockerfile selected, the Dockerfile path, the Docker context and the Docker registry credential](https://assets.sevalla.com/screenshots/light/app-settings-build-strategy-dockerfile.png)

## 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. To move to a newer Jekyll or theme release, run `bundle update`, check the site locally, and commit `Gemfile.lock`. See [Static site deployments](https://docs.sevalla.com/static-sites/deployments.md) and [Deployments](https://docs.sevalla.com/applications/deployments.md).

## Add a custom domain

To serve the site from your own domain, such as `blog.example.com`, add the domain on the static site's **Domains** tab and create the DNS records that Sevalla shows, then set `url` in `_config.yml` to `https://blog.example.com`, so the feed and the canonical links use it. Applications have a **Domains** tab too, but custom domains need a web process larger than **H1**. See [Custom domains](https://docs.sevalla.com/domains/overview.md).

## Troubleshoot

| Symptom | Cause | Fix |
| - | - | - |
| The static site's deployment fails at the install step with `Could not read package.json: Error: ENOENT: no such file or directory, open '/workspace/source/package.json'` | Static site builds install npm packages before the build command, and the repository has no `package.json` | Add `package.json` and `package-lock.json` as in [Prepare the project for Sevalla](#prepare-the-project-for-sevalla) |
| An application's build log shows `📦 Generating Dockerfile using ingot...`, then `✗ No start command found` and `Add a "start" script to package.json, set "main", or set "start" in ingot.json.` | The application builds with Ingot, which finds no server to start in a Jekyll site | Switch the build strategy to **Dockerfile** and deploy again, as in [Deploy with a Dockerfile](#deploy-with-a-dockerfile) |
| The build log shows `DEPRECATION WARNING [import]: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.` and warnings about `lighten()` and `darken()` | Minima 2.5's stylesheets use Sass features that Dart Sass, which Jekyll 4.4 uses, deprecates | Nothing: the build succeeds. The warnings go away with a theme that doesn't use these features |

For more causes, see [Troubleshoot static sites](https://docs.sevalla.com/static-sites/troubleshooting.md) and [Troubleshoot builds and deployments](https://docs.sevalla.com/applications/troubleshooting/builds-and-deployments.md).

## Deploy with the API

This sequence creates the static site from [Deploy as a static site](#deploy-as-a-static-site) and the application from [Deploy with a Dockerfile](#deploy-with-a-dockerfile) through the [Sevalla API](https://docs.sevalla.com/developer-tools/api.md), 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](https://docs.sevalla.com/developer-tools/cli.md) command and [MCP server](https://docs.sevalla.com/ai-agents/overview.md) tool.

You need:

* An [API key](https://docs.sevalla.com/developer-tools/api-keys.md) 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](#prepare-the-project-for-sevalla), with the files from [Deploy with a Dockerfile](#deploy-with-a-dockerfile).
* A GitHub account that can read the repository, connected to Sevalla. See [Git providers](https://docs.sevalla.com/git/overview.md). The API can't list connected accounts: when none can read the repository, [Create static site](https://api-docs.sevalla.com/v3/static-sites/create-static-site) and [Create application](https://api-docs.sevalla.com/v3/applications/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](https://api-docs.sevalla.com/v3/static-sites/create-static-site) (`createStaticSiteV3`) takes the build settings: `node_version` `lts` is the dashboard's **Latest LTS**, and `published_directory` is the **Publish directory**.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
SITE_ID=$(curl -s https://api.sevalla.com/v3/static-sites \
  -H "Authorization: Bearer $SEVALLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<JSON | jq -r .id
{
  "display_name": "storefront-blog",
  "source": "privateGit",
  "git_type": "github",
  "repo_url": "https://github.com/acme/storefront",
  "default_branch": "main",
  "build_command": "npm run build",
  "published_directory": "_site",
  "node_version": "lts",
  "auto_deploy": true
}
JSON
)
```

CLI: `sevalla static-sites create --name storefront-blog --repository https://github.com/acme/storefront --branch main --source privateGit --git-type github --build-command "npm run build" --publish-directory _site --node-version lts`. MCP: `create_static_site` with the same fields.

**2. Deploy the static site**

[Deploy static site](https://api-docs.sevalla.com/v3/static-sites/deploy-static-site) (`deployStaticSiteV3`) starts the first deployment. Unlike the dashboard, creating a static site through the API doesn't start one.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
SITE_DEPLOYMENT_ID=$(curl -s https://api.sevalla.com/v3/static-sites/$SITE_ID/deployments \
  -H "Authorization: Bearer $SEVALLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
```

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](https://api-docs.sevalla.com/v3/static-sites/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.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for i in $(seq 1 120); do
  STATUS=$(curl -s "https://api.sevalla.com/v3/static-sites/$SITE_ID/deployments/$SITE_DEPLOYMENT_ID" \
    -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

If it's `failed`, [Get static site deployment logs](https://api-docs.sevalla.com/v3/static-sites/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](https://api-docs.sevalla.com/v3/static-sites/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:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
SITE_URL=https://$(curl -s https://api.sevalla.com/v3/static-sites/$SITE_ID \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r .hostname)
for i in $(seq 1 12); do
  [ "$(curl -s -o /dev/null -w '%{http_code}' $SITE_URL/about/)" = 200 ] && break
  sleep 10
done
curl -s $SITE_URL/about/ | grep -o '<title>[^<]*</title>'
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
<title>About | Storefront</title>
```

CLI: `sevalla static-sites get "$SITE_ID"`. MCP: `get_static_site`.

**5. Look up the location and the size**

[List clusters](https://api-docs.sevalla.com/v3/resources/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](https://api-docs.sevalla.com/v3/resources/list-process-resource-types) (`listProcessResourceTypesV3`) returns the process sizes.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
CLUSTER_ID=$(curl -s https://api.sevalla.com/v3/resources/clusters \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.[] | select(.location == "eu-frankfurt-1") | .id')
H1_ID=$(curl -s https://api.sevalla.com/v3/resources/process-resource-types \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.[] | select(.name == "h1") | .id')
```

CLI: `sevalla resources clusters` and `sevalla resources process-types`. MCP: `get_platform_options`.

**6. Create the application**

[Create application](https://api-docs.sevalla.com/v3/applications/create-application) (`createApplicationV3`) creates the application with a web process and doesn't deploy it.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
APP_ID=$(curl -s https://api.sevalla.com/v3/applications \
  -H "Authorization: Bearer $SEVALLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<JSON | jq -r .id
{
  "display_name": "storefront",
  "cluster_id": "$CLUSTER_ID",
  "source": "privateGit",
  "git_type": "github",
  "repo_url": "https://github.com/acme/storefront",
  "default_branch": "main"
}
JSON
)
```

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](https://api-docs.sevalla.com/v3/applications/list-processes) (`listProcessesV3`) returns the web process, and [Update process](https://api-docs.sevalla.com/v3/applications/update-process) (`updateProcessV3`) sets its size. Keep **S1**, and skip this step, to add a custom domain later.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
WEB_ID=$(curl -s https://api.sevalla.com/v3/applications/$APP_ID/processes \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.data[] | select(.type == "web") | .id')
curl -s -X PATCH https://api.sevalla.com/v3/applications/$APP_ID/processes/$WEB_ID \
  -H "Authorization: Bearer $SEVALLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"resource_type_id\": \"$H1_ID\"}" | jq -r .resource_type_name
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
h1
```

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**

[Update application](https://api-docs.sevalla.com/v3/applications/update-application) (`updateApplicationV3`) sets the build strategy. Ingot can't serve the site, so deploy only after this step.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s -X PATCH https://api.sevalla.com/v3/applications/$APP_ID \
  -H "Authorization: Bearer $SEVALLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"build_type": "dockerfile", "dockerfile_path": "Dockerfile", "docker_context": ".", "auto_deploy": true}' | jq -c '{build_type, auto_deploy}'
```

```json theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
{"build_type":"dockerfile","auto_deploy":true}
```

The same call turns on auto-deploy. CLI: `sevalla apps update "$APP_ID" --data` with the same body. MCP: `sevalla_api_update` with `updateApplicationV3` and the same body.

**9. Deploy**

[Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) (`triggerDeploymentV3`) builds the latest commit of the branch and rolls it out.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
DEPLOYMENT_ID=$(curl -s https://api.sevalla.com/v3/applications/$APP_ID/deployments \
  -H "Authorization: Bearer $SEVALLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
```

CLI: `sevalla apps deployments trigger "$APP_ID"`. MCP: `trigger_deployment`.

**10. Wait for the deployment**

[Get deployment](https://api-docs.sevalla.com/v3/applications/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.

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for i in $(seq 1 120); do
  STATUS=$(curl -s "https://api.sevalla.com/v3/applications/$APP_ID/deployments/$DEPLOYMENT_ID" \
    -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
success
```

CLI: `sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID"`. MCP: `get_deployment`.

**11. Check the application**

[Get application deployment logs](https://api-docs.sevalla.com/v3/applications/get-application-deployment-logs) (`getApplicationDeploymentLogsV3`) returns the build log, which shows Jekyll's summary and, on a failed deployment, ends with the error. [List application domains](https://api-docs.sevalla.com/v3/applications/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:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s "https://api.sevalla.com/v3/applications/$APP_ID/deployments/$DEPLOYMENT_ID/logs?limit=5000" \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.[].message'
APP_URL=https://$(curl -s https://api.sevalla.com/v3/applications/$APP_ID/domains \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.data[] | select(.is_primary) | .name')
for i in $(seq 1 12); do
  [ "$(curl -s -o /dev/null -w '%{http_code}' $APP_URL/)" = 200 ] && break
  sleep 5
done
curl -s $APP_URL/about/ | grep -o '<title>[^<]*</title>'
curl -s -o /dev/null -w '%{http_code}\n' $APP_URL/no-such-page
```

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.