# Web hosting (Sites)
Source: https://sovaria.in/docs/hosting

Host static sites and single-page apps on <site>.sovaria.app and your own domains: deploys that upload only changed files, instant rollback, preview channels, redirects, rewrites to functions and SPA fallback.

A **site** serves a build folder (Vite, React, Vue, Svelte, Astro, a Next.js static export, docs, a landing page) at `https://<site>.sovaria.app` — a domain of its own, separate from the console and the API, so a page can never read or set their cookies — and at any custom domain you connect. Every deploy is immutable; a release points the site at one deploy in a single step, so visitors see the old site or the new one, never a mix; rollback is the same step backwards.

(Diagram: How a page is served)

## Deploy a site

1. **Create the site and its config**
```bash
sov hosting init --site my-app --public dist --spa      # writes the hosting section of sovaria.json
```
2. **Build and deploy** — Only files the project doesn't already have are uploaded; then the release switches the site.
```bash
npm run build && sov hosting deploy -m "new pricing page"
# ✓ Deployed 214 files (3 uploaded) → https://my-app.sovaria.app
```
3. **Roll back if needed**
```bash
sov hosting releases
sov hosting rollback                 # the previous release, at once (or --to <deploy id>)
```

In the console (**Hosting**) you can also create sites, deploy a folder from the browser, roll back, manage preview channels, domains and deploy tokens. From code: `sovaria.hosting.deployFiles(site, files)` (JavaScript) or `HostingClient(sov).deploy_directory(site, "dist")` (Python). Sovariq can create a site and publish a page you write together.

## sovaria.json

sovaria.json:
```json
{
  "hosting": {
    "site": "my-app",
    "public": "dist",
    "spa": true,
    "cleanUrls": true,
    "trailingSlash": false,
    "redirects": [{ "source": "/blog/:slug", "destination": "https://blog.example.com/:slug", "type": 301 }],
    "rewrites": [
      { "source": "/api/**", "function": "api" },
      { "source": "/app/**", "destination": "/app.html" }
    ],
    "headers": [{ "source": "**/*.@(js|css)", "headers": { "Cache-Control": "public, max-age=600" } }],
    "errorPage": "/404.html",
    "ignore": ["**/.*", "**/node_modules/**"]
  }
}
```

| Setting | What it does |
|---|---|
| `spa` | Requests for pages that don't exist serve `/index.html` (client-side routing). Missing assets (`/x.png`) still get 404. |
| `cleanUrls` | `/about` serves `about.html`; `/about.html` redirects to `/about`. |
| `trailingSlash` | `true`: `/docs` → `/docs/`; `false`: `/docs/` → `/docs`; omitted: both work. |
| `redirects` | `source` → `destination` with `type` 301/302/303/307/308. The query string is kept. |
| `rewrites` | Serve another file, or call a **function** (as an [HTTP function](https://sovaria.in/docs/functions-http) request from the visitor; it may return HTML here). |
| `headers` | Extra response headers for matching paths (CSP, CORS, caching). `Set-Cookie`, `Content-Type` and framing headers are set by Sovaria. |
| `errorPage` | The page sent with 404 (default `/404.html` when it exists). |

Patterns: `**` any depth, `*` one segment, `?` one character, `{a,b}` and `@(a|b)` alternatives, `:name` one segment and `:name*` the rest, both usable in the destination. A request is answered by the first that applies: redirects → the exact file (with cleanUrls / trailingSlash) → rewrites → the SPA fallback → the 404 page.

## Caching, compression and paths

- Content types come from the file extension (`text/html; charset=utf-8`, `text/javascript`, `font/woff2`, `application/wasm`…).
- Fingerprinted assets (`app-3f9a2b1c.js`, the default for Vite and most bundlers) are cached for a year with `immutable`; pages are revalidated on every visit with an `ETag` (unchanged → `304`), so a release shows at once; other files for an hour.
- Pre-compressed files next to an asset (`app.js.br`, `app.js.gz`) are sent to browsers that accept them, with `Vary: Accept-Encoding`.
- Paths are strict: `..`, backslashes and control characters are refused at deploy and at serve, and there are no directory listings — a path is a file in the deploy or it isn't.

## Preview channels

A channel serves its own deploy at `https://<site>--<channel>.sovaria.app` and expires on its own. Use one per pull request or for a stakeholder review.

CLI:
```bash
sov hosting channels deploy pr-12 --expires 7d
sov hosting channels list
sov hosting channels delete pr-12
```

With a GitHub repository linked (console › Functions › GitHub, **Site folder**), every push to the branch deploys the folder live, and each pull request gets a `pr-<number>` channel that is deleted when it closes — for prebuilt or plain static folders. To build first, use the CI flow below.

## Deploy from CI

A deploy token deploys one site and nothing else; it needs no person's session. Create it in the console (Hosting › Deploy tokens) or with `sov hosting tokens create --name "GitHub Actions"`, and store it as the secret `SOVARIA_HOSTING_TOKEN`.

.github/workflows/site.yml:
```text
on: { push: { branches: [main] }, pull_request: {} }
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: pip install sovaria-cli
      - run: sov hosting deploy --site my-app ${{ github.event_name == 'pull_request' && format('--channel pr-{0} --expires 7d', github.event.number) || '' }}
        env:
          SOVARIA_HOSTING_TOKEN: ${{ secrets.SOVARIA_HOSTING_TOKEN }}
```

## Custom domains

1. **Add the domain**
```bash
sov hosting domains add www.example.com --site my-app
```
2. **Add the two DNS records it shows** — A TXT record proves you own the domain; a CNAME sends its traffic to Sovaria.
3. **Verify** — Console › Hosting › Domains › Check DNS, or `sov hosting domains verify <id>`. TLS is issued automatically once the domain is verified and points here; ownership is re-checked regularly.

## Limits and billing

|  | Free | Pro | Ultra | Team | Enterprise |
|---|---|---|---|---|---|
| Sites per project (web hosting) | 1 | 10 | 25 | 50 | 200 |
| Site files stored | 1 GB | 10 GB | 50 GB | 200 GB | 1 TB |
| Monthly site bandwidth | 2,147,483,648 | 107,374,182,400 | 536,870,912,000 | 2,199,023,255,552 | 10,995,116,277,760 |
| Custom domains for sites | 1 | 10 | 25 | 100 | 500 |
| Preview channels per site | 1 | 10 | 25 | 50 | 100 |
| Longest preview channel lifetime (days) | 7 days | 30 days | 30 days | 90 days | 90 days |
| Largest file in a site deploy | 10 MB | 50 MB | 100 MB | 100 MB | 100 MB |
| Largest site deploy | 250 MB | 1 GB | 2 GB | 5 GB | 10 GB |
| Files per site deploy | 5,000 | 20,000 | 50,000 | 100,000 | 100,000 |
| Releases kept per site for rollback | 10 | 50 | 100 | 200 | 500 |

- Stored files count each distinct file once across your sites and the releases kept for rollback; older deploys are pruned past `host.releases_kept`.
- Bandwidth on Free is a hard monthly cap: past it, sites answer 429 until the next month. Paid plans keep serving; site downloads count with the plan's bandwidth and are billed past it.
- Sovaria may take down a site that hosts phishing, malware or other abuse; it then answers "Site unavailable" and you are told why.
