Skip to content

Container images have never published: GHCR refuses the push #5763

Description

@norman-abramovitz

No release has ever published a container image. The v5.0.0 release run surfaced it, but it is not new and 5.0.0 did not cause it.

What this affects

The all-in-one image is Stratos packaged as a single container — a Debian base holding the compiled jetstream backend, the prebuilt UI, templates and certs. It exists so someone can run the console with one docker run, with no Cloud Foundry deployment and no building from source. The same image also works with cf push -o <image>: it detects VCAP_APPLICATION at runtime and serves HTTP on $PORT instead of HTTPS on 5443.

If it published, operators would get ghcr.io/cloudfoundry/stratos:5.0.0. Today the newest thing under that name is a single tag, stable, pushed by some earlier process that predates the current workflows.

The GitHub release itself is unaffected — v5.0.0 has its nine downloadable artifacts and correct notes. Only the container distribution channel is missing.

What happens

Build All-in-One Image builds the image successfully — all layers, manifest exported — and then fails on the upload:

#16 pushing layers 0.8s done
#16 ERROR: failed to push ghcr.io/cloudfoundry/stratos:5.0.0: denied: permission_denied: write_package

Step outcomes for that job show where the line falls:

Log in to GitHub Container Registry: success
Extract metadata:                    success
Build and push all-in-one image:     failure

Authentication succeeds. Authorization fails. It is not a missing or expired credential — a valid, logged-in token is being told it may not write that particular package.

It has never worked

The job was added on 2026-07-20 (48efb46493). It has run in exactly two releases and failed identically in both:

Release Date Result
v5.0.0-dev.147 2026-07-29 failure — denied: permission_denied
v5.0.0 2026-08-06 failure — denied: permission_denied

Every earlier release run predates the job, which is why they look clean. The same failure makes each release run report failure overall even though Create GitHub Release succeeded and the release is fine — so the red tick is misleading rather than informative.

This is not limited to releases. Every workflow that pushes to GHCR fails the same way:

Workflow Credential Recent result
container-push-base-images-develop.yaml GH_PACKAGES_USER / GH_PACKAGES_TOKEN steps skipped — secrets not set
release.yml (all-in-one) GITHUB_TOKEN failure x2
docker.yml GITHUB_TOKEN failure x3
ci-image.yml GITHUB_TOKEN failure

The one workflow written to use a personal access token skips silently: it checks whether the secrets exist, warns "Stratos containers are not supported at this time", and does nothing. It has been reporting green for a no-op. The repository's only configured secret is GH_PAGES_DEPLOY, from 2020.

Why the push is refused

On GitHub a container image is a package with its own access control, separate from the repository. release.yml correctly requests packages: write, but GITHUB_TOKEN is a per-run token belonging to the repository — it can write packages the repository owns, not one that already exists without the repository having been granted write on it. ghcr.io/cloudfoundry/stratos is public and predates these workflows, so the token has no standing on it.

The username in the login step (github.actor) is incidental; GHCR authorizes on the token, so triggering a release from a different account changes nothing.

Two ways to fix it

  1. Grant the cloudfoundry/stratos repository the Write role on the existing package, in the package's settings under Manage Actions access. No new credential, nothing to rotate. Requires admin on the package.
  2. Supply a personal access token with write:packages, belonging to an account that can write that package, and point the login steps at it. Works regardless of the package's Actions-access configuration, but adds a long-lived secret to own and rotate.

Either way the workflow logic is unchanged apart from which credential the login step uses.

Unresolved

Which of the two situations applies — package access not granted, or an organization policy restricting who may publish packages — cannot be determined without read:packages on the organization. Both produce this identical error.

Also worth deciding separately: container-push-base-images-develop.yaml targets ghcr.io/anynines/… rather than cloudfoundry, so it wants a different credential than the release image does. Reusing one secret name for both would point a token at a namespace it was not meant for.

Worth noting

The skip-with-a-warning guard in container-push-base-images-develop.yaml is the better pattern for an optional credential. The other three build for minutes and then die at the upload. Whatever is decided here, that difference is worth making consistent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions