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

# Self-host

> Run the Skyvern server on your own machine or cluster with pip, Docker Compose, or Kubernetes, using your own LLM keys or a local model.

Self-hosted Skyvern runs the API server, the browser, and the database on your infrastructure, with your own LLM keys. Every setting is an environment variable; the full list is in [configuration](https://skyvern.com/docs/reference/configuration.md).

| You get | You run yourself |
| - | - |
| Data, recordings, and credentials stay on your infrastructure, except what goes to the LLM provider you configure | Servers, scaling, backups, upgrades |
| Any LLM provider, including local models | LLM bills, paid to your provider |
| Full control of the browser and extensions | Proxies. Skyvern Cloud's proxy network and CAPTCHA solving are not included. |

## Pick a path

| Path | Best for | Includes the web UI |
| - | - | - |
| [pip](#pip-local-server) | Calling a local server from code or MCP | No |
| [Docker Compose](#docker-compose) | A full install on one machine | Yes |
| [Kubernetes](#kubernetes) | Teams already running a cluster | Yes |

### pip: local server

Requires Python 3.11 to 3.14.

```bash theme={null}
pip install "skyvern[server]"
skyvern quickstart --install-type server
```

`skyvern quickstart` asks for your LLM provider and key, installs Chromium, sets up the database, writes a local API key to `.env`, and can configure MCP for your coding agent. It then offers to start the server.

Start it later with:

```bash theme={null}
skyvern run server
```

The API listens on `http://localhost:8000`.

Database choice:

* If PostgreSQL is already running locally, quickstart uses it.
* Otherwise it offers to start a PostgreSQL container, which needs Docker.
* `--no-postgres` skips that and uses SQLite at `~/.skyvern/data.db`.
* `--database-string=postgresql+psycopg://user:pass@host:5432/dbname` uses a database you already have.

> [!NOTE]
>
> `pip install skyvern` without the `[server]` extra is the client SDK only. A pip install runs the API server without the web UI. For the UI, use Docker Compose or a source checkout.

### Docker Compose

Starts PostgreSQL, the API server with its browser, and the web UI.

**1. Get the code and env files**

```bash theme={null}
git clone https://github.com/Skyvern-AI/skyvern.git
cd skyvern
cp .env.example .env
cp skyvern-frontend/.env.example skyvern-frontend/.env
```

**2. Set your LLM key**

In `.env`, enable one provider (see [Set up an LLM provider](#set-up-an-llm-provider)).

**3. Clear the UI's placeholder key**

In `skyvern-frontend/.env`, set `VITE_SKYVERN_API_KEY=` (empty). The UI then reads the key the server generates. If you leave `YOUR_API_KEY` there, the UI sends it as is and every request fails.

**4. Start**

```bash theme={null}
docker compose up -d
docker compose ps
```

The first start runs migrations and creates your organization. Wait until `postgres` and `skyvern` show `healthy`.

**5. Open the app**

Go to [http://localhost:8080](http://localhost:8080). The API is on port 8000. Your API key is the `cred` value in `.skyvern/credentials.toml`.

| Service | Ports | Role |
| - | - | - |
| `postgres` | internal | Database, stored in `./postgres-data` |
| `skyvern` | 8000, 6080 | API server and browser (6080 is VNC streaming) |
| `skyvern-ui` | 8080, 9090 | Web app and artifact server |

Recordings, artifacts, HAR files, logs, and downloads are written to folders next to `docker-compose.yml`.

> [!WARNING]
>
> Leave `DATABASE_STRING` in `docker-compose.yml`. The value in `.env.example` points at `localhost`, which inside the container is the container itself. If an older `.env` has `DATABASE_STRING=...@localhost...`, delete that line.

If you have the CLI installed, `skyvern quickstart --docker-compose` from the repo folder creates the env files, asks for your LLM key, and runs `docker compose up -d` for you.

To serve it on a network, put a reverse proxy in front with authentication, and set `VITE_API_BASE_URL`, `VITE_WSS_BASE_URL`, and `VITE_ARTIFACT_API_BASE_URL` in `skyvern-frontend/.env` to the public URLs. Don't expose Skyvern to the internet without authentication.

### Kubernetes

The repository has plain manifests in [`kubernetes-deployment/`](https://github.com/Skyvern-AI/skyvern/tree/main/kubernetes-deployment): a backend, a frontend, PostgreSQL, and a Traefik ingress. Fill in `backend/backend-secrets.yaml` (the same variables as `.env`) and `frontend/frontend-secrets.yaml`, then run `./k8s-deploy.sh`. Its README and [configuration](https://skyvern.com/docs/reference/configuration.md#kubernetes) cover the API key, volumes, TLS, and scaling. The manifests have no authentication, so keep the cluster off the public internet.

## Set up an LLM provider

Enable one provider and point `LLM_KEY` at a model. Quickstart writes these for you.

```bash .env theme={null}
ENABLE_OPENAI=true
OPENAI_API_KEY=sk-...
LLM_KEY=OPENAI_GPT5_5
```

```bash .env theme={null}
ENABLE_ANTHROPIC=true
ANTHROPIC_API_KEY=sk-ant-...
LLM_KEY=ANTHROPIC_CLAUDE4.7_OPUS
```

`LLM_KEY` defaults to `OPENAI_GPT5_5`. `SECONDARY_LLM_KEY` sets a cheaper model for lighter calls. Azure OpenAI, Gemini, Vertex AI, Bedrock, OpenRouter, Groq, and others work the same way. The provider flags and every valid `LLM_KEY` are in [configuration](https://skyvern.com/docs/reference/configuration.md). Restart the server after any change.

### Use a local model

**Ollama:**

```bash .env theme={null}
ENABLE_OLLAMA=true
LLM_KEY=OLLAMA
OLLAMA_SERVER_URL=http://localhost:11434
OLLAMA_MODEL=qwen2.5vl:7b
OLLAMA_SUPPORTS_VISION=true
```

Set `OLLAMA_SUPPORTS_VISION=true` only for a model that reads images. Skyvern works from screenshots, so a vision model gives much better results.

**Any OpenAI-compatible endpoint** (LiteLLM, vLLM, LM Studio):

```bash .env theme={null}
ENABLE_OPENAI_COMPATIBLE=true
LLM_KEY=OPENAI_COMPATIBLE
OPENAI_COMPATIBLE_MODEL_NAME=your-model
OPENAI_COMPATIBLE_API_KEY=sk-anything
OPENAI_COMPATIBLE_API_BASE=http://localhost:4000/v1
# Set true only if the selected model supports images
OPENAI_COMPATIBLE_SUPPORTS_VISION=true
```

> [!WARNING]
>
> If Skyvern runs in Docker, `localhost` is the container. Use `http://host.docker.internal:11434` (or your port) to reach a model on the host. Without `LLM_KEY`, Skyvern keeps calling the default OpenAI model.

## Browser options

`BROWSER_TYPE` sets how Skyvern runs Chromium.

| Value | Behavior |
| - | - |
| `chromium-headful` | Visible window (default). In Docker it draws on a virtual display. |
| `chromium-headless` | No window. Some sites block headless browsers. |
| `cdp-connect` | Drive a Chrome you already run. Set `BROWSER_REMOTE_DEBUGGING_URL` (default `http://127.0.0.1:9222`), or set `CHROME_EXECUTABLE_PATH` and Skyvern starts your installed Chrome with a copy of your profile. |

`skyvern init browser` walks you through `cdp-connect`. From Docker, reach Chrome on the host at `http://host.docker.internal:9222`. Other settings, including viewport, locale, timezone, timeouts and CDP details, are in [configuration](https://skyvern.com/docs/reference/configuration.md#browser-settings). For reusable sessions and profiles, see [Browsers](https://skyvern.com/docs/browsers.md).

Each concurrent run needs its own browser, and browsers use a lot of memory. Size memory for the number of runs you expect at once, and add servers to scale.

## Storage

Artifacts (recordings, screenshots, HAR files, logs) go to local disk by default. For several servers, or for durability, set `SKYVERN_STORAGE_TYPE`.

| `SKYVERN_STORAGE_TYPE` | Where | Main settings |
| - | - | - |
| `local` (default) | Server disk | `ARTIFACT_STORAGE_PATH`, `VIDEO_PATH`, `HAR_PATH`, `LOG_PATH` |
| `s3` | AWS S3 | `AWS_REGION`, `AWS_S3_BUCKET_ARTIFACTS`, `AWS_S3_BUCKET_SCREENSHOTS`, `AWS_S3_BUCKET_BROWSER_SESSIONS`, `AWS_S3_BUCKET_UPLOADS`. Uses the standard AWS credential chain. |
| `azureblob` | Azure Blob Storage | `AZURE_STORAGE_ACCOUNT_NAME`, `AZURE_STORAGE_ACCOUNT_KEY` (required; managed identity is not supported), and one `AZURE_STORAGE_CONTAINER_*` per bucket type |
| `gcs` | Google Cloud Storage | `GCS_PROJECT_ID`, one `GCS_BUCKET_*` per bucket type. Uses Application Default Credentials. |

Create the S3 or GCS buckets before you start. Azure containers are created on first use. Links in run responses are signed. Screenshot and recording links expire after 1 hour; on S3, other links expire after `PRESIGNED_URL_EXPIRATION` seconds (default 86400, 24 hours). Switching storage doesn't move existing artifacts. Bucket setup, IAM policy and migration are in [configuration](https://skyvern.com/docs/reference/configuration.md#storage).

## URL validation

Skyvern doesn't block requests to internal addresses by default, so it reaches an intranet portal or a service on your network without extra setup. Set `ENABLE_URL_VALIDATION=true` to refuse loopback, private-network and cloud-metadata addresses instead. What it blocks, and how to allow individual hosts, is in [configuration](https://skyvern.com/docs/reference/configuration.md#url-validation).

> [!WARNING]
>
> Whoever can start a task chooses the URLs Skyvern opens and fetches. If you share a server with people you don't trust, turn URL validation on.

## Google integrations

The Google Sheets, Gmail, and Google Drive integrations are off on a self-hosted server until you add a Google OAuth client.

**1. Turn on encryption**

Set `ENABLE_ENCRYPTION=true` and `ENCRYPTOR_AES_SECRET_KEY` (generate it with `openssl rand -base64 32`), then restart. Don't change the key later: stored credentials become unreadable.

**2. Create the OAuth client**

In Google Cloud, enable the Google Sheets, Google Drive, and Gmail APIs. Configure the **OAuth consent screen**; for an External app, add each user as a test user (or publish the app) before they can connect. Then create an OAuth client ID of type **Web application**. Add `https://<your-ui-host>/integrations/google/callback` as an authorized redirect URI. For local use, that's `http://localhost:8080/integrations/google/callback`.

**3. Add it to Skyvern**

In the app, open the **Google OAuth** form on **Integrations** or **Settings**. Fill in **Client ID** and **Client Secret**, set **Redirect Hosts** (host name only) and **App Origins** (for example `https://skyvern.example.com`), and click **Save Configuration**. Or set `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_HOSTS`, and `GOOGLE_OAUTH_APP_ORIGINS` on the server. The form wins over the variables.

Skyvern asks Google for the scopes each integration needs. If your consent screen restricts scopes, add these so users can grant them:

| Integration | Scopes |
| - | - |
| Google Sheets | `https://www.googleapis.com/auth/spreadsheets`, `https://www.googleapis.com/auth/drive.file`, `https://www.googleapis.com/auth/drive.metadata.readonly` |
| Gmail, Read inbox | `https://www.googleapis.com/auth/gmail.readonly` |
| Gmail, Send email | `https://www.googleapis.com/auth/gmail.send`, `openid`, `email` |
| Google Drive | `https://www.googleapis.com/auth/drive` |

`gmail.send` is a sensitive scope in Google Cloud. On the Gmail card, **Read inbox** is ticked by default and **Send email** is not; Google is asked only for what is ticked.

Then click **Connect** on the integration card. If it fails, the redirect URI registered with Google must match **Redirect Hosts**, and **App Origins** must hold the exact origin the UI runs on.

## Connect your code

Point the SDK at your server with the local API key.

```python Python theme={null}
from skyvern import Skyvern

client = Skyvern(base_url="http://localhost:8000", api_key="YOUR_LOCAL_API_KEY")
```

```typescript TypeScript theme={null}
import { Skyvern } from "@skyvern/client";

const client = new Skyvern({ baseUrl: "http://localhost:8000", apiKey: "YOUR_LOCAL_API_KEY" });
```

The API answers on both `/v1/` and `/api/v1/`.

## Upgrade

**Docker Compose:** the server runs database migrations on start.

```bash theme={null}
git pull
docker compose pull
docker compose up -d
```

**pip:**

```bash theme={null}
pip install --upgrade "skyvern[server]"
skyvern init
skyvern run server
```

`skyvern init` runs the database migrations. Back up the database before upgrading. Release notes are in the [changelog](https://skyvern.com/docs/changelog.md).

## Next

* [Configuration](https://skyvern.com/docs/reference/configuration.md): every environment variable and model key
* [Going to production](https://skyvern.com/docs/developers/production.md): webhooks, errors, cost limits
* [Troubleshooting](https://skyvern.com/docs/developers/troubleshooting.md#self-hosted)


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