Skip to main content
Tesslate OpenSail A complete walk-through for getting OpenSail running locally with Docker Compose on macOS, Windows (WSL 2), or Linux. By the end you will have a logged-in user, a working project, and a live preview at http://localhost.
If you just need commands, jump to the Quick Start. For the native desktop app, see the Desktop install guide.

1. Prerequisites

Node.js and Python are NOT required on the host. Everything runs inside containers.

OS support

macOS 13+ on Intel or Apple Silicon. Docker Desktop with the Virtualization.framework backend is recommended. Apple Silicon pulls arm64 images transparently.

2. Clone and configure

1

Clone the repo

2

Set required env vars

Only two values are genuinely required for first boot; everything else has sensible defaults.
3

Review optional env groups

Open .env.example for the full list. The groups below are the ones you are most likely to touch.
Docker Compose does not interpolate variables inside other variables in .env, so ALLOWED_HOSTS=${APP_DOMAIN} keeps the literal ${APP_DOMAIN} string. For dev it still works because APP_DOMAIN=localhost is also the default. In production, set ALLOWED_HOSTS to an explicit value.

3. First boot

1

Bring up the stack

This builds the orchestrator and app images from source, pulls Postgres, Redis, and Traefik, and brings up seven services. The first build takes 3 to 6 minutes depending on your machine. Later boots are near-instant thanks to the build cache.
2

Build the devserver image

The devserver service in docker-compose.yml exists purely to produce the tesslate-devserver:latest image that every user project container derives from. Its entrypoint: true keeps it from running, but the build still happens when Compose sees it. If docker compose up skipped the build (for example after a clean image prune), run it explicitly:
Without this image, creating or starting any user project fails with pull access denied for tesslate-devserver.

Services

Verify health

Healthy output looks like this (timings vary):
The orchestrator can show health: starting for up to 30 seconds while Alembic migrations run. Tail it until Uvicorn reports ready:

4. Seed the database

On first backend startup the orchestrator automatically runs run_all_seeds() from orchestrator/app/seeds/__init__.py. That covers themes, bases, agents, skills, MCP servers, and deployment targets on a clean database. Confirm:
If the count is zero (older database or partial seed), re-run the scripts manually. Each is idempotent.
What you get: themes (default-dark, default-light, midnight, ocean, forest, rose, sunset), official and open-source agents (Librarian, ReAct, Stream Builder), marketplace bases (Next.js, Vite+React+FastAPI, Vite+React+Go, Expo), open-source and Tesslate skills, MCP server catalog entries, and deployment targets (Vercel, Netlify, Cloudflare, Railway).

5. Access URLs

6. Create your first user

  1. Visit http://localhost.
  2. Click “Sign up”, enter email and password.
  3. You are logged in. Billing starts on the FREE tier.

7. Create your first project

1

New project

From the dashboard, click “New project”. Pick a base such as “Vite + React + FastAPI”, give it a name, and confirm. A slug like my-app-k3x8n2 is generated.
2

Wait for scaffolding

The orchestrator copies the template and writes a docker-compose.yml into /projects/{slug}/. Watch for the “Project ready” toast.
3

Start the containers

Click “Start”. Containers spin up on tesslate-network and register with Traefik. The preview panel loads http://frontend.localhost (or whatever the base’s primary container is called).
4

Talk to the agent

Open the chat panel and ask the agent to make a change. For the complete agent tool reference (read/write, bash, sessions, web search, skills, schedules), see the tesslate-agent reference.

8. Clean slate reset

This drops every OpenSail container, volume, and image. Use it when a local state gets stuck.
Leave out step 2 if you only want to reset the database; step 1 already wipes tesslate-postgres-dev-data, tesslate-redis-data, tesslate-projects-data, tesslate-base-cache, and tesslate-gateway-locks. Database-only reset:

Quick Start

For someone who has already read this guide once:

Platform notes

WSL 2 (Windows)

  • Clone into the WSL filesystem (for example ~/code/opensail). Bind mounts from /mnt/c/... are slow and drop file-change events.
  • Run docker compose from inside WSL, not from PowerShell.
  • When piping scripts that pass container paths through docker exec, prefix with MSYS_NO_PATHCONV=1 on Git Bash.

macOS with Colima

Colima replaces Docker Desktop on macOS. OpenSail works with two tweaks:
virtiofs is the only mount type fast enough to keep Vite HMR events responsive.

Linux rootless Docker

The orchestrator bind-mounts the Docker socket so it can manage user project containers. Under rootless Docker the socket path is $XDG_RUNTIME_DIR/docker.sock, and the orchestrator inside the container cannot see it at /var/run/docker.sock. Either run rootful Docker for development, or change the volume mount in docker-compose.yml to the rootless socket and set DOCKER_HOST inside the orchestrator. Kubernetes mode sidesteps this entirely.

Volume permissions

On Linux, the Postgres volume is owned by UID 70 (the alpine postgres user). If you shell in as a different UID you may see permission errors writing to /var/lib/postgresql/data. Do not chown the volume from the host; let the container manage it.

Troubleshooting

Symptom: bind: address already in use on 80, 5432, 6379, 8000, 5173, or 8080.Override the port in .env:
Re-run docker compose up -d. If you changed APP_PORT, Traefik dashboard moves with it, so use http://localhost:8081.
Usual causes:
  • Postgres not ready yet: wait 15 more seconds.
  • SECRET_KEY is empty or still at the placeholder.
  • LITELLM_API_BASE unreachable: boot continues but the log shows warnings.
  • Port 8000 busy on the host: change BACKEND_PORT.
Modern Linux (systemd-resolved), macOS, and Windows with WSL 2 resolve *.localhost to 127.0.0.1 automatically. Some distros do not.
  • Linux: add address=/localhost/127.0.0.1 to dnsmasq, or add per-project entries to /etc/hosts.
  • Windows native: edit C:\Windows\System32\drivers\etc\hosts.
  • Chrome and Firefox honor loopback for *.localhost without /etc/hosts.
The compose file already sets WATCHFILES_FORCE_POLLING=true, CHOKIDAR_USEPOLLING=true, and WATCHPACK_POLLING=true. If it still stops working:
  • Inotify limit hit on Linux: sudo sysctl fs.inotify.max_user_watches=524288.
  • WSL 2: make sure the repo lives inside the WSL filesystem (~/code/...), not /mnt/c/.... The /mnt mount does not emit file events reliably.
If the container is unhealthy, check docker compose logs postgres. Usually a leftover volume with a mismatched password; run the clean slate reset.
  • Traefik dashboard at http://localhost:8080 lists every router. Confirm your container is there.
  • Verify the container has com.tesslate.routable=true (the orchestrator sets this automatically from the generated compose).
  • Check the container is on tesslate-network: docker network inspect tesslate-network.
The orchestrator mounts /var/run/docker.sock. If you run rootless Docker, the socket lives in $XDG_RUNTIME_DIR/docker.sock and the mount is wrong. Either run rootful Docker or edit the volume mount in docker-compose.yml.

Next steps

Desktop install

Install the native Tauri desktop app with local SQLite and per-project runtimes.

Local Kubernetes

Mirror production on Minikube with btrfs CSI and Volume Hub.

AWS production deployment

Deploy OpenSail to AWS EKS with Terraform, ECR, and NGINX Ingress.

Publishing Apps

Turn a project into a Tesslate App and list it in the marketplace.

Getting help

Discord

Chat with the OpenSail team and community.

GitHub Issues

File bugs and feature requests.

Email support

Direct support for teams and self-hosters.