GHCR 403 Forbidden

  1. Root Cause of the 403 Forbidden Error

  2. Manual Package Creation: The error occurs when a developer builds and pushes the initial container image manually from a local terminal using a Personal Access Token (PAT).

  3. Strict Account Ownership: When pushed manually, GitHub assigns package ownership strictly to the individual user profile (ritikkumar27) rather than associating it with a specific code repository.
  4. Automated Token Restriction: Even if the .github/workflows/deploy.yml pipeline specifies permissions: packages: write, GitHub's default security model protects user-owned packages. It prevents the repository's automated bot token (${{ secrets.GITHUB_TOKEN }}) from overwriting a user-owned package without explicit authorization, resulting in a 403 Forbidden build failure.

  1. Manual Pushes vs. GitHub Actions Linking

  2. Automated Actions Behavior: When an image is built and pushed directly by a GitHub Actions workflow, GitHub detects that the token originated from a specific repository and automatically links the resulting container package to that repository's sidebar dashboard.

  3. Manual Terminal Behavior: Pushing from a laptop terminal bypasses repository context, dropping the package under global profile packages in an unlinked state.
  4. The Dockerfile Metadata Solution: To force repository linkage during manual local pushes, developers can add a standard metadata label near the top of the Dockerfile:
  5. This explicit label instructs GHCR which repository owns the image regardless of where the push originated.

  1. Step-by-Step Resolution (Granting Pipeline Access)

To resolve the 403 Forbidden error and allow the CI/CD pipeline to push updates, package permissions must be adjusted in GitHub:

  1. Go to your GitHub Profile and click the Packages tab.
  2. Click on the astro_portfolio package.
  3. On the right-hand sidebar, select Package settings.
  4. Scroll to the Manage Actions access section and click Add Repository.
  5. Search for the astro_portfolio code repository and add it.
  6. Change the Access Role: In the dropdown next to the added repository, change the permission role from Read to Write or Admin.

Once this role is updated, subsequent workflow runs will succeed without permission rejection.


  1. Public vs. Private Visibility & Server Pull Permissions

In addition to push permissions, package visibility determines how the homelab server pulls the image during deployment:

  • Private Packages (Default): Newly created GHCR packages are private by default. To pull a private image during deployment (docker compose pull), the homelab server must first authenticate using a PAT with the read:packages scope via echo "$TOKEN" | docker login ghcr.io -u <username> --password-stdin.
  • Public Packages: If visibility is changed to Public under Package Settings, the homelab server can execute docker compose pull and docker compose up -d over the open network without requiring any server-side login credentials.