---
title: Docker Setup
subtitle: Get Skyvern running in 10 minutes with Docker Compose
slug: self-hosted/docker
---

This guide walks you through deploying Skyvern using Docker Compose. By the end, you'll have a working Skyvern instance with the API server, browser, database, and web UI running on your machine.

## Prerequisites

- Docker and Docker Compose v2 installed ([Get Docker](https://docs.docker.com/get-docker/)). All commands use `docker compose` (with a space). If you have an older installation, replace with `docker-compose`.
- 4GB+ RAM available
- An LLM API key (OpenAI, Anthropic, Azure, Gemini, or Bedrock)

## Quick start

### 1. Clone the repository

```bash
git clone https://github.com/Skyvern-AI/skyvern.git
cd skyvern
```

### 2. Configure environment variables

Copy the example environment file and configure your LLM provider:

```bash
cp .env.example .env
```

Open `.env` and set your LLM provider. Here's an example for OpenAI:

```bash .env
ENABLE_OPENAI=true
OPENAI_API_KEY=sk-your-api-key-here
LLM_KEY=OPENAI_GPT4O
```

For other providers, see [LLM Configuration](/self-hosted/llm-configuration).

### 3. Configure the frontend

Copy the frontend environment file:

```bash
cp skyvern-frontend/.env.example skyvern-frontend/.env
```

The default values work for local development. You'll update `VITE_SKYVERN_API_KEY` after the first startup.

### 4. Start the services

```bash
docker compose up -d
```

This pulls the images and starts three services:
- **postgres**: Database on port 5432 (internal only)
- **skyvern**: API server on port 8000
- **skyvern-ui**: Web interface on port 8080

First startup takes 1-2 minutes as it runs database migrations and creates your organization.

### 5. Get your API key

Wait for all services to be healthy before continuing:

```bash
docker compose ps
```

All three services should show `healthy` in the STATUS column. The `skyvern` container runs database migrations and generates credentials on first startup. This takes 1-2 minutes.

Once healthy, retrieve your API key:

```bash
cat .streamlit/secrets.toml
```

This file is auto-generated by Skyvern on first startup. The `.streamlit` path is a legacy artifact. The credentials inside are standard Skyvern API keys.

You'll see output like:

```toml
[skyvern]
configs = [
    {env = "local", host = "http://skyvern:8000/api/v1", orgs = [{name="Skyvern", cred="eyJhbGciOiJIUzI1..."}]}
]
```

The `host` value uses the Docker-internal hostname `skyvern`. From your machine, use `http://localhost:8000` instead. You only need the `cred` value. This is your API key.

### 6. Update frontend configuration

Add your API key to the frontend environment:

```bash skyvern-frontend/.env
VITE_SKYVERN_API_KEY=eyJhbGciOiJIUzI1...
```

Restart the UI to pick up the change:

```bash
docker compose restart skyvern-ui
```

### 7. Verify the installation

Open [http://localhost:8080](http://localhost:8080) in your browser. You should see the Skyvern dashboard.

Test the API by listing workflows (should return an empty array on fresh install):

```bash
curl -s http://localhost:8000/v1/workflows \
  -H "x-api-key: YOUR_API_KEY_HERE"
```

<Note>
The API accepts requests on both `/v1/` and `/api/v1/`. The frontend uses `/api/v1` for backward compatibility. New integrations should use `/v1/`.
</Note>

---

## Understanding the services

The Docker Compose file defines three services that work together:

```mermaid
flowchart LR
    UI[skyvern-ui<br/>:8080] --> API[skyvern<br/>:8000]
    API --> DB[(postgres<br/>:5432)]
    API --> Browser[Browser<br/>internal]
    API --> LLM[Your LLM<br/>Provider]
```

| Service | Image | Ports | Purpose |
|---------|-------|-------|---------|
| `postgres` | postgres:14-alpine | 5432 (internal) | Stores tasks, workflows, credentials, and run history |
| `skyvern` | public.ecr.aws/skyvern/skyvern | 8000, 9222 | API server + embedded browser |
| `skyvern-ui` | public.ecr.aws/skyvern/skyvern-ui | 8080, 9090 | Web dashboard and artifact server |

The `skyvern` container includes Playwright with Chromium. The browser runs inside the same container as the API server. No separate browser service is needed.

### Data volumes

Docker Compose mounts several directories for persistent storage:

| Local path | Container path | Contents |
|------------|----------------|----------|
| `./postgres-data` | `/var/lib/postgresql/data` | Database files |
| `./artifacts` | `/data/artifacts` | Extracted data, screenshots |
| `./videos` | `/data/videos` | Browser session recordings |
| `./har` | `/data/har` | HTTP Archive files for debugging |
| `./log` | `/data/log` | Application logs |
| `./.streamlit` | `/app/.streamlit` | Generated API credentials |

---

## Environment variables reference

The `.env` file controls the Skyvern server. Here are the most important variables grouped by purpose.

### LLM Configuration

Configure at least one LLM provider. See [LLM Configuration](/self-hosted/llm-configuration) for all providers.

```bash
# OpenAI
ENABLE_OPENAI=true
OPENAI_API_KEY=sk-...
LLM_KEY=OPENAI_GPT4O

# Or Anthropic
ENABLE_ANTHROPIC=true
ANTHROPIC_API_KEY=sk-ant-...
LLM_KEY=ANTHROPIC_CLAUDE3.5_SONNET
```

### Browser settings

```bash
# Browser mode: chromium-headful (visible) or chromium-headless (no display)
BROWSER_TYPE=chromium-headful

# Timeout for individual browser actions (milliseconds)
BROWSER_ACTION_TIMEOUT_MS=5000

# Where to save recordings
VIDEO_PATH=./videos
```

### Task execution

```bash
# Maximum steps before a task times out
MAX_STEPS_PER_RUN=50

# Server port
PORT=8000

# Log verbosity: DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=INFO
```

### Database

The database connection is set in `docker-compose.yml`, not `.env`:

```yaml
environment:
  - DATABASE_STRING=postgresql+psycopg://skyvern:skyvern@postgres:5432/skyvern
```

To use an external database, update this connection string and remove the `postgres` service.

---

## Common operations

### View logs

```bash
# All services
docker compose logs -f

# Specific service
docker compose logs -f skyvern
```

### Restart after configuration changes

```bash
docker compose restart skyvern
```

### Stop all services

```bash
docker compose down
```

### Update to latest version

```bash
docker compose pull
docker compose up -d
```

### Reset everything (including database)

<Warning>
This deletes all data including task history, credentials, and recordings.
</Warning>

```bash
docker compose down -v
rm -rf postgres-data artifacts videos har log .streamlit
docker compose up -d
```

---

## Exposing to the network

By default, Skyvern only accepts connections from localhost. To expose it on your network:

### Option 1: Bind to all interfaces

Edit `docker-compose.yml` to change the port bindings:

```yaml
services:
  skyvern:
    ports:
      - "0.0.0.0:8000:8000"  # Accept connections from any IP
```

### Option 2: Use a reverse proxy

For production deployments, put Skyvern behind nginx or Traefik:

```
your-domain.com → nginx → localhost:8080 (UI)
api.your-domain.com → nginx → localhost:8000 (API)
```

Update the frontend environment to use your domain:

```bash skyvern-frontend/.env
VITE_API_BASE_URL=https://api.your-domain.com/api/v1
VITE_WSS_BASE_URL=wss://api.your-domain.com/api/v1
VITE_ARTIFACT_API_BASE_URL=https://artifacts.your-domain.com
```

<Warning>
If exposing Skyvern to the internet, add authentication at the reverse proxy layer or use a VPN.
</Warning>

---

## Troubleshooting

<Accordion title="Container exits immediately">
Check the logs for the failing service:

```bash
docker compose logs skyvern
```

Common causes:
- Missing or invalid LLM API key: look for LLM-related errors in logs
- Database connection failed: check if `postgres` service is healthy with `docker compose ps`
</Accordion>

<Accordion title='API returns "Invalid credentials" (403)'>
The API key is missing, malformed, or doesn't match the organization. Verify:

1. The `x-api-key` header is included in your request
2. The key matches exactly what's in `.streamlit/secrets.toml`
3. No extra whitespace or newlines in the key

```bash
# Correct format
curl -H "x-api-key: eyJhbGciOiJIUzI1..." http://localhost:8000/v1/workflows
```
</Accordion>

<Accordion title='API returns "Could not validate credentials" (403)'>
The API key format is invalid (JWT decode failed). This usually means:
- The key was truncated or corrupted during copy-paste
- You're using an API key from a different Skyvern installation

Regenerate credentials by resetting the installation:

```bash
rm -rf .streamlit
docker compose restart skyvern
cat .streamlit/secrets.toml  # Get new key
```
</Accordion>

<Accordion title="UI shows blank page or connection errors">
Check that `skyvern-frontend/.env` has the correct values:

```bash
VITE_API_BASE_URL=http://localhost:8000/api/v1
VITE_WSS_BASE_URL=ws://localhost:8000/api/v1
VITE_SKYVERN_API_KEY=<your-key-from-secrets.toml>
```

After updating, restart the UI:

```bash
docker compose restart skyvern-ui
```
</Accordion>

<Accordion title='Task fails with "Unknown browser type"'>
The `BROWSER_TYPE` environment variable has an invalid value. Valid options:
- `chromium-headful`: Browser with visible window (default)
- `chromium-headless`: No visible window
- `cdp-connect`: Connect to external Chrome
</Accordion>

<Accordion title='Task fails with "Context window exceeded"'>
The page content is too large for the LLM. Try:
- Simplifying your prompt
- Starting from a more specific URL
- Using a model with a larger context window
</Accordion>

---

## Next steps

<CardGroup cols={2}>
  <Card title="LLM Configuration" icon="microchip" href="/self-hosted/llm-configuration">
    Configure OpenAI, Anthropic, Azure, Ollama, and other providers
  </Card>
  <Card title="Browser Configuration" icon="window" href="/self-hosted/browser">
    Customize browser settings, locales, and connect to external Chrome
  </Card>
  <Card title="Storage Configuration" icon="hard-drive" href="/self-hosted/storage">
    Store artifacts in S3 or Azure Blob instead of local disk
  </Card>
  <Card title="Proxy Setup" icon="shield" href="/self-hosted/proxy">
    Configure proxies to avoid bot detection
  </Card>
</CardGroup>
