The database can carry the worker too.
Store a complete VGI executable or archive in a DuckDB table, move or snapshot that database
like any other, then ATTACH the worker with a
database:// location. VGI verifies, caches, and runs the package
locally — no separate release download or container pull at query time.
A database-backed worker is still a local worker. What changes is delivery: instead of naming a command, release asset, or container image, the location names a row in a package registry. The bytes travel with the database; each attaching machine executes its own platform build.
From BLOB to process
How it works
Package
Store one executable or archive as a BLOB row, alongside its platform, version, entrypoint, and SHA-256.
Resolve
ATTACH selects exactly one row for the requested worker, current platform, and immutable package version.
Run locally
VGI verifies the bytes, materializes them in its private cache, and launches the entrypoint as a child process.
The resolver is deliberately narrow: it finds exactly one row, recomputes its digest, and turns it into a child-process location. It is not a database extension loader, a remote execution service, or a sandbox.
Write the registry
Package a worker with SQL
vgi_worker_package() is a convenience table macro over DuckDB's
own read_blob(), pragma_platform(), and
hashing primitives. It is not a separate importer: materialize or insert the one row it
returns using normal SQL and normal transactions.
LOAD vgi;
-- Keep the registry in an ordinary persistent DuckDB database.
ATTACH 'worker-registry.duckdb' AS worker_registry;
-- vgi_worker_package() uses read_blob(), detects the archive format,
-- fills in pragma_platform(), and computes the SHA-256.
CREATE TABLE worker_registry.main.worker_packages AS
SELECT * FROM vgi_worker_package(
'dist/acme-worker.tar.gz',
'acme-worker',
'1.4.0',
entrypoint := 'bin/acme-worker'
);
ALTER TABLE worker_registry.main.worker_packages
ADD PRIMARY KEY (worker_name, platform, package_version);
CHECKPOINT;
The source file is needed only while that statement runs. After commit, the complete bytes
live in contents; a reader does not need the build directory or
the original artifact path.
-- Add another immutable build using normal SQL.
INSERT INTO worker_registry.main.worker_packages
SELECT * FROM vgi_worker_package(
'dist/acme-worker-1.5.0',
'acme-worker',
'1.5.0'
);
-- Cross-packaging: use the exact pragma_platform() value of the target.
INSERT INTO worker_registry.main.worker_packages
SELECT * FROM vgi_worker_package(
'dist/acme-worker-linux-arm64.tar.gz',
'acme-worker',
'1.5.0',
entrypoint := 'bin/acme-worker',
platform := 'linux_arm64'
);
Seven required columns
Package table contract
The table name is yours. VGI only requires the columns below; the packaging macro also
emits an optional created_at timestamp. Additional publisher
metadata is fine.
| Column | Type | Meaning |
|---|---|---|
worker_name |
VARCHAR | Stable package name used by the URI. |
platform |
VARCHAR | Exact consumer platform from pragma_platform(). |
package_version |
VARCHAR | Publisher-selected immutable version. |
package_format |
VARCHAR | Executable or supported archive/compression format. |
entrypoint |
VARCHAR | Relative executable path after extraction. |
contents |
BLOB | Complete executable or archive bytes. |
sha256 |
VARCHAR | 64-character SHA-256 of contents. |
VGI counts all matching rows and fails if the coordinate is ambiguous. A primary key on
(worker_name, platform, package_version) makes that invariant
explicit.
Read in a new process
Attach from the registry
The location has four path components — catalog, schema, table, and worker — followed by a required package version:
Catalog, schema, and table are resolved as quoted identifiers. Worker name, platform, and
version are bound values rather than SQL text. Percent-encode names containing
/, ?, #, or
%.
-- The registry may be attached read-only.
ATTACH 'worker-registry.duckdb' AS worker_registry (READ_ONLY);
ATTACH 'acme' AS acme (
TYPE vgi,
LOCATION 'database://worker_registry/main/worker_packages/acme-worker?package_version=1.4.0'
);
SELECT * FROM acme.main.events LIMIT 10;
Resolution uses a dedicated database connection. Commit the package row before attaching; temporary tables and uncommitted changes are intentionally not visible. The attached registry itself can be read-only.
-- The row digest is always recomputed. For an out-of-band pin,
-- append the expected 64-character SHA-256 as a URI fragment.
ATTACH 'acme' AS pinned_acme (
TYPE vgi,
LOCATION 'database://worker_registry/main/worker_packages/acme-worker?package_version=1.4.0#sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
);
What the BLOB contains
Formats and platforms
A worker is often more than one file. Store the whole runtime as an archive and name the executable inside it; VGI extracts the package with bounded, traversal-safe archive rules.
executable
A directly runnable file. raw and binary are accepted aliases.
zip · tar · tar.gz · tar.zst
Multi-file archives. Supply an explicit relative entrypoint.
gzip · zstd
A single compressed executable. The decompressed filename becomes the entrypoint unless overridden.
package_format := 'auto' recognizes those filename extensions.
Everything else is treated as a raw executable. Archive packages require an explicit,
relative entrypoint.
Store one row per supported platform. By default the macro records the packaging machine's
exact pragma_platform() value; pass platform
:= … when inserting a cross-compiled build. At attach time there is no fallback to a
“close enough” architecture — the platform must match exactly.
Reuse without races
Cache and cleanup
Cache identity includes the verified content digest, normalized format, and entrypoint. Installation happens through a private staging directory and atomic rename, so concurrent DuckDB processes converge on one immutable copy. Running catalogs and pooled subprocesses hold cross-process leases; cleanup skips an artifact while any process still uses it.
-- See every immutable package materialized on this machine.
SELECT * FROM vgi_worker_cache();
-- Apply the configured age and size limits now.
SELECT * FROM vgi_worker_cache_prune();
-- Remove everything not protected by a running catalog or process lease.
DETACH acme;
SELECT * FROM vgi_worker_pool_flush();
SELECT * FROM vgi_worker_cache_flush();
| Setting | Default | Limit |
|---|---|---|
vgi_worker_cache_max_bytes |
5 GiB | Maximum managed cache size. |
vgi_worker_cache_ttl_seconds |
30 days | Remove packages unused for this long. |
vgi_worker_package_max_bytes |
512 MiB | Maximum stored/compressed BLOB size. |
vgi_worker_package_max_extracted_bytes |
1 GiB | Maximum extracted package size. |
vgi_worker_package_max_files |
10,000 | Maximum archive entries. |
Set vgi_worker_cache_dir to choose the cache root. It must be on a
filesystem that permits execution. With no override, VGI follows its release cache and
then XDG_CACHE_HOME or the current user's cache directory.
Trust boundary
A verified package is still native code
Attaching a database package executes code supplied by that database. Use registries whose writers you trust. SHA-256 verification proves which bytes you received; it does not sandbox them or decide whether they are safe.
VGI recomputes the row digest before cache lookup and can require an independent digest pin in the URI. Cached entrypoints are executed directly rather than through a shell. Archive entries must be relative and normalized; traversal, links, backslashes, drive prefixes, excessive file counts, and oversized extraction are rejected before installation.
Because execution requires a native child process, database:// is
for desktop and server DuckDB builds. It is not available inside browser DuckDB-WASM.
Before handing it over
Production checklist
Use a committed, non-temporary table. Resolution runs through a dedicated connection, so temporary tables and uncommitted rows are intentionally invisible.
Enforce one row per worker, platform, and package version with a primary key or unique constraint.
Treat package versions as immutable. Publish a new version instead of replacing bytes under an existing coordinate.
Build and test every platform you publish. A package row must contain native code for the machine that will execute it.
Use an out-of-band digest pin when the expected build is known independently of the registry.
Attach only registries whose writers you trust. Integrity verification detects changed bytes; it does not make untrusted native code safe.