Publishing a DevContainer Feature to GHCR

A custom Feature is only reusable across repos once it's published. This page packages a Feature and pushes it to GitHub Container Registry (GHCR) with the Features CLI, so other projects reference it by OCI path and pin its version like any dependency.

Prerequisites

You need a built Feature and GHCR publish access.

  • A Feature with install.sh and devcontainer-feature.json.
  • The DevContainer Features publishing action or CLI.
  • GHCR write access for the namespace.

Publish prerequisitesYou need the Feature source, a publish action, an OCI path, and version pinning.Feature dirsrc/mytool/Publish actionfeatures CIOCI pathghcr.io/org/mytoolPinconsumers pin version

Step-by-Step Implementation

  1. Lay out the Feature in the standard structure.
src/mytool/devcontainer-feature.json
src/mytool/install.sh
  1. Publish with the official action on release.
      - uses: devcontainers/action@v1
        with:
          publish-features: true
          base-path-to-features: ./src
  1. Reference it by OCI path, pinned.
{ "features": { "ghcr.io/acme/mytool:1": { "version": "1.2.0" } }, "remoteUser": "vscode" }
  1. Verify a consumer build resolves it.
devcontainer read-configuration --workspace-folder .

Publish pipelineThe Features action publishes the standard layout to GHCR for OCI-pinned consumption.Standard layoutsrc/<id>/Publish actionpushes to GHCROCI referenceghcr.io/org/id:majorPinned by consumerslike a dependency

Common Pitfalls

Publishing issues are wrong layout, missing permissions, or unpinned consumption.

Publish triageA triage path from a failing publish to a shareable, pinnable Feature.Is the layout src/<id>/?NOFix the directory structureDoes the action have GHCR write?YESConsumers pin the versionFeature is shareable

SymptomRoot CauseRemediation
Publish failsWrong Feature layoutUse src// with metadata + install.sh
403 pushing to GHCRNo package write permissionGrant packages: write to the workflow
Consumers get 'latest' silentlyReferenced without a versionPin ghcr.io/org/id to a version
Feature not foundWrong OCI namespaceMatch the namespace to your GHCR org

Conclusion

Publishing a Feature to GHCR turns it into an OCI artifact any project references by path and pins by version — exactly like the official Features. Use the standard src/<id>/ layout and the publishing action, and pin it in consumers so updates are intentional.

Publish and consumePublishing gives an OCI reference; consumers pin and review it.Publishing givesOCI referenceVersion tagsCross-repo reuseConsumers shouldPin the versionReview bumpsTrust the source

FAQ

Why publish to GHCR instead of a local path? A local path only works within one repo. Publishing to GHCR gives an OCI reference (ghcr.io/org/id) any project can consume and pin, so a Feature authored once is reused across every repo — the same distribution model as the official DevContainer Features.

How do consumers pin my Feature? They reference it by OCI path with a version, e.g. ghcr.io/acme/mytool:1 plus a version option, and treat bumps as reviewed changes. Pinning keeps a Feature update from silently changing their toolchain, matching base-image and Feature pinning discipline.

What permissions does the publish workflow need? packages: write for the GHCR namespace, so the action can push the Feature package. Grant it in the workflow's permissions block and ensure the namespace matches your organization, then the publishing action pushes on release.