Skip to content

docs(docker): correct the container quick start for the private image - #57

Merged
zhanghanduo merged 2 commits into
ApodexAI:mainfrom
Yi-111-a:docs-private-image-quickstart
Oct 2, 2026
Merged

zhanghanduo merged 2 commits into
ApodexAI:mainfrom
Yi-111-a:docs-private-image-quickstart

Conversation

@Yi-111-a

@Yi-111-a Yi-111-a commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #47

Problem

ghcr.io/apodexai/frontieragent is private by org policy β€” .github/workflows/docker-publish.yml says so explicitly:

The package is private by org policy, so this pull relies on the ghcr.io login established earlier in the job. Do not add an anonymous-pull check here: it cannot succeed.

docs/install/global-install.md says so too. But the pages a new user lands on first promise a build-free pull. On a clean machine with no ghcr.io login:

$ git clone https://github.com/ApodexAI/FrontierAgent && cd FrontierAgent
$ cp .env.example .env
$ docker compose run --rm agent
Image ghcr.io/apodexai/frontieragent:latest Error error from registry: unauthorized

I confirmed the package is still private against the live registry (not just taking the report's word for it):

$ curl -s "https://ghcr.io/token?scope=repository:apodexai/frontieragent:pull&service=ghcr.io"
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}

$ curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOK" \
    "https://ghcr.io/v2/apodexai/frontieragent/tags/list"
403

Three places carried the wrong promise:

  • README.md β€” "Containers and local models" told you to run docker compose run --rm agent
  • docs/install/docker.md β€” "does not build the repository locally", "Pull and launch the pre-built container"
  • docs/install/README.md β€” the chooser row advertised the "published agent container"

What this changes

Docs only. Each quick start now names the two ways to start: docker login ghcr.io if your account is authorized for the package, or build this checkout.

The build path keeps the compose.dev.yaml override on every command, not just the build. That detail is load-bearing: compose.yaml sets pull_policy: always and compose.dev.yaml sets pull_policy: build, so running without the override makes Compose re-fetch the private registry and fail again even though the image is right there. docker/run.sh invokes compose.yaml alone, so it now says so explicitly.

The same wart shows up in "Pin a release or another image": that section says any image name works, including one you built locally, but compose.yaml still sets pull_policy: always, so a local-only tag would be resolved against a registry. That section now carries its own example that opts out with --pull never, so a locally built tag is usable without editing the compose files.

I also dropped pull_policy: always β†’ missing on compose.yaml in a first pass, then reverted it: apodex/tests/test_deployment_config.py::test_default_compose_pulls_release_image_and_preserves_cli_state asserts pull_policy == "always" deliberately, and that looks like an intentional contract rather than an oversight. It is a real wart β€” missing would make a local build usable without the override on every command β€” so if maintainers want that behaviour change, I'd rather it be a separate PR against a deliberate decision. Happy to split it out on request.

Test

tests/test_container_image_docs.py pins the fix, following the existing deployment-config test idiom:

  • 3 Γ— each quick-start doc states the image is private
  • 3 Γ— the unreachable pre-fix claims are gone
  • a block that builds locally must not then run without the override
  • the README run command must carry the override
  • a locally built tag run through Compose must opt out with --pull never

Verified it fails before and passes after:

$ git stash push -- README.md docs/install/README.md docs/install/docker.md
$ python -m pytest tests/test_container_image_docs.py -q
5 failed, 3 passed in 0.28s

$ git stash pop
$ python -m pytest tests/test_container_image_docs.py -q
8 passed in 0.59s

Testing

  • pytest tests/test_container_image_docs.py β€” 8 passed
  • ruff check + ruff format --check on the new file β€” clean
  • pytest apodex/tests/test_deployment_config.py β€” 17 passed (the compose contract is untouched)
  • Full suite, before vs. after: identical 108 pre-existing failures/errors, no new ones. They are environmental in this sandbox (ModuleNotFoundError: agent_core / textual / pypdf, and OSError from the 99%-full disk); I diffed the failure lists with and without this branch to confirm.

I did not run the container itself β€” no Docker daemon in my sandbox, so docker compose build/run are unexercised. The compose invocation shapes are copied from the commands already documented in docs/install/docker.md and pinned by the existing dev-override tests.

The published `ghcr.io/apodexai/frontieragent` package is private by org
policy, so an anonymous pull fails with `unauthorized`. README.md,
docs/install/docker.md and the chooser table all promised a build-free
`docker compose run --rm agent`, which sends anyone outside the org to
that error on their first command.

