Collaborating on GitHub
Topic 41

Releases and Packages

Collaboration

GitHub Releases attach versioned, downloadable artifacts and notes to a Git tag; GitHub Packages is a registry that hosts your built artifacts — npm, Docker, Maven, NuGet — next to the code. Together they cover the two halves of shipping a version: announcing it to humans and serving it to machines.

The three things involved — a tag, a release, and a package — are easy to conflate but do different jobs, and the most important rule across all of them is immutability: once a version is published, consumers pin to it, so re-pointing it later hands them different code than they audited.

Tags vs Releases

A Git tag is a pure pointer to a commit, with no assets. A release is metadata — notes and downloadable files — layered on top of that tag, with the tag serving as the immutable anchor. The tag is the contract; the release is the human-facing wrapper around it.

Release Notes

Notes can be written by hand, but GitHub auto-generates them from the pull requests merged since the previous tag, and a release.yml config groups those entries into categories based on PR labels. Hand-writing notes every time is wasted effort once labels and that config are in place.

Release Assets

A release can carry uploaded binaries and checksums as assets, served and download-counted by GitHub. These are the files a human clicks to download, distinct from a package a build tool resolves.

GitHub Packages

Packages is a registry supporting npm, the Container registry (ghcr.io), Maven, Gradle, NuGet, and RubyGems, with authentication by token. The Container registry is the notable one: publishing images to ghcr.io lets them inherit the repository's access control instead of requiring a separate credential for an external registry. Package visibility must be set deliberately, since defaults can leave an internal artifact public.

Automating Releases

Cutting a version should be one git push of a tag. A workflow triggered on on: push: tags can build the artifacts, create the release with an action like softprops/action-gh-release, and publish the package — tying the whole sequence to the release event so nothing is done by hand.

Git Tag vs GitHub Release vs GitHub Package

Git tag — a pure Git pointer to a commit, with no assets. Release — wraps a tag with notes and downloadable files for humans to read and grab.

Package — a machine-consumable artifact in a registry that npm install or docker pull resolves. For one version you often produce all three: tag the commit, cut a release with notes, and publish the package.

Common Mistakes
  • Moving or re-pointing a tag after a release — consumers who pinned that version now get different code than they audited.
  • Writing release notes by hand every time — GitHub auto-generates them from merged PRs once you configure labels and release.yml.
  • Publishing a Docker image to an external registry when ghcr.io would inherit repo permissions and avoid a separate credential.
  • Not setting package visibility — a package can default in ways that leave an internal artifact public.
  • Treating a pre-release as stable in automation — consumers' ^ ranges then pull a -rc tag they should not.
Best Practices
  • Configure auto-generated release notes with a release.yml so notes are built from PR labels, not hand-typed.
  • Tag immutably and never re-point a published tag; cut a new version instead.
  • Publish container images to ghcr.io to inherit repository access control rather than managing a separate registry credential.
  • Mark non-final builds as "pre-release" so semver ranges and download links treat them correctly.
  • Automate tag-triggered releases in Actions with on: push: tags so cutting a version is one git push.
Comparable toolsGitLab Releases plus Package and Container registriesBitbucket downloads plus ArtifactoryAzure DevOps Artifactsnpm / Docker Hub the registry side specifically

Knowledge Check

How do a tag, a release, and a package relate for one version?

  • The tag anchors the commit, the release wraps it with notes and files, and the package is the artifact a build tool resolves
  • They are simply three different names for one and the same underlying object stored in the repo
  • A package contains the tag, which in turn contains the release for that one version of the code, nesting all three inside one another in a strict hierarchy
  • A release entirely replaces and supersedes the tag once it has been published to consumers, leaving the tag with nothing left to point at

Why is re-pointing a published tag a problem?

  • Consumers who pinned that version now resolve to different code than they originally audited
  • GitHub permanently locks the entire repository the moment a previously published tag gets re-pointed
  • The release notes attached to that version are deleted automatically
  • Older clones can no longer fetch from the repository at all

Why publish a container image to ghcr.io rather than an external registry?

  • The image inherits the repository's access control, avoiding a separate registry credential to manage
  • External registries are not able to host Docker images at all, only ghcr.io can
  • Only ghcr.io supports version tags on its images, a capability that external registries entirely lack
  • It makes the image public by default, which is a hard requirement for any GitHub-hosted package

What does marking a build as "pre-release" protect against?

  • Consumers' semver ranges and "latest" download links treating an unfinished -rc build as stable
  • The release being deleted by a scheduled cleanup automation before it ever has a chance to ship
  • The underlying tag suddenly becoming mutable and re-pointable
  • The package registry rejecting the artifact upload outright

You got correct