Publishing modules and providers
Publish private Terraform and OpenTofu modules and providers to your registry with the lbr CLI or a CI action — providers signed for you, with no GPG in your workflow.
Early access. Registry is rolling out behind an organization flag.
Longbridge Registry also hosts private Terraform and OpenTofu modules and
providers — on the native registry protocol both terraform and tofu speak,
under the same host, keys, and quota as your images. Publish with the
lbr CLI or a CI action; consumers pull with terraform init /
tofu init (see Consuming modules and providers).
Publishing is an ordinary read-write act on a registry key — the same credential you push images with. There is no separate publisher role.
Publish a module
lbr registry module publish packages a module directory into a tarball and
uploads it. The module is addressed <registry>/<name>/<system> and the line is
created on first publish — nothing to declare ahead of time.
lbr registry module publish ./modules/vpc \
--registry acme-prod --name vpc --system aws --version 1.4.0
The system is a free-form token, conventionally the primary provider (aws,
gcp); a provider-agnostic module can use any label. .git, .terraform, and
.terraform.lock.hcl are left out of the tarball. Modules aren’t signed — the
registry stores and serves the archive as-is.
Consume it with a module {} block; see Consuming modules and
providers.
Publish a provider
The provider protocol makes signatures mandatory — and Longbridge is the signing
authority, so you never hold a GPG key. Build your provider the way you
already do (goreleaser build), then hand the build directory — the per-platform
zips plus manifest.json, the output of goreleaser build minus its signs:
block — to lbr.
lbr registry provider publish \
--registry acme-prod --type widget --version 1.2.3 --dist dist
Longbridge signs it for you with your registry’s managed
key and serves the full provider protocol, so you
can delete the signs: block from your .goreleaser.yaml. Longbridge is the
checksum authority: a SHA256SUMS or signature you upload in managed mode is
ignored, with a notice. The provider is addressed <registry>/<type> and its
line is created on first publish, which is also when the managed key is minted.
Consume it with a required_providers block; signature verification happens
automatically on the consumer’s init.
Publish from CI
The longbridge/registry-publish@v1 action wraps lbr registry <provider|module> publish --oidc. At run time it trades your workflow’s OIDC token for a
short-lived, read-write registry key — so there is no stored secret and no GPG
in the workflow. Create a keyless OIDC access rule in
readwrite mode that trusts your repository once, then:
name: release
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write # required — lets the job mint an OIDC token
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: goreleaser/goreleaser-action@v6
with:
args: build --clean # produces ./dist, no signing
- uses: longbridge/registry-publish@v1
with:
kind: provider
registry: acme-prod
type: widget
version: ${{ github.ref_name }} # a v1.2.3 tag publishes as 1.2.3
dist: dist
For a module, set kind: module with name, system, and dir. A readwrite
rule is required — a pull rule fails cleanly. The signing key is minted and held
server-side, so a CI credential can never mint, rotate, or revoke it: a
compromised workflow can at worst publish within its rule’s scope.
Where to next
- Consuming modules and providers — pull them into your configuration.
- Provider signing — the managed key, rotation, and revocation.
- Access rules — the keyless OIDC rule the CI action needs.