How to publish private Bicep extensions to OCI registries
Ship private Bicep extensions through the OCI registry your team already trusts.
Bicep v0.45.6 just launched a couple of days ago, and there's an interesting new experimental feature added: OCI registry support for publishing and restoring Bicep artifacts.
To get right off the bat, in practical terms, authors are no longer limited to Azure Container Registry (ACR) for publishing/restoring. You can now store any private extension in an OCI-compliant registry that your organization might already operate. That includes GitHub Container Registry, Docker Hub, or a self-hosted registry such as Harbor.
But this isn't just a simple change. It's more than that. That's because it lets teams distribute Bicep extensions through the same registry platform and DevOps processes that they use for other internal artifacts.
[!NOTE]
As mentioned, the feature is experimental. Treat it as such when you're evaluating it before it becomes a production dependency.
This article goes through an end-to-end flow by enabling the feature, authenticating, and publishing an existing extension so it makes it available to template authors. At the end, it also explains what the change means for organizations that need to govern (private) extensions.
What OCI registry support changes
An OCI registry is a server that stores artifacts using the Open Container Initiative distribution specification. Interestingly enough, container is in that name, and it's commonly used for container images. But they can also store other versioned artifacts.
Bicep extensions are precisely that type of artifact because they're packaged as OCI artifacts. That means that the familiar registry reference format still applies:
br:registry.github.com/organization/bicep-extensions/myextension:v1.0.0Before v0.45.6, Bicep used the Azure Container Registry SDK for registry operations. That limitation didn't allow for publishing and restoring to ACR-compatible workflows. Now, if you place the ociEnabled feature flag in your Bicep configuration file, Bicep is going to use the ORAS transport for non-Azure OCI registries.
[!NOTE]
ORAS is a client library for moving OCI artifacts without treating them as container images.
With this change, existing ACR workflows don't have to change. Bicep continues to use the Azure SDK and Azure credentials for ACR endpoints. Non-Azure registries use Docker-compatible credentials. The extension reference syntax and Bicep's local artifact cache stay the same, so template authors do not need to learn a second reference format.

Prerequisites
To fully use this functionality, there are a couple of prerequisites. Of course, you're going to need the latest Bicep release (v0.45.6) or later. Then, you should add the following flags in your bicepconfig.json:
{
"experimentalFeaturesEnabled": {
"ociEnabled": true,
"localDeploy": true,
"extensibility": true
}
}This article uses GHCR, which Bicep trusts by default. This is important to know. Any other registry is rejected by Bicep as it isn't listed in the trusted registries before connecting (or sending credentials). Follow the official using OCI registries documentation.
Keeping my Databricks extension private on GitHub
Back in the days, I had written a Bicep Databricks extension, which is open-sourced on GitHub. This extension showcased an ACR-based workflow.
But let's imagine that we want to keep it private through GitHub. I would move this repository to an enterprise organization, such as mycompany-bicep-extensions/bicep-ext-databricks, mark it as private repository, and publish the extension package to GHCR.
When that happens, the installable artifacts lives in GitHub Container Registry, at a ghcr.io address. After publishing, I would configure the package as private and grant the platform team read access to those who want to use it. GitHub can inherit package access from the linked private repository, or the package can have its own granular permissions.
So, what does that look like from the existing extension? The repository already contains a build script, creating platform-specific binaries. From the repository root, I would build them, sign in to GHCR with an identity that has package write permissions, and then publish the extension:
./build.ps1 -Clean -Configuration Release
$env:DOCKER_REGISTRY = 'ghcr.io'
$env:DOCKER_USERNAME = 'mycompany-bicep-extensions-release-bot'
$env:DOCKER_PASSWORD = $env:GHCR_WRITE_TOKEN
bicep publish-extension `
--bin-win-x64 ./output/win-x64/databricks-extension.exe `
--bin-linux-x64 ./output/linux-x64/databricks-extension `
--bin-osx-x64 ./output/osx-x64/databricks-extension `
--target br:ghcr.io/mycompany-bicep-extensions/bicep-ext-databricks:v1.0.0The first command builds those platform-specific binaries. The environment variables scope the Docker credentials to ghcr.io. Bicep uses them only for that matching registry in this example.
The final command uploads the binaries as one Bicep extension artifact. v1.0.0 is an example release, marking most (software) extension releases as GA.
For a GitHub Actions release workflow, grant the job packages: write permission and publish with GITHUB_TOKEN. When the workflow publishes from the source repository, GitHub associates the package with that repository and can apply its package-access model:
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: azure/setup-bicep@v2
with:
version: v0.45.6
- name: Sign in to GitHub Container Registry
run: echo "${{ github.token }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
- name: Build and publish the extension
shell: pwsh
run: |
./build.ps1 -Clean -Configuration Release
bicep publish-extension --bin-win-x64 ./output/win-x64/databricks-extension.exe --bin-linux-x64 ./output/linux-x64/databricks-extension --bin-osx-x64 ./output/osx-x64/databricks-extension --target br:ghcr.io/mycompany-bicep-extensions/bicep-ext-databricks:v1.0.0 # Increment this automaticallyConfigure consumers for restoration
The process for configuring consumers is basically the same as ACR-enabled workflows. But in this case, you'll use docker login ghcr.io locally. Or you can use other examples from the official documentation.
// bicepconfig.json
{
"experimentalFeaturesEnabled": {
"ociEnabled": true,
"localDeploy": true,
"extensibility": true
},
"extensions": {
"databricksExtension": "br:ghcr.io/mycompany-bicep-extensions/bicep-ext-databricks:v1.0.0"
},
"implicitExtensions": []
}The configuration key matches the extension's name. Bicep restores the artifact into its local cache when the extension is used:
targetScope = 'local'
param workspaceUrl string
extension databricksExtension with {
workspaceUrl: workspaceUrl
}Developers don't need to download the extension binaries. It's all handled behind the scenes.
Why OCI publishing matters to enterprises
While enterprises already could leverage ACR-enabled workflows, often organizational standards are broader. That could mean that there's already an approved way to publish artifacts in a certain way. And that way could be OCI.
With this expansion, distribution can become part of the software supply chain. OCI support gives technical leaders an option to place those extensions where governance already exists. Whether that's a regional Harbor instance, an enterprise GitHub organization (seen in this article), or another approved registry instead of requiring a separate ACR just for Bicep. Existing vulnerability scanning, audit logs, retention policies, replication, and backup processes may apply as well, depending on the registry.
The fact that there's now a trusted registry allowlist is also an important part of a governance model. You should prefer an exact hostname such as harbor.mycompany.com over a broad wildcard (unless registry hosts need to share the trust boundary). In CI pipelines, you can scope a pull token to consumer repositories and a separate push token to the publishing pipeline.

Lastly, because the capability is experimental, I encourage you to test it out, but also plan for this change. Start with a small extension and a small group of consumers. Validate it on each type of developer workstation and confirm that everything can be pulled in smoothly.
Next steps
OCI registry support lets Bicep extensions live alongside the rest of an organization's private artifacts without changing how authors reference an extension. Make the following part of the extension's release process:
- Enable
ociEnabledfor publishers and consumers. - Authenticate with a registry-specific identity.
- Publish versioned extension packages (preferably immutable).
- Declare the extension reference in the consumer configuration.
- Manage the trusted-registry list, package permissions, and CI validation centrally.
For more information, check out the following articles: