{"id":78748,"date":"2026-09-26T05:32:03","date_gmt":"2026-09-26T05:32:03","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=78748"},"modified":"2026-09-26T05:32:07","modified_gmt":"2026-09-26T05:32:07","slug":"github-packages-essentials-2-hour-hands-on-tutorial","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/github-packages-essentials-2-hour-hands-on-tutorial\/","title":{"rendered":"GitHub Packages Essentials \u2014 2-Hour Hands-On Tutorial"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Last Verified:<\/strong>&nbsp;September 2026<br><strong>Scope:<\/strong>&nbsp;GitHub.com \/ GitHub Enterprise Cloud unless explicitly stated otherwise<br><strong>Duration:<\/strong>&nbsp;~2 hours including hands-on practice<br><strong>Audience:<\/strong>&nbsp;Developers, DevOps engineers, platform engineers, administrators, trainers, and students<br><strong>Primary hands-on registry:<\/strong>&nbsp;GitHub Container Registry (GHCR)<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Packages is GitHub&#8217;s package-hosting platform. It lets teams publish, store, secure, discover, and consume software packages and container images while keeping source code, CI\/CD, permissions, and package ownership close to the GitHub workflow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This Essentials tutorial intentionally does&nbsp;<strong>not<\/strong>&nbsp;attempt to cover every advanced capability. It focuses on the smallest set of concepts and practical skills a learner should understand before working with GitHub Packages in real projects.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">1. What You Should Be Able to Do After 2 Hours<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">By the end of this tutorial, you should be able to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Explain what GitHub Packages is and when to use it.<\/li>\n\n\n\n<li>Distinguish GitHub Packages from GitHub Releases and GitHub Actions artifacts.<\/li>\n\n\n\n<li>Identify the six main GitHub Packages registries.<\/li>\n\n\n\n<li>Understand granular packages vs repository-scoped packages.<\/li>\n\n\n\n<li>Explain package visibility and Read \/ Write \/ Admin permissions.<\/li>\n\n\n\n<li>Authenticate using PAT classic locally and\u00a0<code>GITHUB_TOKEN<\/code>\u00a0in GitHub Actions.<\/li>\n\n\n\n<li>Build, publish, and pull a container image from GHCR.<\/li>\n\n\n\n<li>Publish a container automatically using GitHub Actions.<\/li>\n\n\n\n<li>Understand cross-repository package consumption.<\/li>\n\n\n\n<li>Apply essential versioning, security, and troubleshooting practices.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">2. Recommended 2-Hour Learning Flow<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-right\" data-align=\"right\">Time<\/th><th class=\"has-text-align-left\" data-align=\"left\">Topic<\/th><th class=\"has-text-align-left\" data-align=\"left\">Outcome<\/th><\/tr><\/thead><tbody><tr><td class=\"has-text-align-right\" data-align=\"right\">0\u201310 min<\/td><td>What GitHub Packages is<\/td><td>Understand the problem it solves<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">10\u201325 min<\/td><td>Registries and package model<\/td><td>Know supported ecosystems and permission models<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">25\u201340 min<\/td><td>Authentication and permissions<\/td><td>Understand PAT classic,&nbsp;<code>GITHUB_TOKEN<\/code>, package roles<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">40\u201365 min<\/td><td>Hands-on GHCR<\/td><td>Build, push, verify, and pull an image<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">65\u201390 min<\/td><td>GitHub Actions publishing<\/td><td>Automate build-test-publish<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">90\u2013105 min<\/td><td>Cross-repository access<\/td><td>Understand producer\/consumer package access<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">105\u2013115 min<\/td><td>Security + versioning + troubleshooting<\/td><td>Learn production habits and common failures<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">115\u2013120 min<\/td><td>Knowledge check + cheat sheet<\/td><td>Reinforce essential commands and decisions<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 1 \u2014 Understand GitHub Packages<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3. What Is GitHub Packages?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A&nbsp;<strong>package<\/strong>&nbsp;is a reusable software artifact that another developer, application, build, or deployment can consume.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Docker \/ OCI container image<\/li>\n\n\n\n<li>npm package<\/li>\n\n\n\n<li>Maven artifact<\/li>\n\n\n\n<li>Gradle-published Java library<\/li>\n\n\n\n<li>NuGet package<\/li>\n\n\n\n<li>Ruby gem<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Packages provides registries for storing and distributing these artifacts.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Why do teams need a package registry?<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Source code alone is not what applications deploy or consume.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A typical delivery path looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">LR<\/span>\n    <span class=\"hljs-selector-tag\">A<\/span><span class=\"hljs-selector-attr\">&#91;Source Code]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">B<\/span><span class=\"hljs-selector-attr\">&#91;Build]<\/span>\n    <span class=\"hljs-selector-tag\">B<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">C<\/span><span class=\"hljs-selector-attr\">&#91;Test]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">D<\/span><span class=\"hljs-selector-attr\">&#91;Package]<\/span>\n    <span class=\"hljs-selector-tag\">D<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">E<\/span><span class=\"hljs-selector-attr\">&#91;Publish]<\/span>\n    <span class=\"hljs-selector-tag\">E<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">F<\/span><span class=\"hljs-selector-attr\">&#91;GitHub Packages]<\/span>\n    <span class=\"hljs-selector-tag\">F<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">G<\/span><span class=\"hljs-selector-attr\">&#91;Install \/ Pull]<\/span>\n    <span class=\"hljs-selector-tag\">G<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">H<\/span><span class=\"hljs-selector-attr\">&#91;Application \/ Deployment]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A registry sits between the&nbsp;<strong>producer<\/strong>&nbsp;and the&nbsp;<strong>consumer<\/strong>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Producer: creates and publishes a package.<\/li>\n\n\n\n<li>Registry: stores package versions.<\/li>\n\n\n\n<li>Consumer: installs or pulls a package.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Typical real-world example<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">A team builds a service called&nbsp;<code>payments-api<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of deploying directly from source code, CI builds a container image:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">ghcr.io\/acme\/payments-api:1.4.2\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That image is stored in GitHub Container Registry and later pulled by Kubernetes or another deployment platform.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">4. GitHub Packages vs Releases vs Actions Artifacts<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">These three GitHub features solve different problems.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Feature<\/th><th class=\"has-text-align-left\" data-align=\"left\">Primary purpose<\/th><th class=\"has-text-align-left\" data-align=\"left\">Best example<\/th><\/tr><\/thead><tbody><tr><td>GitHub Packages<\/td><td>Reusable dependencies and deployable packages<\/td><td><code>ghcr.io\/acme\/api:1.2.0<\/code><\/td><\/tr><tr><td>GitHub Releases<\/td><td>Release event, notes, downloadable assets<\/td><td><code>v1.2.0<\/code>&nbsp;release with binaries<\/td><\/tr><tr><td>GitHub Actions artifacts<\/td><td>Temporary workflow outputs<\/td><td>Test reports, build logs, intermediate files<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Simple rule<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<strong>GitHub Packages<\/strong>&nbsp;when another build, developer, application, or deployment will consume the artifact as a package.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<strong>GitHub Releases<\/strong>&nbsp;when publishing release notes and downloadable release assets.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<strong>Actions artifacts<\/strong>&nbsp;for files mainly associated with a workflow run.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 2 \u2014 Registries and Permission Models<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">5. Supported GitHub Packages Registries<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Packages supports six principal package ecosystems.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Ecosystem<\/th><th class=\"has-text-align-left\" data-align=\"left\">Endpoint<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical client<\/th><th class=\"has-text-align-left\" data-align=\"left\">Permission model<\/th><\/tr><\/thead><tbody><tr><td>Container<\/td><td><code>ghcr.io<\/code><\/td><td>Docker \/ OCI<\/td><td>Granular user\/org scoped<\/td><\/tr><tr><td>npm<\/td><td><code>npm.pkg.github.com<\/code><\/td><td>npm \/ Yarn<\/td><td>Granular user\/org scoped<\/td><\/tr><tr><td>NuGet<\/td><td><code>nuget.pkg.github.com<\/code><\/td><td><code>dotnet<\/code>&nbsp;\/ NuGet<\/td><td>Granular user\/org scoped<\/td><\/tr><tr><td>RubyGems<\/td><td><code>rubygems.pkg.github.com<\/code><\/td><td><code>gem<\/code>&nbsp;\/ Bundler<\/td><td>Granular user\/org scoped<\/td><\/tr><tr><td>Maven<\/td><td><code>maven.pkg.github.com<\/code><\/td><td>Maven<\/td><td>Repository scoped<\/td><\/tr><tr><td>Gradle<\/td><td>Maven-compatible endpoint<\/td><td>Gradle<\/td><td>Repository scoped<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The old Docker registry endpoint:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">docker<\/span><span class=\"hljs-selector-class\">.pkg<\/span><span class=\"hljs-selector-class\">.github<\/span><span class=\"hljs-selector-class\">.com<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">is legacy. New designs should use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ghcr<\/span><span class=\"hljs-selector-class\">.io<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">6. The Most Important Architecture Concept: Granular vs Repository-Scoped<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction changes how access control works.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">flowchart TD\n    A&#91;GitHub Package] --&gt; B{Permission model}\n    B --&gt;|Granular| C&#91;User or Organization Scope]\n    C --&gt; D&#91;Independent package permissions]\n    C --&gt; E&#91;Optional repository connection]\n    B --&gt;|Repository scoped| F&#91;Repository]\n    F --&gt; G&#91;Repository permissions]\n    F --&gt; H&#91;Repository visibility]\n<\/code><\/span><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Granular packages<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Supported by:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Container<\/li>\n\n\n\n<li>npm<\/li>\n\n\n\n<li>NuGet<\/li>\n\n\n\n<li>RubyGems<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These packages can have access rules independent of a repository.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A package can be owned by organization&nbsp;<code>acme<\/code>, while five different repositories receive read access.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Repository-scoped packages<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Supported by:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Maven<\/li>\n\n\n\n<li>Gradle<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These packages inherit the repository&#8217;s permissions and visibility.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Remember this<\/h4>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Container\/npm\/NuGet\/RubyGems = package-level access model.<br>Maven\/Gradle = repository permission model.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">This is one of the most common GitHub Packages design mistakes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">7. Package Visibility and Roles<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Package roles<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Role<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical capability<\/th><\/tr><\/thead><tbody><tr><td>Read<\/td><td>View metadata and download\/install<\/td><\/tr><tr><td>Write<\/td><td>Read + publish\/upload<\/td><\/tr><tr><td>Admin<\/td><td>Manage package, access, versions, deletion<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Visibility<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Depending on registry and account context, packages may be:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Public<\/li>\n\n\n\n<li>Private<\/li>\n\n\n\n<li>Internal<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For proprietary company packages, a safe starting point is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">Private<\/span>\/Internal -&gt; explicitly grant access -&gt; make <span class=\"hljs-keyword\">public<\/span> only after review\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Important GHCR exception<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Public GHCR container images can be pulled anonymously.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Most other GitHub Packages package-client workflows still require authentication even when a package is public.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 3 \u2014 Authentication<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">8. PAT Classic vs&nbsp;<code>GITHUB_TOKEN<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Authentication is where many first-time GitHub Packages users get confused.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Local package-client access<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">For package clients, GitHub documents&nbsp;<strong>personal access token (classic)<\/strong>&nbsp;authentication.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Important scopes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Scope<\/th><th class=\"has-text-align-left\" data-align=\"left\">Use<\/th><\/tr><\/thead><tbody><tr><td><code>read:packages<\/code><\/td><td>Download\/install<\/td><\/tr><tr><td><code>write:packages<\/code><\/td><td>Publish\/upload<\/td><\/tr><tr><td><code>delete:packages<\/code><\/td><td>Delete package\/version<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Example local GHCR login:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> CR_PAT=<span class=\"hljs-string\">\"YOUR_PAT_CLASSIC\"<\/span>\n\necho <span class=\"hljs-string\">\"$CR_PAT\"<\/span> | docker login ghcr.io \\\n  -u YOUR_GITHUB_USERNAME \\\n  --password-stdin\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not hardcode the token inside scripts committed to Git.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Inside GitHub Actions<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer&nbsp;<code>GITHUB_TOKEN<\/code>&nbsp;whenever the package access model supports the operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Read-only workflow:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n  packages: read\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Publishing workflow:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n  packages: write\n<\/code><\/span><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Why&nbsp;<code>GITHUB_TOKEN<\/code>&nbsp;is preferred in Actions<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">It is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Automatically generated<\/li>\n\n\n\n<li>Short lived<\/li>\n\n\n\n<li>Repository\/workflow scoped<\/li>\n\n\n\n<li>Permission controllable<\/li>\n\n\n\n<li>Easier to audit than a shared long-lived PAT<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Security rule<\/h4>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Local package client -&gt; PAT classic when required\nGitHub Actions -&gt; GITHUB_TOKEN whenever possible\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 4 \u2014 Hands-On: Publish a Container to GHCR<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">9. Lab 1 \u2014 Build and Publish Manually<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Objective<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Publish a versioned Docker image to GitHub Container Registry and pull it back successfully.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Prerequisites<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">You need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>GitHub account<\/li>\n\n\n\n<li>GitHub repository<\/li>\n\n\n\n<li>Docker<\/li>\n\n\n\n<li>Permission to publish to the target namespace<\/li>\n\n\n\n<li>PAT classic with appropriate package scope<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For examples below, replace:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">acme\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">with your GitHub username or organization.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 1 \u2014 Create a simple application<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Create&nbsp;<code>index.html<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Hello from GitHub Packages<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Create&nbsp;<code>Dockerfile<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">FROM nginx:alpine\n\nCOPY .\/index.html \/usr\/share\/nginx\/html\/index.html\n\nLABEL org.opencontainers.image.source=<span class=\"hljs-string\">\"https:\/\/github.com\/acme\/hello-packages\"<\/span>\nLABEL org.opencontainers.image.description=<span class=\"hljs-string\">\"GitHub Packages Essentials demo\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The source label helps GitHub associate the image with its source repository.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 2 \u2014 Authenticate to GHCR<\/h4>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> CR_PAT=<span class=\"hljs-string\">\"YOUR_PAT_CLASSIC\"<\/span>\n\necho <span class=\"hljs-string\">\"$CR_PAT\"<\/span> | docker login ghcr.io \\\n  -u YOUR_GITHUB_USERNAME \\\n  --password-stdin\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Expected result:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Login Succeeded\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 3 \u2014 Build the image<\/h4>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">docker<\/span> <span class=\"hljs-selector-tag\">build<\/span> <span class=\"hljs-selector-tag\">-t<\/span> <span class=\"hljs-selector-tag\">hello-packages<\/span><span class=\"hljs-selector-pseudo\">:1.0.0<\/span> .\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Verify locally:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker images\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 4 \u2014 Tag the image for GHCR<\/h4>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker tag hello-packages:1.0.0 \\\n  ghcr.io\/acme\/hello-packages:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Image naming pattern:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">ghcr.io\/OWNER\/IMAGE:TAG\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 5 \u2014 Push the image<\/h4>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker push ghcr.io\/acme\/hello-packages:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Expected result:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The image layers are uploaded and GitHub creates or updates the package.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 6 \u2014 Verify in GitHub<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Open the owner account or organization and navigate to&nbsp;<strong>Packages<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Package exists<\/li>\n\n\n\n<li>Version\u00a0<code>1.0.0<\/code>\u00a0exists<\/li>\n\n\n\n<li>Source repository is correct<\/li>\n\n\n\n<li>Visibility is correct<\/li>\n\n\n\n<li>Package metadata is visible<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Step 7 \u2014 Pull the image<\/h4>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker pull ghcr.io\/acme\/hello-packages:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run it:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker run --rm -p 8080:80 \\\n  ghcr.io\/acme\/hello-packages:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Open:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">http:<span class=\"hljs-comment\">\/\/localhost:8080<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Expected result:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Hello <span class=\"hljs-keyword\">from<\/span> GitHub Packages\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">10. Tag vs Digest<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A container can be referenced using a tag:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">ghcr.io\/acme\/hello-packages:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">or an immutable digest:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">ghcr.io\/acme\/hello-packages@sha256:...\n<\/code><\/span><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Why digest matters<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Tags are human-friendly references. A digest identifies exact content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For production deployments, digest pinning gives the strongest guarantee that the artifact being deployed is exactly the artifact that was tested.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Recommended release tags<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">A mature image may have:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">2<span class=\"hljs-selector-class\">.4<\/span><span class=\"hljs-selector-class\">.1<\/span>\n2<span class=\"hljs-selector-class\">.4<\/span>\n2\n<span class=\"hljs-selector-tag\">sha-a1b2c3d<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Avoid using only:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">latest\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">for production.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 5 \u2014 Automate Publishing with GitHub Actions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">11. Why Move Publishing into CI?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Manual publishing is useful for learning, but production publishing should normally happen in CI.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Benefits:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Reproducible build<\/li>\n\n\n\n<li>Automated testing<\/li>\n\n\n\n<li>Centralized credentials<\/li>\n\n\n\n<li>Audit trail<\/li>\n\n\n\n<li>Controlled permissions<\/li>\n\n\n\n<li>Easier provenance and security controls<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended lifecycle:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">LR<\/span>\n    <span class=\"hljs-selector-tag\">A<\/span><span class=\"hljs-selector-attr\">&#91;Commit \/ Release]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">B<\/span><span class=\"hljs-selector-attr\">&#91;Checkout]<\/span>\n    <span class=\"hljs-selector-tag\">B<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">C<\/span><span class=\"hljs-selector-attr\">&#91;Build]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">D<\/span><span class=\"hljs-selector-attr\">&#91;Test]<\/span>\n    <span class=\"hljs-selector-tag\">D<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">E<\/span><span class=\"hljs-selector-attr\">&#91;Login]<\/span>\n    <span class=\"hljs-selector-tag\">E<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">F<\/span><span class=\"hljs-selector-attr\">&#91;Publish]<\/span>\n    <span class=\"hljs-selector-tag\">F<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">G<\/span><span class=\"hljs-selector-attr\">&#91;Verify]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">12. Lab 2 \u2014 Publish GHCR Image Using GitHub Actions<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.github\/workflows\/publish.yml\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">name<\/span>: Publish container\n\non:\n  release:\n    types: &#91;published]\n\npermissions:\n  contents: read\n  packages: write\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v6\n\n      - name: Login to GHCR\n        uses: docker\/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build image\n        run: |\n          docker build \\\n            -t ghcr.io\/${{ github.repository }}:${{ github.event.release.tag_name }} \\\n            .\n\n      - name: Push image\n        run: |\n          docker push \\\n            ghcr.io\/${{ github.repository }}:${{ github.event.release.tag_name }}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">What this workflow does<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Starts when a GitHub Release is published.<\/li>\n\n\n\n<li>Checks out the source code.<\/li>\n\n\n\n<li>Authenticates to GHCR using\u00a0<code>GITHUB_TOKEN<\/code>.<\/li>\n\n\n\n<li>Builds the container.<\/li>\n\n\n\n<li>Tags it using the release tag.<\/li>\n\n\n\n<li>Publishes it to GHCR.<\/li>\n<\/ol>\n\n\n\n<h4 class=\"wp-block-heading\">Why the permissions block matters<\/h4>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n  packages: write\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The workflow receives only the access it needs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not assume the default token permissions are sufficient.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">13. Better Production Pattern: Test Before Publish<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Publishing should depend on successful tests.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">jobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v6\n      - run: .\/scripts\/test.sh\n\n  publish:\n    needs: test\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - uses: actions\/checkout@v6\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># login, build, and publish steps<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The key line is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">needs<\/span>: test\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If testing fails, publishing does not start.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Essential release rule<\/h4>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Build -&gt; Test -&gt; Package -&gt; Publish\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">not:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Build -&gt; Publish -&gt; Hope tests pass\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 6 \u2014 Cross-Repository Package Access<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">14. Producer and Consumer Repositories<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Real organizations often separate the repository that creates a package from the repositories that consume it.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">LR<\/span>\n    <span class=\"hljs-selector-tag\">A<\/span><span class=\"hljs-selector-attr\">&#91;Producer Repo]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">B<\/span><span class=\"hljs-selector-attr\">&#91;GitHub Actions]<\/span>\n    <span class=\"hljs-selector-tag\">B<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">C<\/span><span class=\"hljs-selector-attr\">&#91;Organization Package]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">D<\/span><span class=\"hljs-selector-attr\">&#91;Consumer Repo A]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">E<\/span><span class=\"hljs-selector-attr\">&#91;Consumer Repo B]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">F<\/span><span class=\"hljs-selector-attr\">&#91;Consumer Repo C]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">shared-types repo\n        |\n        v\n@acme\/shared-types\n        |\n   +----+----+\n   |         |\npayments   orders\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For granular packages, GitHub lets you grant selected repositories access through package settings.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Typical cross-repository flow<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Publish the package from repository A.<\/li>\n\n\n\n<li>Open the package settings.<\/li>\n\n\n\n<li>Find\u00a0<strong>Manage Actions access<\/strong>.<\/li>\n\n\n\n<li>Add repository B.<\/li>\n\n\n\n<li>Grant only the required role.<\/li>\n\n\n\n<li>In repository B, configure:<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n  packages: read\n<\/code><\/span><\/pre>\n\n\n<ol start=\"7\" class=\"wp-block-list\">\n<li>Consume the package using\u00a0<code>GITHUB_TOKEN<\/code>.<\/li>\n<\/ol>\n\n\n\n<h4 class=\"wp-block-heading\">Critical concept<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Token scope alone is not enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A workflow can have:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">packages<\/span>: read\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">and still fail if the repository itself has not been granted access to the package.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Think of authorization as two layers:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Workflow token permission\n        +\nPackage\/repository access\n        =\nSuccessful package operation\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 7 \u2014 Understand the Other Registries<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">15. Essential Ecosystem Examples<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You do not need to master every registry during this two-hour session. You should understand the configuration pattern.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">npm<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Package name:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"@acme\/shared-utils\"<\/span>,\n  <span class=\"hljs-attr\">\"version\"<\/span>: <span class=\"hljs-string\">\"1.2.0\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>.npmrc<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">@acme:registry=https:<span class=\"hljs-comment\">\/\/npm.pkg.github.com<\/span>\n<span class=\"hljs-comment\">\/\/npm.pkg.github.com\/:_authToken=${NODE_AUTH_TOKEN}<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Publish:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">npm publish\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Install:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-keyword\">@acme<\/span>\/shared-utils<span class=\"hljs-keyword\">@1<\/span>.2.0\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Maven<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Endpoint pattern:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">https:<span class=\"hljs-comment\">\/\/maven.pkg.github.com\/OWNER\/REPOSITORY<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Publish:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">mvn --batch-mode deploy\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Important: Maven packages are&nbsp;<strong>repository scoped<\/strong>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Gradle<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Publish:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.\/gradlew publish\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Important: GitHub Gradle packages use Maven-format publishing and are&nbsp;<strong>repository scoped<\/strong>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">NuGet<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Add source:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">dotnet nuget add source \\\n  --name github \\\n  <span class=\"hljs-string\">\"https:\/\/nuget.pkg.github.com\/acme\/index.json\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Publish:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">dotnet<\/span> <span class=\"hljs-selector-tag\">nuget<\/span> <span class=\"hljs-selector-tag\">push<\/span> <span class=\"hljs-selector-tag\">PACKAGE<\/span><span class=\"hljs-selector-class\">.nupkg<\/span> \\\n  <span class=\"hljs-selector-tag\">--source<\/span> <span class=\"hljs-selector-tag\">github<\/span> \\\n  <span class=\"hljs-selector-tag\">--api-key<\/span> <span class=\"hljs-selector-tag\">YOUR_PAT_CLASSIC<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">RubyGems<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Registry pattern:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">https:<span class=\"hljs-comment\">\/\/rubygems.pkg.github.com\/NAMESPACE<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Build:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">gem<\/span> <span class=\"hljs-selector-tag\">build<\/span> <span class=\"hljs-selector-tag\">package<\/span><span class=\"hljs-selector-class\">.gemspec<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Publish:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">gem push \\\n  --host https:<span class=\"hljs-comment\">\/\/rubygems.pkg.github.com\/acme \\<\/span>\n  package.gem\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 8 \u2014 Essential Security and Release Practices<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">16. The 10 Rules Worth Remembering<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Prefer organization ownership for company packages.<\/li>\n\n\n\n<li>Prefer\u00a0<code>GITHUB_TOKEN<\/code>\u00a0instead of long-lived PATs in Actions.<\/li>\n\n\n\n<li>Use minimum token permissions.<\/li>\n\n\n\n<li>Publish production packages from trusted CI.<\/li>\n\n\n\n<li>Test before publishing.<\/li>\n\n\n\n<li>Do not reuse release version numbers.<\/li>\n\n\n\n<li>Use SemVer for libraries where appropriate.<\/li>\n\n\n\n<li>Use immutable container digests for production deployment.<\/li>\n\n\n\n<li>Do not store credentials in repository files.<\/li>\n\n\n\n<li>Grant consumers Read access, not Write\/Admin access.<\/li>\n<\/ol>\n\n\n\n<h4 class=\"wp-block-heading\">Good vs risky<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Risky<\/th><th class=\"has-text-align-left\" data-align=\"left\">Better<\/th><\/tr><\/thead><tbody><tr><td>Shared PAT<\/td><td>Individual auth or&nbsp;<code>GITHUB_TOKEN<\/code><\/td><\/tr><tr><td>Admin token for downloads<\/td><td>Read-only access<\/td><\/tr><tr><td>Publish manually from laptop<\/td><td>Controlled CI publishing<\/td><\/tr><tr><td>Deploy&nbsp;<code>latest<\/code><\/td><td>Deploy version + digest<\/td><\/tr><tr><td>Rebuild for each environment<\/td><td>Build once, promote same artifact<\/td><\/tr><tr><td>Unlimited dev versions<\/td><td>Retention\/cleanup policy<\/td><\/tr><tr><td>Package with no source link<\/td><td>Connect package to source repository<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">17. Build Once, Deploy Many<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A mature delivery process promotes the same artifact through environments.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">LR<\/span>\n    <span class=\"hljs-selector-tag\">A<\/span><span class=\"hljs-selector-attr\">&#91;Commit]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">B<\/span><span class=\"hljs-selector-attr\">&#91;Build + Test]<\/span>\n    <span class=\"hljs-selector-tag\">B<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">C<\/span><span class=\"hljs-selector-attr\">&#91;Immutable Package]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">D<\/span><span class=\"hljs-selector-attr\">&#91;Dev]<\/span>\n    <span class=\"hljs-selector-tag\">D<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">E<\/span><span class=\"hljs-selector-attr\">&#91;Stage]<\/span>\n    <span class=\"hljs-selector-tag\">E<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">F<\/span><span class=\"hljs-selector-attr\">&#91;Production]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not rebuild independently for staging and production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Why?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because two builds from the same Git commit can still contain different dependency resolutions, timestamps, base image layers, or build-environment effects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For containers, promote the same digest.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Good:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">ghcr.io\/acme\/api@sha256:abc123...\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Riskier:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">ghcr.io\/acme\/api:latest\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 9 \u2014 Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">18. Fast Troubleshooting Matrix<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Problem<\/th><th class=\"has-text-align-left\" data-align=\"left\">Likely cause<\/th><th class=\"has-text-align-left\" data-align=\"left\">Check<\/th><\/tr><\/thead><tbody><tr><td><code>401 Unauthorized<\/code><\/td><td>Invalid\/expired credential<\/td><td>Token, login, SSO authorization<\/td><\/tr><tr><td>Pull\/install denied<\/td><td>Missing read access<\/td><td>Package ACL +&nbsp;<code>packages: read<\/code><\/td><\/tr><tr><td>Push denied<\/td><td>Missing write access<\/td><td>Package role +&nbsp;<code>packages: write<\/code><\/td><\/tr><tr><td>Works locally, fails in Actions<\/td><td>Workflow token too restricted<\/td><td><code>permissions:<\/code>&nbsp;block<\/td><\/tr><tr><td>Cross-repo package not found<\/td><td>Consumer repo not granted access<\/td><td>Manage Actions access<\/td><\/tr><tr><td>Image tag not found<\/td><td>Wrong tag or owner<\/td><td>Package page and image reference<\/td><\/tr><tr><td><code>exec format error<\/code><\/td><td>Architecture mismatch<\/td><td>Image platforms<\/td><\/tr><tr><td>Version already exists<\/td><td>Immutable version collision<\/td><td>Increment version<\/td><\/tr><tr><td>npm publishes to npmjs.org<\/td><td>Registry config incorrect<\/td><td><code>.npmrc<\/code>&nbsp;\/ package config<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Troubleshooting order<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">When package access fails, check in this order:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">1. Correct registry endpoint?\n2. Correct package name\/version?\n3. Authentication working?\n4. Token has required scope\/permission?\n5. User\/repository has package access?\n6. Workflow permissions correct?\n7. Cross-repository access configured?\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This sequence prevents random trial-and-error debugging.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 10 \u2014 Five-Minute Final Practice<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">19. Mini Exercise<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Assume:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Organization:\u00a0<code>acme<\/code><\/li>\n\n\n\n<li>Producer repository:\u00a0<code>payments-api<\/code><\/li>\n\n\n\n<li>Package:\u00a0<code>ghcr.io\/acme\/payments-api<\/code><\/li>\n\n\n\n<li>Consumer repository:\u00a0<code>deployment-platform<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Answer the following before checking the solution.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Questions<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Which registry stores the image?<\/li>\n\n\n\n<li>Is GHCR granular or repository scoped?<\/li>\n\n\n\n<li>What local token type should you use for Docker authentication?<\/li>\n\n\n\n<li>What workflow permission is required to publish?<\/li>\n\n\n\n<li>What workflow permission is required only to pull?<\/li>\n\n\n\n<li>What else must be configured when another repository consumes a private package?<\/li>\n\n\n\n<li>Which is safer for production:\u00a0<code>:latest<\/code>\u00a0or\u00a0<code>@sha256:...<\/code>?<\/li>\n\n\n\n<li>Should production rebuild the image after staging approval?<\/li>\n<\/ol>\n\n\n\n<h4 class=\"wp-block-heading\">Answers<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li>GitHub Container Registry \/\u00a0<code>ghcr.io<\/code>.<\/li>\n\n\n\n<li>Granular user\/organization scoped.<\/li>\n\n\n\n<li>PAT classic with the required package scope.<\/li>\n\n\n\n<li><code>packages: write<\/code>.<\/li>\n\n\n\n<li><code>packages: read<\/code>.<\/li>\n\n\n\n<li>Grant the consumer repository package\/Actions access.<\/li>\n\n\n\n<li>Immutable digest:\u00a0<code>@sha256:...<\/code>.<\/li>\n\n\n\n<li>No. Promote the same tested artifact.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 11 \u2014 Essential Cheat Sheet<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">20. Registry Endpoints<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Registry<\/th><th class=\"has-text-align-left\" data-align=\"left\">Endpoint<\/th><\/tr><\/thead><tbody><tr><td>Container<\/td><td><code>ghcr.io\/OWNER\/IMAGE<\/code><\/td><\/tr><tr><td>npm<\/td><td><code>https:\/\/npm.pkg.github.com<\/code><\/td><\/tr><tr><td>Maven<\/td><td><code>https:\/\/maven.pkg.github.com\/OWNER\/REPOSITORY<\/code><\/td><\/tr><tr><td>Gradle<\/td><td>Maven-compatible GitHub Packages endpoint<\/td><\/tr><tr><td>NuGet<\/td><td><code>https:\/\/nuget.pkg.github.com\/NAMESPACE\/index.json<\/code><\/td><\/tr><tr><td>RubyGems<\/td><td><code>https:\/\/rubygems.pkg.github.com\/NAMESPACE<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">21. PAT Classic Scopes<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">read<\/span><span class=\"hljs-selector-pseudo\">:packages<\/span>\n<span class=\"hljs-selector-tag\">write<\/span><span class=\"hljs-selector-pseudo\">:packages<\/span>\n<span class=\"hljs-selector-tag\">delete<\/span><span class=\"hljs-selector-pseudo\">:packages<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">22. Workflow Permissions<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Read:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n  packages: read\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Publish:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n  packages: write\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">23. GHCR Commands<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Login:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"$CR_PAT\"<\/span> | docker login ghcr.io \\\n  -u <span class=\"hljs-string\">\"$GITHUB_USER\"<\/span> \\\n  --password-stdin\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Build:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker build -t ghcr.io\/acme\/api:1.0.0 .\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Push:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker push ghcr.io\/acme\/api:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Pull:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker pull ghcr.io\/acme\/api:1.0.0\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Pull immutable digest:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker pull ghcr.io\/acme\/api@sha256:REPLACE_WITH_DIGEST\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 12 \u2014 What to Learn Next<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">24. Topics Intentionally Left for the Complete Guide<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After completing this Essentials tutorial, move to the full reference guide for deeper study of:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Package deletion and restoration<\/li>\n\n\n\n<li>REST API and\u00a0<code>gh api<\/code><\/li>\n\n\n\n<li>Webhooks<\/li>\n\n\n\n<li>GitHub Apps<\/li>\n\n\n\n<li>Billing and storage management<\/li>\n\n\n\n<li>Multi-architecture builds<\/li>\n\n\n\n<li>Automated cleanup<\/li>\n\n\n\n<li>Registry migration<\/li>\n\n\n\n<li>Legacy Docker registry migration<\/li>\n\n\n\n<li>Artifact attestations<\/li>\n\n\n\n<li>SBOM<\/li>\n\n\n\n<li>Dependency graph and Dependabot<\/li>\n\n\n\n<li>Linked artifacts<\/li>\n\n\n\n<li>Enterprise-specific behavior<\/li>\n\n\n\n<li>Organization governance<\/li>\n\n\n\n<li>Audit and usage reporting<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These are important topics, but they are better studied after the package lifecycle, authentication model, permissions, GHCR workflow, and CI publishing process are understood.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Part 13 \u2014 Wrap-Up<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">25. Final Mental Model<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you remember only one flow from this tutorial, remember this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">LR<\/span>\n    <span class=\"hljs-selector-tag\">A<\/span><span class=\"hljs-selector-attr\">&#91;Source]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">B<\/span><span class=\"hljs-selector-attr\">&#91;Build]<\/span>\n    <span class=\"hljs-selector-tag\">B<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">C<\/span><span class=\"hljs-selector-attr\">&#91;Test]<\/span>\n    <span class=\"hljs-selector-tag\">C<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">D<\/span><span class=\"hljs-selector-attr\">&#91;Package]<\/span>\n    <span class=\"hljs-selector-tag\">D<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">E<\/span><span class=\"hljs-selector-attr\">&#91;Publish with least privilege]<\/span>\n    <span class=\"hljs-selector-tag\">E<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">F<\/span><span class=\"hljs-selector-attr\">&#91;GitHub Packages]<\/span>\n    <span class=\"hljs-selector-tag\">F<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">G<\/span><span class=\"hljs-selector-attr\">&#91;Authorized Consumer]<\/span>\n    <span class=\"hljs-selector-tag\">G<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">H<\/span><span class=\"hljs-selector-attr\">&#91;Deploy exact version\/digest]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">And remember these four rules:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-number\">1.<\/span> Know the registry permission model.\n<span class=\"hljs-number\">2.<\/span> Use the smallest required permission.\n<span class=\"hljs-number\">3.<\/span> Publish <span class=\"hljs-keyword\">from<\/span> trusted CI.\n<span class=\"hljs-number\">4.<\/span> Promote immutable artifacts.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If those four ideas are clear, you have the foundation needed to work safely and effectively with GitHub Packages.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">26. Two-Hour Completion Checklist<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before considering the Essentials session complete, verify that the student can answer&nbsp;<strong>Yes<\/strong>&nbsp;to the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] I can explain what GitHub Packages is.<\/li>\n\n\n\n<li>[ ] I know when to use Packages vs Releases vs Actions artifacts.<\/li>\n\n\n\n<li>[ ] I know the six supported package ecosystems.<\/li>\n\n\n\n<li>[ ] I know which registries are granular and which are repository scoped.<\/li>\n\n\n\n<li>[ ] I understand Read \/ Write \/ Admin package roles.<\/li>\n\n\n\n<li>[ ] I understand PAT classic vs\u00a0<code>GITHUB_TOKEN<\/code>.<\/li>\n\n\n\n<li>[ ] I can log in to GHCR.<\/li>\n\n\n\n<li>[ ] I can build, tag, push, and pull an image.<\/li>\n\n\n\n<li>[ ] I can publish an image using GitHub Actions.<\/li>\n\n\n\n<li>[ ] I understand cross-repository package access.<\/li>\n\n\n\n<li>[ ] I understand why production should use immutable versions\/digests.<\/li>\n\n\n\n<li>[ ] I know the first checks to make when authentication or authorization fails.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">27. Source<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This Essentials tutorial is a condensed learning path derived from the&nbsp;<strong>GitHub Packages \u2014 Complete Reference Guide &amp; Hands-On Tutorial<\/strong>, last verified September 2026. It intentionally prioritizes foundational concepts and hands-on skills suitable for a two-hour study\/practice session rather than reproducing the complete advanced reference.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last Verified:&nbsp;September 2026Scope:&nbsp;GitHub.com \/ GitHub Enterprise Cloud unless explicitly stated otherwiseDuration:&nbsp;~2 hours including hands-on practiceAudience:&nbsp;Developers, DevOps engineers, platform engineers, administrators, trainers, and studentsPrimary hands-on registry:&nbsp;GitHub Container Registry&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-78748","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78748","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=78748"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78748\/revisions"}],"predecessor-version":[{"id":78749,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78748\/revisions\/78749"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=78748"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=78748"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=78748"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}