
<div class="callout callout-attn"><p><strong>Alpha.</strong> Via is under active development. APIs, operational flows, and host requirements may change between releases.</p></div>

> **Attn:** We are still exploring what SQLite access should look like on the Via platform. Expect this surface to change.

Managed SQLite gives an app a Via-owned database file. It is the first concrete Via add-on, not the whole add-on system. The app does not need a database path or credential in source, build logs, image layers, or deploy commands. Operators create the add-on with `via addon` and bind it to an app under a name such as `DATABASE`. At runtime, the app asks the config server for that binding.

SQLite V1 is single-node. One Via host manages one local SQLite database file for an app. Use a networked database if the app needs distributed writes or a database cluster.

## SQLite proxy mode

Managed SQLite keeps the database file on the Via host. App code opens the binding name:

```vary
let db = Sql.connect("DATABASE")
```

```mermaid
flowchart TB
    app["App code<br/>Sql.connect(&quot;DATABASE&quot;)"]
    runtime["Vary runtime<br/>proxy-backed SqlConnection"]
    config["Config server"]
    auth["Authorize workload + binding"]
    sqlite["Managed SQLite file"]
    result["Query rows or update count"]

    app --> runtime
    runtime --> config
    config --> auth
    auth --> sqlite
    sqlite --> result
    result --> runtime
```

The runtime sends SQLite work through the config server. Via authorizes every operation against workload identity, app, deploy, runtime instance, release binding, add-on type, descriptor generation, and scope.

| Operation | Used for | Result |
|---|---|---|
| `query` | Reads, including `execute_query`. | Column names and rows. |
| `execute` | Writes. | JDBC update count. |
| `setup` | Schema/setup statements. | JDBC update count. |

Read-only bindings can run only `query`. Missing tokens return `401`; invalid or unbound runtime identities return `403`; malformed requests return `400`; unexpected SQLite failures return a generic `500` with descriptor material redacted.

This path keeps raw SQLite paths out of app environment variables, deploy commands, image layers, and logs. It is not a general SQL service for arbitrary clients.

Apps that need direct file SQLite can still open a normal path, for example under `/data` when the app has the `fs` capability:

```vary
let db = Sql.connect("/data/app.sqlite")
```

That direct-file pattern is app-managed storage, not the managed SQLite add-on created with `via addon create sqlite`.

## Add-on lifecycle

Run these commands on the server host as an operator. Add `--state-dir /var/lib/via` only when you are targeting a non-default state root.

```bash
via addon create sqlite app-db --app api
via addon list --app api
via addon bind api app-db --binding DATABASE
via addon status app-db
via addon snapshot app-db
via addon rotate-credentials app-db --app api --binding DATABASE
via addon revoke-credentials app-db --app api --binding DATABASE --generation 1
via addon restore app-db --snapshot snap_123 --drain --restart-bound-apps
via addon unbind api DATABASE
via addon destroy app-db
```

The create command provisions storage for the app. The bind command records future binding policy. The next successful deploy snapshots that binding into the release.

For the common create-and-bind path, use the explicit convenience flow:

```bash
via addon create sqlite app-db --app api --bind DATABASE
```

`DATABASE` is the app-facing binding name. Keep it stable across deploys so app code does not care if the add-on id changes.

## Command reference

Create and inspect:

```bash
via addon create sqlite NAME --app APP
via addon create sqlite NAME --app APP --bind BINDING
via addon list --app APP
via addon status NAME
```

Binding changes:

```bash
via addon bind APP NAME --binding BINDING
via addon unbind APP ADDON_OR_BINDING
```

Binding changes affect future releases. Existing releases keep their snapshotted claims until they are replaced.

Snapshots and restore:

```bash
via addon snapshot NAME
via addon restore NAME --snapshot SNAPSHOT --drain --restart-bound-apps
```

Restoring over an active add-on requires an explicit safety flow, such as `--drain --restart-bound-apps` or the emergency `--force-after-timeout`.

Credential generations:

