Set up a plugin registry
A registry is the store your instance reads at boot to download the plugins it
declares, and the store plugin authors publish to with meridian-plugin publish.
This recipe stands up one registry — either a GCS bucket or a filesystem
directory — and wires the IAM so the API can read and authors can publish.
For what to put in the manifest once the registry exists, see Declare and install plugins. For the publishing side, see Publish to the registry.
Choose a backend
The store backend is chosen from the URL prefix
(libs/plugin-cli/src/registry/store.ts, openStore):
gs://bucket[/prefix]— a Google Cloud Storage bucket, accessed over the GCS JSON API with an ADC token. Use this for shared and production deployments.- A local path (
/srv/plugins,./plugins, orfile:///srv/plugins) — a directory on disk or an NFS mount. Use this for development, tests, or a single-host deployment where all components share the filesystem.
Both backends implement the same layout and immutability rules
(libs/plugin-cli/src/registry/registry.ts): a published version lives under
@publisher/name/<version>/… and is immutable — re-publishing an existing
version is refused, so you bump the version instead.
Option A — GCS bucket
1. Create the bucket
gcloud storage buckets create gs://meridian-plugins \
--location=europe-west1 \
--uniform-bucket-level-access
Pick any name; the manifest and CLI reference the bucket by its gs:// URL. A
prefix is supported (gs://meridian-plugins/prod) if you want to share a bucket
across environments.
2. Grant read to the instance identity
At boot the API resolves each declared plugin against the registry and downloads
missing versions (apps/api/src/registry.ts, ensureInstancePlugins). The
process identity — a service account, typically via workload identity — only ever
reads the bucket, so roles/storage.objectViewer is sufficient:
gcloud storage buckets add-iam-policy-binding gs://meridian-plugins \
--member="serviceAccount:sa-clinical-api@PROJECT.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
Authentication uses ADC: the store requests the
devstorage.read_write scope and calls GoogleAuth.getAccessToken()
(store.ts, gcsStore). The granted IAM role — not the requested scope — is
what bounds actual access, so read-only viewer is correct for the API.
3. Grant publish to plugin authors
meridian-plugin publish writes objects but never overwrites: every put uses
ifGenerationMatch: 0 (create-if-absent), which fails with HTTP 412 if the
object already exists (store.ts, gcsStore.put). So authors need object
create, not full object admin:
gcloud storage buckets add-iam-policy-binding gs://meridian-plugins \
--member="group:plugin-authors@posos.fr" \
--role="roles/storage.objectCreator"
roles/storage.objectCreator lets them add new versions but not delete or
replace existing ones, which matches the immutability guarantee.
Option B — local directory
1. Create the directory
mkdir -p /srv/plugins
The directory backend (store.ts, dirStore) creates nested paths on write and
enforces the same create-if-absent rule, so no further layout setup is needed.
2. Grant filesystem access
- The API process must be able to read and traverse the tree — mount it read-only if you want to enforce that the instance never writes.
- Plugin authors need write access to publish. If the registry is an NFS share, mount it read-write on author hosts and read-only on the API host.
Everything that touches this path (API endpoint and proxy processes) must see the same directory, so a shared/NFS mount is required for multi-host setups.
Point the instance at the registry
Set the registry in the instance manifest, or fall back to the
MERIDIAN_REGISTRY environment variable — the manifest's registry: key wins,
the env var is the default (apps/api/src/registry.ts, ensureInstancePlugins):
# instance manifest (INSTANCE_CONFIG)
registry: gs://meridian-plugins # or /srv/plugins, or file:///srv/plugins
plugins:
"@posos/demo": "^0.1.0"
Or, without a manifest key:
export MERIDIAN_REGISTRY=gs://meridian-plugins
A relative local path in either place is resolved from the repo root; gs://
and file:// URLs are used as-is. See
registry and
MERIDIAN_REGISTRY for the full
resolution rules, and note that downloaded versions are cached under
MERIDIAN_PLUGIN_CACHE (default .data/plugins) — mount it as a volume so
restarts do not re-download.
Verify
Point the CLI at the registry and list a plugin's published versions
(libs/plugin-cli/src/commands/publish.ts, versionsCommand). An empty
registry prints "no published versions", which still confirms the store is
reachable and the credentials work:
export MERIDIAN_REGISTRY=gs://meridian-plugins # or pass --registry <url>
meridian-plugin versions @posos/demo
To confirm the read path end to end, declare a published plugin in the manifest and boot the instance: a plugin that is listed but cannot be resolved (no registry configured, version not found, download failed) fails the boot loudly — this is treated as a configuration error, the same as an invalid manifest.
Related
- Declare and install plugins — the manifest side.
- Publish to the registry — the author side.
- Bundling and distribution — why versions are immutable and what a published
dist/contains. - Environment variables and Instance manifest — configuration reference.