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

> Deploy a Streamlit 1.66 app to Sevalla with uv, built by Ingot or your own Dockerfile: the WebSocket through Sevalla's proxy, file uploads, health checks and secrets.

A Streamlit app runs on Sevalla as an application: [Ingot](https://docs.sevalla.com/applications/builds/ingot.md), Sevalla's builder, installs its dependencies from `uv.lock`, finds `streamlit_app.py` and starts the web process with `streamlit run`, on the port in the `PORT` environment variable. The browser and the app talk over a WebSocket at `/_stcore/stream`, which Sevalla's proxy passes through with no configuration.

## At a glance

| | |
| - | - |
| Source | A Git repository |
| Build | [Ingot](https://docs.sevalla.com/applications/builds/ingot.md) (Python 3.14, uv), or the [Dockerfile](#deploy-with-a-dockerfile) |
| Start command | `streamlit run streamlit_app.py --server.port "$PORT" --server.address 0.0.0.0 --server.headless true`, which Ingot detects from `streamlit_app.py` |
| Port | The `PORT` environment variable, `8080` by default |
| Processes | Web process, 1 instance |
| Services | None |
| Disk | None |
| Health check | `GET /_stcore/health` returns `200` with `ok` (readiness probe) |
| Size | **H1** |

### Environment variables

| Variable | Required | Available during | Secret | Value |
| - | - | - | - | - |
| `PORT` | No | Runtime | No | Set by Sevalla to the web process port |
| `STREAMLIT_BROWSER_GATHER_USAGE_STATS` | No | Runtime | No | `false` stops the browser from sending Streamlit's usage statistics |

## 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).
* Python 3.14 and [uv](https://docs.astral.sh/uv/) 0.13 or later on your computer.

## Create the project

Create the project with uv and add Streamlit:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
uv init --app --no-package --python 3.14 dashboard
cd dashboard
uv add streamlit
rm main.py
```

`uv init` writes `pyproject.toml`, a `.python-version` with `3.14`, a `.gitignore` that keeps out `.venv`, and a Git repository. `--no-package` keeps the project a set of scripts rather than a package to install, which is what a Streamlit app is. `uv add` installs Streamlit 1.66 into `.venv` and pins every package in `uv.lock`. The app replaces the `main.py` that `uv init` writes:

```python streamlit_app.py theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
import pandas as pd
import streamlit as st

st.title("Storefront sales")

uploaded = st.file_uploader("Upload a sales CSV with date and revenue columns", type="csv")
if uploaded is None:
    st.info("Waiting for a CSV file.")
else:
    sales = pd.read_csv(uploaded, parse_dates=["date"])
    st.metric("Total revenue", f"${sales['revenue'].sum():,.2f}")
    st.line_chart(sales, x="date", y="revenue")
```

Run it with `uv run streamlit run streamlit_app.py`. The first time, Streamlit asks for an email address in the terminal: leave it blank and press `Enter`. Then open `http://localhost:8501`. To try the uploader, save this sample anywhere on your computer and upload it:

```text sales.csv theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
date,revenue
2026-10-01,120.50
2026-10-02,98.00
2026-10-03,143.25
```

The app shows **Total revenue** `$361.75` and a line chart. Press `Ctrl+C` to stop it.

## Prepare the project for Sevalla

The project already has what Ingot needs:

| File | Why Sevalla needs it |
| - | - |
| `.python-version` | Ingot builds on the Python line it names, 3.14. Without it, Ingot uses its default line, which a new Ingot release can move. See [Pin the Python version](https://docs.sevalla.com/guides/python/overview.md#pin-the-python-version). |
| `uv.lock` | Ingot installs the exact versions in it with `uv sync --locked`, into a virtual environment at `/app/.venv` that's on the `PATH`. Commit it. |
| `streamlit_app.py` | Streamlit's own name for an app's entry point. Ingot runs it when Streamlit is a dependency; without it, Ingot runs `main.py`, `app.py` or `server.py`, the first one it finds. |

Ingot starts the web process with:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
streamlit run streamlit_app.py --server.port "$PORT" --server.address 0.0.0.0 --server.headless true
```

* `--server.port "$PORT"` and `--server.address 0.0.0.0` make Streamlit listen on all interfaces, on the port that Sevalla sets in `PORT` (`8080` by default).
* `--server.headless true` stops Streamlit from asking for an email address or trying to open a browser when it starts.
* Keep Streamlit's protection settings at their defaults. Sevalla's proxy passes the `Host` header and cookies through, so the WebSocket's same-origin check (`server.enableCORS`) and the uploads' XSRF check (`server.enableXsrfProtection`) both pass. Don't turn them off.

To start Streamlit with other options, set the web process's start command; see [Set the start command](https://docs.sevalla.com/applications/builds/ingot.md#set-the-start-command).

Commit everything and push it to your Git provider. `uv init` already created the repository:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
git add .
git commit -m "Prepare for Sevalla"
git branch -M main
git remote add origin https://github.com/acme/dashboard.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): Python 3.14 from `.python-version`, uv from `uv.lock`, and Streamlit with the start command for `streamlit_app.py`.

   ```text Build log theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   ╭─ ■ Streamlit · Python 3.14 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   │ Python           3.14                                                                                                  .python-version "3.14" │
   │ Package manager  uv                                                                                                    uv.lock                │
   │ uv               0.13                                                                                                  default                │
   │ Framework        Streamlit                                                                                             streamlit_app.py       │
   │ Start            streamlit run streamlit_app.py --server.port "$PORT" --server.address 0.0.0.0 --server.headless true  Streamlit              │
   ├─ Image ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
   │ Build            python:3.14-slim-trixie                                                                                                      │
   │ Runtime          python:3.14-slim-trixie                                                                                                      │
   │ 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 `dashboard-x7k2m.sevalla.app`. The page shows **Storefront sales** and the uploader, which means the browser's WebSocket reached the app. Upload `sales.csv`: the app shows **Total revenue** `$361.75` and the chart.

4. Streamlit's health endpoint answers too:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s https://dashboard-x7k2m.sevalla.app/_stcore/health
   ```

   The response is `ok`.

5. To check the deployment through the API, use `APP_ID` and `DEPLOYMENT_ID` from [Deploy with the API](#deploy-with-the-api). [Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) returns `success`, and [Get application deployment logs](https://api-docs.sevalla.com/v3/applications/get-application-deployment-logs) (`getApplicationDeploymentLogsV3`) returns the build log, build plan included:

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

   The first command prints `success`, and the second `🚀 Web has been deployed successfully.`, in green: the log's messages carry terminal color codes. Without `search`, the second command returns the whole build log, build plan included. The CLI has `sevalla apps deployments get` and `sevalla apps deployments logs`, and the MCP server `get_deployment` and `get_deployment_logs`.

![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)

If the deployment fails, read its build log, then see [Troubleshoot Ingot builds](https://docs.sevalla.com/applications/builds/ingot.md#troubleshoot-ingot-builds). If it deploys but the page stays blank, read the application's [runtime logs](https://docs.sevalla.com/applications/logs.md).

## Keep the web process at one instance

Streamlit keeps each session, and the files uploaded to it, in the memory of the instance that holds the browser's WebSocket. Sevalla doesn't keep a visitor's requests on one instance, so with 2 or more instances an upload can reach an instance that doesn't know the session and fails with `400` and `Invalid session_id. This is likely caused by a multi-replica deployment without sticky sessions`. Keep the web process at 1 instance, and choose a larger size when the app needs more CPU or memory. Each open session holds its data in memory: watch the web process's memory on the **Analytics** tab. See [Scaling](https://docs.sevalla.com/applications/scaling.md).

## Add a health check

Without a readiness probe, a new instance receives traffic as soon as its container starts, before Streamlit listens. Point the probe at `/_stcore/health`, which returns `ok` once the server runs:

**1. Open the web process**

On the application's **Processes** tab, click **⋯** on the **Web** process, then click **Update process**.

**2. Turn on the readiness probe**

Under **Health checks**, turn on **Readiness probe** and set the **Path** to `/_stcore/health`. Keep the **Port** at the web process's port, `8080` unless you changed it.

**3. Save and deploy**

Click **Update process**, then click **Deploy** in the undeployed changes bar at the bottom of the page.

The deployment reaches **Deployed** once the new instance passes the probe. See [Health checks](https://docs.sevalla.com/applications/processes.md#health-checks).

## 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 python:3.14-slim-trixie AS build
COPY --from=ghcr.io/astral-sh/uv:0.13 /uv /usr/local/bin/
ENV UV_COMPILE_BYTECODE=1 \
    UV_PYTHON_DOWNLOADS=0
WORKDIR /app
COPY pyproject.toml uv.lock .python-version ./
RUN uv sync --locked --no-dev

FROM python:3.14-slim-trixie
RUN useradd --uid 1000 --create-home app
ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONUNBUFFERED=1 \
    PORT=8080
WORKDIR /app
COPY --from=build /app/.venv .venv
COPY . .
USER 1000:1000
EXPOSE 8080
CMD ["sh", "-c", "exec streamlit run streamlit_app.py --server.port \"$PORT\" --server.address 0.0.0.0 --server.headless true"]
```

| Line or choice | Why |
| - | - |
| `python:3.14-slim-trixie` | The Python line that `.python-version` pins, on Debian 13, as Ingot builds it. A line tag picks up each patch release and security fix when you deploy again; a digest would stay on one release. |
| Two stages | The first stage installs the packages with uv. The final image gets only the virtual environment and the code, not uv. |
| `ghcr.io/astral-sh/uv:0.13` | uv's official image, which Ingot uses too. `COPY --from` takes its `uv` binary without a download script. |
| `uv sync --locked --no-dev` | Installs exactly what `uv.lock` pins, and fails if the lock file doesn't match `pyproject.toml`. `UV_COMPILE_BYTECODE=1` compiles the packages at build time, so instances start faster. `UV_PYTHON_DOWNLOADS=0` makes uv use the image's Python rather than download one. |
| Lock files before the code | Docker reuses the installed packages until `pyproject.toml`, `uv.lock` or `.python-version` change, so a code change rebuilds only the last layers. |
| `useradd` and `USER 1000:1000` | Runs Streamlit as an unprivileged user with a home directory, where Streamlit keeps its settings folder. The code and packages stay owned by root, so the app can't change them. |
| `sh -c "exec streamlit run ..."` | Streamlit doesn't read `PORT` itself, so a shell puts it on the command line. `exec` replaces the shell with Streamlit, which runs as PID 1, stops on `SIGTERM` and needs no init process. |

```text .dockerignore theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
.git
.venv
**/__pycache__
.env*
.streamlit/secrets.toml
Dockerfile
.dockerignore
```

Commit and push both files. Ingot doesn't use the `Dockerfile`, so you can keep it in a repository that builds with Ingot, but it adds the `.dockerignore` patterns to its own: its build plan shows an **Ignored** row with `7 patterns from .dockerignore`. 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 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)

The build log shows `📦 Building and pushing docker image using Dockerfile...` where Ingot's build plan was. The domain shows **Storefront sales**, uploading `sales.csv` shows `$361.75`, and the deployment's **Dockerfile** tab shows your Dockerfile. See [Dockerfile](https://docs.sevalla.com/applications/builds/dockerfile.md).

## 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 instance. After you add a package with `uv add`, commit `pyproject.toml` and `uv.lock` together. See [Deployments](https://docs.sevalla.com/applications/deployments.md).

## Use environment variables

Keep API keys and other secrets in [environment variables](https://docs.sevalla.com/applications/environment-variables.md) and read them with `os.environ`, for example `os.environ["SALES_API_TOKEN"]`. Streamlit's `st.secrets` reads `.streamlit/secrets.toml`, a file for your computer only: add it to `.gitignore` so it never reaches the repository.

Streamlit also reads its own settings from environment variables named `STREAMLIT_` followed by the section and option in upper case: `browser.gatherUsageStats` is `STREAMLIT_BROWSER_GATHER_USAGE_STATS`. For example, to stop the browser from sending Streamlit's usage statistics:

**1. Add the variable**

On the application's **Environment variables** tab, click **Add environment variables**. Add `STREAMLIT_BROWSER_GATHER_USAGE_STATS` with the value `false`, keep **Available during runtime** selected, and click **Add environment variables**.

**2. Deploy**

Click **Deploy** in the undeployed changes bar at the bottom of the page.

After the deployment, the web process's [runtime logs](https://docs.sevalla.com/applications/logs.md) no longer start with `Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.`

## Add a custom domain

To serve the app from your own domain, such as `dashboard.example.com`, add the domain on the application's **Domains** tab and create the DNS records that Sevalla shows. Streamlit accepts any host by default, so no code change is needed. 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).

## Deploy with the API

These calls make the same deployment as [Deploy to Sevalla](#deploy-to-sevalla), in an order that an agent or a script runs as is. Each step names the API operation, then the [CLI](https://docs.sevalla.com/developer-tools/cli.md) command and the [MCP](https://docs.sevalla.com/ai-agents/overview.md) tool that do the same. The CLI reads the key from `SEVALLA_API_TOKEN` and prints tables; with `--json` it prints the API's JSON, except that lists are bare arrays rather than the API's `data` field.

Before you start:

* Put an API key in `SEVALLA_API_KEY`. The key needs the **Company developer** role or **Company admin**; see [API keys](https://docs.sevalla.com/developer-tools/api-keys.md). The commands use `curl` and `jq`.
* Push the repository and connect its Git account to Sevalla, as in [Before you begin](#before-you-begin). No API call reports whether the account is connected: if it isn't, or Sevalla's GitHub App can't see the repository, creating the application fails with `400` and a message that starts with `Cannot access repository https://github.com/acme/dashboard: Repository not found or you don't have access.` and says where to grant access.
* Look up the location and the size. The first command picks the location by its region ID, here `eu-frankfurt-1` (Frankfurt, Germany); see [Locations](https://docs.sevalla.com/platform/locations.md) for the others. [List clusters](https://api-docs.sevalla.com/v3/resources/list-clusters) (`listClustersV3`) and [List process resource types](https://api-docs.sevalla.com/v3/resources/list-process-resource-types) (`listProcessResourceTypesV3`) return them; the CLI has `sevalla resources clusters` and `sevalla resources process-types`, and the MCP server `get_platform_options`.

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

If a variable ends up empty or `null`, run its `curl` command without `jq`: the response's `message` says what went wrong.

**1. Create the application**

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

```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 @- <<EOF | jq -r .id
{
  "display_name": "dashboard",
  "cluster_id": "$CLUSTER_ID",
  "source": "privateGit",
  "git_type": "github",
  "repo_url": "https://github.com/acme/dashboard",
  "default_branch": "main"
}
EOF
)
```

CLI: `sevalla apps create --name dashboard --cluster "$CLUSTER_ID" --source privateGit --git-type github --repository https://github.com/acme/dashboard --branch main`. MCP: `create_application` with `deploy` set to `false`; by default it starts a deployment before the size and the probe are set.

**2. Set the size and the readiness probe**

The API creates the web process at **S1**, not the dashboard's **H1**, and without health checks. [List processes](https://api-docs.sevalla.com/v3/applications/list-processes) (`listProcessesV3`) finds the web process, and [Update process](https://api-docs.sevalla.com/v3/applications/update-process) (`updateProcessV3`) sets **H1** and the readiness probe from [Add a health check](#add-a-health-check). Send every probe field, `timeoutSeconds` included: a probe set through the API without it waits up to 120 seconds for an answer, not the dashboard's 1 second. The values are the dashboard's defaults:

```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 @- <<EOF | jq -c '{size: .resource_type_name, readiness: .readiness_probe.httpGet.path, timeout: .readiness_probe.timeoutSeconds}'
{
  "resource_type_id": "$H1_ID",
  "readiness_probe": {
    "httpGet": {"path": "/_stcore/health", "port": 8080, "scheme": "HTTP"},
    "initialDelaySeconds": 0,
    "periodSeconds": 10,
    "timeoutSeconds": 1,
    "successThreshold": 1,
    "failureThreshold": 3
  }
}
EOF
```

It prints `{"size":"h1","readiness":"/_stcore/health","timeout":1}`. The process keeps 1 instance, which [Keep the web process at one instance](#keep-the-web-process-at-one-instance) explains. CLI: `sevalla apps processes update "$WEB_ID" --app-id "$APP_ID" --resource-type "$H1_ID"` sets the size; the CLI can't set probes. MCP: `sevalla_api_update` with `updateProcessV3` and the same body.

**3. 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 '{}' | jq -r .id)
```

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

**4. Wait for the deployment**

[Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) until `status` is `success` or `failed`:

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

It prints `success` when the deployment finishes, or `failed`; after 30 minutes without either, it prints the status it last read. On `failed`, read the build log with [Get application deployment logs](https://api-docs.sevalla.com/v3/applications/get-application-deployment-logs) (`getApplicationDeploymentLogsV3`), as in [Verify the deployment](#verify-the-deployment). CLI: `sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID"`. MCP: `get_deployment`, then `get_deployment_logs`.

**5. Turn on auto-deploy**

[Update application](https://api-docs.sevalla.com/v3/applications/update-application) (`updateApplicationV3`):

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

It prints `true`. CLI: `sevalla apps update "$APP_ID" --data '{"auto_deploy": true}'`. MCP: `sevalla_api_update` with `updateApplicationV3` and the same body.

[List application domains](https://api-docs.sevalla.com/v3/applications/list-application-domains) (`listApplicationDomainsV3`) returns the domain to run the checks in [Verify the deployment](#verify-the-deployment) against:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
APP_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')
curl -s https://$APP_HOST/_stcore/health
```

The response is `ok`. To build from the [Dockerfile](#deploy-with-a-dockerfile) instead, send `{"build_type": "dockerfile", "dockerfile_path": "Dockerfile", "docker_context": "."}` to [Update application](https://api-docs.sevalla.com/v3/applications/update-application), then deploy and wait again. CLI: `sevalla apps update "$APP_ID" --build-type dockerfile --dockerfile-path Dockerfile`.


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