> ## 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 Ktor application

> Deploy a Ktor 3.6 server in Kotlin to Sevalla: a Gradle Kotlin DSL project with the Ktor Gradle plugin, Java 25, the PORT variable, and a build with Ingot or a Dockerfile.

A Ktor server runs on Sevalla as a Kotlin application on the JVM, built from your Git repository: [Ingot](https://docs.sevalla.com/applications/builds/ingot.md), Sevalla's builder, recognizes the Gradle build, builds it with the project's Gradle wrapper, and the web process runs the executable jar that the Ktor Gradle plugin builds with `java -jar /app/app.jar`. The server reads the port that Sevalla sets in the `PORT` environment variable itself.

## At a glance

| | |
| - | - |
| Source | A Git repository |
| Build | [Ingot](https://docs.sevalla.com/applications/builds/ingot.md) (Java 25, Gradle), or the [Dockerfile](#deploy-with-a-dockerfile) |
| Start command | `java -jar /app/app.jar`, the jar that the Ktor Gradle plugin builds |
| Port | The `PORT` environment variable, `8080` by default |
| Processes | Web process |
| Services | None |
| Disk | None |
| Health check | None |
| Size | **H1** for the web process |

### Environment variables

The application needs no environment variables of its own. It reads `PORT`, which Sevalla sets to the web process port.

## 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).
* A Java 25 JDK on your computer, and [Gradle](https://gradle.org/install/) 9 to create the project's Gradle wrapper once. After that, the wrapper runs the builds.

## Create the project

This guide writes a minimal project by hand: two Gradle files and one Kotlin file. Create the folders:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
mkdir storefront && cd storefront
mkdir -p src/main/kotlin/com/example
```

Name the project:

```kotlin settings.gradle.kts theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
rootProject.name = "storefront"
```

Define the build:

```kotlin build.gradle.kts theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
plugins {
    kotlin("jvm") version "2.4.21"
    id("io.ktor.plugin") version "3.6.0"
}

group = "com.example"
version = "1.0.0"

application {
    mainClass = "com.example.ApplicationKt"
}

kotlin {
    jvmToolchain(25)
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.ktor:ktor-server-netty")
    implementation("org.slf4j:slf4j-simple:2.0.20")
}

tasks.shadowJar {
    manifest {
        attributes("Enable-Native-Access" to "ALL-UNNAMED")
    }
}
```

| Part | What it does |
| - | - |
| `kotlin("jvm")` | Kotlin 2.4.21 for the JVM |
| `io.ktor.plugin` | The Ktor Gradle plugin, which projects from the [Ktor project generator](https://start.ktor.io) use too. It sets every Ktor library to its own version, 3.6.0, and builds `build/libs/storefront-all.jar`, one executable jar with every dependency, which is what Ingot runs. |
| `application` | Names the main class, so `./gradlew run` and the jar's manifest start the server. The Ktor plugin applies Gradle's `application` plugin, which reads it. |
| `jvmToolchain(25)` | Compiles and runs with Java 25, and tells Ingot which Java to build on |
| `ktor-server-netty` | Ktor on the Netty engine, without a version: the Ktor plugin sets it |
| `slf4j-simple` | Prints Ktor's log at `INFO` level. Without a logging library, Ktor's log is lost. |
| `Enable-Native-Access` | Lets Netty load its native library on Java 25 without a warning in the log. The Ktor plugin builds the jar with the Shadow plugin's `shadowJar` task, so the attribute goes in that task's manifest. |

Create the server:

```kotlin src/main/kotlin/com/example/Application.kt theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
package com.example

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun main() {
    val port = System.getenv("PORT")?.toInt() ?: 8080
    embeddedServer(Netty, port = port, host = "0.0.0.0", module = Application::module)
        .start(wait = true)
}

fun Application.module() {
    monitor.subscribe(ApplicationStopped) {
        log.info("Server stopped")
    }
    routing {
        get("/") {
            call.respondText("Hello from Ktor on Sevalla")
        }
    }
}
```

* The server listens on `PORT`, which Sevalla sets to the web process's port (`8080` by default), on all network interfaces. On your computer, it uses port 8080.
* The `ApplicationStopped` handler logs `Server stopped` when the server shuts down. See [Shut down gracefully](#shut-down-gracefully).

Create the Gradle wrapper, which pins the Gradle version for every build:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
gradle wrapper --gradle-version 9.8.2
```

Keep Gradle's and Kotlin's local folders and the build output out of the repository:

```text .gitignore theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
.gradle/
.kotlin/
build/
```

## Prepare the project for Sevalla

The project needs no build or start configuration:

| File | Why Sevalla needs it |
| - | - |
| `build.gradle.kts` and `settings.gradle.kts` | Ingot recognizes a Gradle project by them. It reads the Java version from `jvmToolchain(25)`, and because the build applies the Ktor Gradle plugin, it runs the `-all.jar`. See [Pin the Java version](https://docs.sevalla.com/guides/java/overview.md#pin-the-java-version). |
| `gradlew` and `gradle/wrapper/` | Ingot builds with the wrapper, so the build runs Gradle 9.8.2. Commit both. |
| `src/main/kotlin/com/example/Application.kt` | The server, which reads `PORT` itself |

From these files, Ingot:

* Builds on the Java 25 JDK with `./gradlew --no-daemon assemble`, which builds the jars without running the tests; run them in CI.
* Copies `build/libs/storefront-all.jar` into the image as `/app/app.jar`, and runs it on the Java 25 runtime (JRE) as user `1000:1000`, with `JAVA_TOOL_OPTIONS` set to `-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError`. See [Size the JVM memory](https://docs.sevalla.com/guides/java/overview.md#size-the-jvm-memory).

Run `./gradlew run` and open `http://localhost:8080` to check the server locally. Then commit everything and push it to your Git provider:

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

## Deploy to Sevalla

These steps build with [Ingot](https://docs.sevalla.com/applications/builds/ingot.md). To build from your own Dockerfile instead, see [Deploy with a Dockerfile](#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](https://docs.sevalla.com/applications/deployments.md#deploy-automatically-on-push).

## Verify the deployment

1. On the **Deployments** tab, click the deployment. Its **Build log** shows Ingot's [build plan](https://docs.sevalla.com/applications/builds/ingot.md#read-the-build-plan): Java 25 from `jvmToolchain` in `build.gradle.kts`, Gradle from the wrapper, and `java -jar` for the executable jar.

   ```text Build log theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   ╭─ ■ Java 25 application ────────────────────────────────────────────────╮
   │ Java        25                      build.gradle.kts jvmToolchain "25" │
   │ Build tool  Gradle                  gradlew                            │
   │ Start       java -jar /app/app.jar  executable jar                     │
   ├─ Image ────────────────────────────────────────────────────────────────┤
   │ Build       eclipse-temurin:25-jdk-resolute                            │
   │ Runtime     eclipse-temurin:25-jre-resolute                            │
   │ User        1000:1000                                                  │
   │ Init        tini                                                       │
   │ Port        $PORT, default 8080                                        │
   ╰───────────────────────────────────────────────────────── Ingot v0.4.1 ─╯
   ```

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 page shows `Hello from Ktor on Sevalla`.

![The application header with its name, Running status, repository, branch and the domain that opens the application](https://assets.sevalla.com/screenshots/light/app-overview-address.png)

The application's [runtime logs](https://docs.sevalla.com/applications/logs.md) show `Application started in`, followed by the startup time, and `Responding at http://0.0.0.0:8080`. If the deployment fails, read its build log, then see [Troubleshoot](#troubleshoot).

To check the deployment from a terminal or an AI agent, read its `status` with [Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) until it's `success`, and its build log with [Get application deployment logs](https://api-docs.sevalla.com/v3/applications/get-application-deployment-logs) (`getApplicationDeploymentLogsV3`), as in [Deploy with the API](#deploy-with-the-api). Then request the endpoints:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s -w '\n' https://storefront-x7k2m.sevalla.app/
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
Hello from Ktor on Sevalla
```

[Get application runtime logs](https://api-docs.sevalla.com/v3/applications/get-application-runtime-logs) (`getApplicationRuntimeLogsV3`) with `search` finds the address Ktor listens on:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s "https://api.sevalla.com/v3/applications/$APP_ID/runtime-logs?search=Responding%20at" \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.[].message'
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
[DefaultDispatcher-worker-1] INFO io.ktor.server.Application - Responding at http://0.0.0.0:8080
```

## Shut down gracefully

When Sevalla stops an instance, during a deployment, a restart or a scale-down, it waits 5 seconds, sends `SIGTERM`, and stops the instance if it's still running 30 seconds after the shutdown began. See [How processes run](https://docs.sevalla.com/applications/processes.md#how-processes-run).

`embeddedServer` registers a shutdown hook with the JVM, which handles `SIGTERM`: the hook stops the server, and the `ApplicationStopped` handler logs `Server stopped`. That line ends the old instance's [runtime logs](https://docs.sevalla.com/applications/logs.md) on every deployment. Ktor doesn't wait for requests still in progress when it stops: it cancels them. Keep requests shorter than the 5 seconds between the start of the shutdown and `SIGTERM`, or move long work to a [background worker](https://docs.sevalla.com/applications/processes.md#background-workers).

## 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. Add a `Dockerfile` and a `.dockerignore` to the root of the repository:

```dockerfile Dockerfile theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
FROM gradle:9-jdk25 AS build
WORKDIR /build
COPY settings.gradle.kts build.gradle.kts ./
RUN gradle --no-daemon --quiet dependencies > /dev/null
COPY src ./src
RUN gradle --no-daemon buildFatJar

FROM eclipse-temurin:25-jre-resolute
WORKDIR /app
COPY --from=build /build/build/libs/*-all.jar app.jar
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError"
USER 1000:1000
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
```

| Line or choice | Why |
| - | - |
| `gradle:9-jdk25` | Gradle 9, the major version the wrapper pins, on the Java 25 JDK that `jvmToolchain(25)` asks for. The official image has Gradle already, so the build doesn't download it. A line tag, not a digest, so each build gets the newest Gradle 9 release and Java's and Ubuntu's security fixes. |
| Build files copied before the source | `gradle dependencies` downloads the Kotlin and Ktor plugins and resolves the dependencies in a layer that Docker reuses until a build file changes. A code change starts from there. |
| `gradle buildFatJar` | The Ktor plugin's task for the executable jar, `build/libs/storefront-all.jar`. It builds only that jar, without the tests or the plain jar that `assemble` also builds. |
| `eclipse-temurin:25-jre-resolute` | The Java 25 runtime without the compiler, on Ubuntu 26.04: the image Ingot runs the jar on. It has `/bin/sh` for the [web terminal](https://docs.sevalla.com/applications/web-terminal.md) and custom start commands. |
| `COPY` without `--chown`, then `USER 1000:1000` | The server runs as user `1000` (`ubuntu` in this image). The jar stays owned by root, so the server can read it but not change it. |
| `JAVA_TOOL_OPTIONS` | Gives the heap 75% of the memory limit instead of 25%, and exits on `OutOfMemoryError` so Sevalla restarts the instance, as Ingot's image does. See [Size the JVM memory](https://docs.sevalla.com/guides/java/overview.md#size-the-jvm-memory). |
| `CMD ["java", "-jar", "app.jar"]` | The exec form runs `java` as process 1, so it receives `SIGTERM` directly and runs Ktor's shutdown hook, so the image needs no init process. |
| `EXPOSE 8080` | Documents Sevalla's default `PORT`. The server reads `PORT` when it starts. |

```text .dockerignore theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
.git
.gradle
.kotlin
build
.env*
*.log
```

The `.dockerignore` keeps the Git history, Gradle's and Kotlin's local folders, a local build in `build/`, local environment files and logs out of the build.

Commit and push both files. Ingot doesn't use the `Dockerfile`, so you can keep it in a repository that builds with Ingot. Ingot adds the `.dockerignore` patterns to its own, and its build plan shows `Ignored 6 patterns from .dockerignore`; none of them matches a file the Ingot build needs. 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 deployment's status is **Deployed**, the domain shows `Hello from Ktor on Sevalla` again. On the next deployment after a code change, the build log shows `CACHED` for the `gradle dependencies` step. See [Dockerfile](https://docs.sevalla.com/applications/builds/dockerfile.md).

With the API, [Update application](https://api-docs.sevalla.com/v3/applications/update-application) (`updateApplicationV3`) switches the build strategy. Then deploy and wait as in [Deploy with the API](#deploy-with-the-api):

```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":"."}' \
  | jq -c '{build_type, dockerfile_path, docker_context}'
```

```json theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
{"build_type":"dockerfile","dockerfile_path":"Dockerfile","docker_context":"."}
```

The CLI does the same with `sevalla apps update $APP_ID --build-type dockerfile --dockerfile-path Dockerfile`. The new deployment's build log names the Dockerfile build:

```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?search=Building%20and%20pushing" \
  -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.[].message'
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
📦 Building and pushing docker image using Dockerfile...
```

## Deploy changes

With **Auto-deploy** on, every push to the branch starts a deployment; otherwise, click **Deploy now** for each change. Each deployment builds the image, then replaces the web process's instances. See [Deployments](https://docs.sevalla.com/applications/deployments.md).

## Use environment variables

Add configuration on the application's **Environment variables** tab and read it with `System.getenv`, as the server reads `PORT`. Variables apply at the next deployment. With Ingot, variables marked **Available during buildtime** also reach the Gradle build; with a Dockerfile, they arrive as build arguments, which a stage reads only after an `ARG` line for each one. See [Environment variables](https://docs.sevalla.com/applications/environment-variables.md).

## Add a custom domain

To serve the 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](https://docs.sevalla.com/applications/scaling.md) the web process first. See [Custom domains](https://docs.sevalla.com/domains/overview.md).

## Troubleshoot

| Symptom | Cause | Fix |
| - | - | - |
| The runtime logs show `WARNING: java.lang.System::loadLibrary has been called by io.netty.util.internal.NativeLibraryUtil` | The jar's manifest doesn't allow native access, which Java 25 warns about | Add the `tasks.shadowJar` block with `Enable-Native-Access` from [Create the project](#create-the-project) |

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

## Deploy with the API

These calls make the same deployment as [Deploy to Sevalla](#deploy-to-sevalla), in an order that a script or an AI agent runs as it is. Each step names the [Sevalla API](https://docs.sevalla.com/developer-tools/api.md) operation, then the [CLI](https://docs.sevalla.com/developer-tools/cli.md) command and the [MCP server](https://docs.sevalla.com/ai-agents/overview.md) tool that do the same. You need:

* An [API key](https://docs.sevalla.com/developer-tools/api-keys.md) 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 a Git provider that's connected to your Sevalla account. See [Git providers](https://docs.sevalla.com/git/overview.md). Creating the application checks it: when 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 access.
* `curl` and `jq`.

**1. Choose the location**

[List clusters](https://api-docs.sevalla.com/v3/resources/list-clusters) (`listClustersV3`) returns the locations your company can use, each with its `id`, the `display_name` that the dashboard shows and its region ID in `location`. Keep the ID of the one you want by its region ID, such as `eu-frankfurt-1` for Frankfurt, Germany. [Locations](https://docs.sevalla.com/platform/locations.md) lists them all:

```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')
```

CLI: `sevalla resources clusters`. MCP: `get_platform_options`.

**2. Create the application**

[Create application](https://api-docs.sevalla.com/v3/applications/create-application) (`createApplicationV3`) creates the application with a web process. It builds with Ingot (`build_type` is `ingot`) and isn't deployed yet:

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

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

[List application domains](https://api-docs.sevalla.com/v3/applications/list-application-domains) (`listApplicationDomainsV3`) returns the domain that Sevalla gives the application. It appears a few seconds after the application is created, so ask until it's there:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for i in $(seq 1 24); do
  HOST=$(curl -s https://api.sevalla.com/v3/applications/$APP_ID/domains \
    -H "Authorization: Bearer $SEVALLA_API_KEY" | jq -r '.data[]? | select(.is_primary) | .name')
  [ -n "$HOST" ] && break
  sleep 5
done
echo "$HOST"
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
storefront-x7k2m.sevalla.app
```

CLI: `sevalla apps domains list --app-id $APP_ID`. MCP: `get_application`.

**4. Find the web process**

[List processes](https://api-docs.sevalla.com/v3/applications/list-processes) (`listProcessesV3`) returns the web process that the application was created with:

```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')
```

CLI: `sevalla apps processes list --app-id $APP_ID`. MCP: `get_application`.

**5. Find the H1 size**

[List process resource types](https://api-docs.sevalla.com/v3/resources/list-process-resource-types) (`listProcessResourceTypesV3`) returns the sizes by `name`, in lowercase:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
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 process-types`. MCP: `get_platform_options`.

**6. Set the size**

The API creates the web process at **S1**, where the dashboard's default is **H1**. [Update process](https://api-docs.sevalla.com/v3/applications/update-process) (`updateProcessV3`) sets the size:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
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`.

**7. Turn on auto-deploy**

[Update application](https://api-docs.sevalla.com/v3/applications/update-application) (`updateApplicationV3`) turns on automatic deployments for every push to the branch:

```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 '{"auto_deploy":true}' | jq .auto_deploy
```

CLI: `sevalla apps update $APP_ID --data '{"auto_deploy":true}'`. MCP: `sevalla_api_update` with `updateApplicationV3`.

**8. Deploy**

[Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) (`triggerDeploymentV3`) deploys the latest commit on the branch:

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

CLI: `sevalla apps deployments trigger $APP_ID --branch main`. MCP: `trigger_deployment`.

**9. Wait for the deployment**

[Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) returns the deployment's `status`: `waiting` and `inProgress` while it runs, then `success`, `failed`, `cancelled` or `skipped`. Poll it until the status is final, for up to 30 minutes:

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

**10. Read the build log**

[Get application deployment logs](https://api-docs.sevalla.com/v3/applications/get-application-deployment-logs) (`getApplicationDeploymentLogsV3`) returns the build log, oldest line first: the build plan from [Verify the deployment](#verify-the-deployment), the build and the rollout. When the status is `failed`, the last lines say why.

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

CLI: `sevalla apps deployments logs $DEPLOYMENT_ID --app-id $APP_ID`. MCP: `get_deployment_logs`.

**11. Check the application**

Request the home page from the application's domain:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s -w '\n' https://$HOST/
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
Hello from Ktor on Sevalla
```

Then run the other checks in [Verify the deployment](#verify-the-deployment).


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