Grant the GITHUB_TOKEN the needed permissions on the job: permissions: contents: read packages: write (add attestations: write and id-token: write if also generating build attestations).
Check out the repo: - uses: actions/checkout
Log in to the Container registry with docker/login-action: set registry to ghcr.io, username to the github.actor context value, and the login token to the workflow's built-in GITHUB_TOKEN (referenced from the secrets context in the with: block). No long-lived credential is needed.
Extract tags/labels with docker/metadata-action: id: meta uses: docker/metadata-action with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
Build and push with docker/build-push-action using the metadata outputs: uses: docker/build-push-action with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
On first publish, add the org.opencontainers.image.source LABEL to your Dockerfile so the package connects to the repo and the GITHUB_TOKEN keeps push permission on later runs. The workflow's repository is linked automatically when it is the one that first publishes the package, and repository admins where the workflow runs become the package's admins.
If you previously pushed the package to the same namespace from the command line (e.g. with a PAT) and never connected it to a repository, the workflow's GITHUB_TOKEN will NOT have permission to push — you must add org.opencontainers.image.source to the Dockerfile or connect the package to the repo first.
When a package is first published by a workflow using GITHUB_TOKEN, the package inherits the visibility and access permissions of the repository running the workflow, and that repository's admins automatically become the package's admins.
For other (non-publishing) repositories that also need workflow access to the package, you must explicitly add them under 'Manage Actions access → Add repository' on the package's settings page — this is separate from 'connecting'/linking a repository to a package.
Only workflows running in repositories granted write permission on the package can upload new versions; only workflows in repositories granted admin permission can delete package versions (deleting/restoring via the REST API with GITHUB_TOKEN is noted as public preview and subject to change).
Registries that support granular permissions (Container registry included) are recommended to use GITHUB_TOKEN instead of a personal access token for authentication in workflows, to avoid unnecessarily broad, long-lived credentials.
Image names built from ${{ github.repository }} can contain uppercase letters and must be lowercased before use as a Docker reference (see the documented tr '[A-Z]' '[a-z]' pattern) or the build/push will fail.
If the package is private, only workflows running in repositories explicitly granted read permission can pull it; granting a public repository access to a private package can expose it to that repo's forks.
Give your agent this knowledge — and 17,000+ more routes
One MCP install gives any agent live access to the full route map across 5,900+ domains, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp
Need this verified for your stack — or a route we don't have yet?