Via

Managed SQLite

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:

let db = Sql.connect("DATABASE")
App codeSql.connect("DATABASE") Vary runtimeproxy-backed SqlConnection Config server Authorize workload + binding Managed SQLite file Query rows or update count
App codeSql.connect("DATABASE") Vary runtimeproxy-backed SqlConnection Config server Authorize workload + binding Managed SQLite file Query rows or update count

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.

OperationUsed forResult
queryReads, including execute_query.Column names and rows.
executeWrites.JDBC update count.
setupSchema/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:

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.

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:

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:

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:

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:

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:

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:

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:

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

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

/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 materialPurpose
Workload tokenProves app, deploy, and runtime identity.
Config bootstrapTells the runtime where to ask for descriptors.
Binding claimLists 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:

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.

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:

MethodPathPurpose
POST/v1/runtime/config/resolveResolve authorized env values and SQLite descriptors for the running release.
POST/v1/runtime/config/refreshRefresh 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

LimitMeaning
Single-node V1The database file is local to one Via host. Do not treat it as a distributed SQL service.
Binding names are release-scopedA deploy snapshots current bindings. Binding or unbinding affects the next deployment, not already-running releases.
Restore is explicitRestoring over active add-ons is refused unless the operator chooses a drain/restart or force-after-timeout flow.
Builds have no add-on materialBuild containers cannot read managed SQLite descriptors or credentials and cannot use the config server runtime audience.
Storage is Via-ownedOperators 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 for the workload identity and config-server model.

← Runtime trust
Public API release →