```bash
via addon rotate-credentials NAME --app APP --binding BINDING
via addon revoke-credentials NAME --app APP --binding BINDING --generation N
```

Rotation output includes the app, binding, generation, status, and operation id. It does not print credential material. Running workloads that present a revoked generation are rejected on refresh.

Destroy:

```bash
via addon destroy NAME
```

By default, destroy only works on unbound add-ons. Use `--force` only after removing bindings on purpose.
Destroyed add-ons remain inspectable by id, and Via tombstones the stored name
as `NAME#destroyed#ADDON_ID` so the original app/name can be recreated without
colliding with stale metadata. If a failed provisioning record has no
data-bearing storage on disk, Via similarly renames it to
`NAME#failed#ADDON_ID` during the next create so the name can be reused. Failed
records with SQLite bytes still present keep blocking reuse until an operator
makes a backup/retention decision and destroys or repairs the add-on.

## Storage and snapshots

Production add-on storage lives under the Via state root:

```text
/var/lib/via/addons/sqlite/<addon-id>/data.sqlite
```

Snapshot files live beside the database under the add-on storage directory:

```text
/var/lib/via/addons/sqlite/<addon-id>/snapshots/<snapshot-id>.sqlite
```

The control-plane database stores add-on, binding, release-binding, and snapshot metadata. The SQLite database bytes and snapshot bytes remain on disk under the add-on storage tree. Whole-host `via backup` archives include both the control-plane metadata and the managed SQLite storage tree.

## Runtime access

Apps do not receive add-on descriptors during build. Runtime containers receive only scoped material for the running release:

| Runtime material | Purpose |
|---|---|
| Workload token | Proves app, deploy, and runtime identity. |
| Config bootstrap | Tells the runtime where to ask for descriptors. |
| Binding claim | Lists the SQLite bindings this release can use. |

Build tests may still use regular SQLite databases that they create inside the build workspace or `/tmp`.

If the runtime token is missing or expired, `vary app status`, `vary app doctor`, and `vary app smoke --json` report `runtime_identity_expired` and recommend `vary app repair <name> --target <target>`.

In Vary app code, use the binding name instead of a file path:

```vary
let db = Sql.connect("DATABASE")
```

When the runtime SQLite proxy bootstrap is present, `Sql.connect("DATABASE")` resolves `DATABASE` through the config server and uses the Via-managed SQLite proxy. Direct file paths and in-memory databases still use the regular SQLite behaviour documented in [SQLite databases](/docs/sqlite/).

The config server also supports descriptor-shaped requests for the binding name and derived URL material such as `DATABASE_URL` when the runtime identity can use that add-on. These values are runtime-only and are redacted from logs.

The runtime config-server JSON API is:

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/v1/runtime/config/resolve` | Resolve authorized env values and SQLite descriptors for the running release. |
| `POST` | `/v1/runtime/config/refresh` | Refresh selected keys by echoing the app, release, deploy, profile version, scope, and generation returned by `resolve`. |

Both endpoints require `Authorization: Bearer <runtime-identity-jwt>` with the `via-config` audience. Build workload tokens and user session tokens are rejected.

Descriptor refresh uses generations. A runtime sends the add-on binding name, scope `addon`, and generation returned by `resolve`. The config server checks that the generation still belongs to the app, binding, release, and deploy, and that it has not been revoked.

## Operational limits

| Limit | Meaning |
|---|---|
| Single-node V1 | The database file is local to one Via host. Do not treat it as a distributed SQL service. |
| Binding names are release-scoped | A deploy snapshots current bindings. Binding or unbinding affects the next deployment, not already-running releases. |
| Restore is explicit | Restoring over active add-ons is refused unless the operator chooses a drain/restart or force-after-timeout flow. |
| Builds have no add-on material | Build containers cannot read managed SQLite descriptors or credentials and cannot use the config server runtime audience. |
| Storage is Via-owned | Operators should use `via addon snapshot`, `via addon restore`, `via backup create`, and `via backup restore` rather than copying live database files by hand. |

Continue to [Secrets and config](/docs/via/secrets-and-config/) for the workload identity and config-server model.
