Skip to main content
A Streamlit app runs on Sevalla as an application: Ingot, 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

Environment variables

Before you begin

  • A Sevalla company with a payment method, and a GitHub, GitLab or Bitbucket account for the repository. See Git providers.
  • Python 3.14 and uv 0.13 or later on your computer.

Create the project

Create the project with uv and add Streamlit:
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:
streamlit_app.py
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:
sales.csv
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: Ingot starts the web process with:
  • --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. Commit everything and push it to your Git provider. uv init already created the repository:

Deploy to Sevalla

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 deployment

  1. On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: Python 3.14 from .python-version, uv from uv.lock, and Streamlit with the start command for streamlit_app.py.
    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 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:
    The response is ok.
  5. To check the deployment through the API, use APP_ID and DEPLOYMENT_ID from Deploy with the API. Get deployment (getDeploymentV3) returns success, and Get application deployment logs (getApplicationDeploymentLogsV3) returns the build log, build plan included:
    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 applicationThe application header with its name, Running status, repository, branch and the domain that opens the application
If the deployment fails, read its build log, then see Troubleshoot Ingot builds. If it deploys but the page stays blank, read the application’s runtime logs.

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.

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.

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
.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 credentialThe Update build strategy sheet with Dockerfile selected, the Dockerfile path, the Docker context and the Docker registry credential
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.

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.

Use environment variables

Keep API keys and other secrets in environment variables 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 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 the web process first. See Custom domains.

Deploy with the API

These calls make the same deployment as Deploy to Sevalla, in an order that an agent or a script runs as is. Each step names the API operation, then the CLI command and the MCP 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. The commands use curl and jq.
  • Push the repository and connect its Git account to Sevalla, as in 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 for the others. List clusters (listClustersV3) and 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.
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 (createApplicationV3). It creates the web process and doesn’t deploy:
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 (listProcessesV3) finds the web process, and Update process (updateProcessV3) sets H1 and the readiness probe from 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:
It prints {"size":"h1","readiness":"/_stcore/health","timeout":1}. The process keeps 1 instance, which 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 (triggerDeploymentV3) deploys the latest commit on the branch:
CLI: sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.
4

Wait for the deployment

Get deployment (getDeploymentV3) until status is success or failed:
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 (getApplicationDeploymentLogsV3), as in 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 (updateApplicationV3):
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 (listApplicationDomainsV3) returns the domain to run the checks in Verify the deployment against:
The response is ok. To build from the Dockerfile instead, send {"build_type": "dockerfile", "dockerfile_path": "Dockerfile", "docker_context": "."} to Update application, then deploy and wait again. CLI: sevalla apps update "$APP_ID" --build-type dockerfile --dockerfile-path Dockerfile.