docs/install/global-install.md and the comment in the publish workflow
already state the privacy, so this only aligns the pages a new user lands
on first.

Because compose.yaml sets `pull_policy: always`, the local-build path has
to keep the `compose.dev.yaml` override on every command β€” its
`pull_policy: build` is what keeps the built image in use instead of
retrying the registry. That is now spelled out in each quick start.

Closes ApodexAI#47

tests/test_container_image_docs.py pins this: 5 of its 8 cases fail on
the pre-fix tree. The other 3 are guard rails (a dev override exists, and
no block builds locally then runs without the override).
@zhanghanduo

Copy link
Copy Markdown
Collaborator

Thanks for catching this and putting together the fix! Our org currently doesn’t allow us to make the GHCR image public, so documenting the private image and giving users a local-build path makes sense. I also appreciate you keeping the dev override on the run commands β€” that’s an easy detail to miss.

I reviewed the changes and there’s one thing I think we should fix before merging: in β€œPin a release or another image”, the new text says users can use a locally built tag, but the command above still uses pull_policy: always. That means Compose will try to pull the tag from a registry even if it already exists locally. Could you add a separate example for local tags, like this?

FRONTIER_AGENT_IMAGE=frontier-agent:local \
  docker compose run --pull never --rm agent

One tiny wording fix too: the β€œDirect docker run” section says the image below is the private published image, but the example now uses frontier-agent:local.

Otherwise, this looks good to me. I ran the new docs tests and the deployment-config tests locally; all 25 passed. Thanks again for making the setup clearer for people outside the org!

Two follow-ups to the review on ApodexAI#57.

"Pin a release or another image" says any image name works, including one you
built and tagged yourself, but the command above it still runs against
compose.yaml, which sets pull_policy: always. A tag that exists only on the
machine would still be resolved against a registry. That section now has its
own example that opts out:

FRONTIER_AGENT_IMAGE=frontier-agent:local \
  docker compose run --pull never --rm agent

The "Direct docker run" section called the reference below the private
published image while the command had been changed to frontier-agent:local. It
now says which tag the command runs, and points at the published image for the
registry case instead.

Adds test_documented_local_tag_opts_out_of_the_registry, which fails if a
compose run of a local tag loses --pull never. Confirmed by removing the flag
and watching that one test fail.

python -m pytest tests/test_container_image_docs.py β€” 9 passed.
@Yi-111-a

Yi-111-a commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Both fixed in d308d23.

1. Local tag in "Pin a release or another image". You're right that the text and the pull_policy: always above it contradicted each other. Rather than adding another build path, that section now says a local-only tag is the exception and opts out explicitly:

FRONTIER_AGENT_IMAGE=frontier-agent:local \
  docker compose run --pull never --rm agent

I left the compose.yaml override discussion alone β€” --pull never is the flag-level equivalent of pull_policy: build for the one command, so it stays a single self-contained example rather than another -f compose.dev.yaml line to keep in sync.

2. "Direct docker run" wording. It called the reference below the private published image while the command runs frontier-agent:local. Now it states which tag the command runs and that you build it from the checkout first, with the published image moved to the "instead" position rather than deleted β€” both paths stay documented.

On the new test. test_documented_local_tag_opts_out_of_the_registry asserts any docker compose run of a local tag carries --pull never. I confirmed it's discriminating rather than decorative: removing the flag makes that one test fail with the offending command in the message, and the existing eight still pass.

One detail that bit me and would bite the next person: _run_commands only matches lines that start with docker compose, and this example is prefixed with FRONTIER_AGENT_IMAGE=..., so it needs its own match. I kept the widening local to the new test rather than loosening the shared helper, since the README assertion depends on that regex rejecting anything else.

python -m pytest tests/test_container_image_docs.py β€” 9 passed. I couldn't run the deployment-config tests here: the suite doesn't collect in this environment (ModuleNotFoundError: No module named 'agent_core' β€” 48 modules, unrelated to these docs). The existing test_docker_quickstart_offers_a_path_that_needs_no_registry reads the file I edited and still passes, so the block I added is covered by your assertions too.

PR body updated to mention both.

@zhanghanduo zhanghanduo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks

@zhanghanduo
zhanghanduo merged commit ffcb816 into ApodexAI:main Oct 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs/compose tell users to pull ghcr.io/apodexai/frontieragent, but the package is private (unauthorized)

2 participants