Skip to content

fix(oci): honor --insecure-registry when up re-loads the model - #13894

Merged
glours merged 1 commit into
docker:mainfrom
ptrdom:fix/oci-up-insecure-registry
Jul 21, 2026
Merged

fix(oci): honor --insecure-registry when up re-loads the model#13894
glours merged 1 commit into
docker:mainfrom
ptrdom:fix/oci-up-insecure-registry

Conversation

@ptrdom

@ptrdom ptrdom commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Problem

docker compose -f oci://<insecure-registry>/... up fails against a plain-HTTP /
loopback registry unless --yes is passed, even though the initial project
load succeeds.

Root cause

up loads the project once via ToProjectLoadProject, which correctly
forwards --insecure-registry. runUp then calls checksForRemoteStack; without
--yes that path re-loads the model via ToModel to prompt for interpolation
variables. ToModel builds its OCI loader through ProjectOptions.remoteLoaders,
which constructed an empty api.OCIOptions{} — dropping the insecure-registry
list. The resolver then speaks HTTPS to a plain-HTTP registry and fails before the
prompt is even shown.

oci.Get always performs a network resolve (even with the artifact disk-cached),
so the second load genuinely re-contacts the registry with the wrong config.
config and viz share the ToModel path and hit the same failure.

Note this is distinct from the Docker Desktop proxy loopback bypass (#13824): the
shared transport already handles proxy bypass for consuming; this PR fixes the
separate plain-HTTP flag being dropped on the re-load path.

Fix

The two OCI-options construction sites had drifted. Consolidate them into
ProjectOptions.ociOptions() so every path that pulls an OCI artifact uses the
same configuration.

Testing

Covered by an e2e case appended to TestPublish, which already spins up an
insecure registry:3 and does an oci:// round-trip. The existing assertions
load back via config (the ToProject path, which always worked), so the new
case drives the re-load instead: publish a fixture carrying an interpolation
variable (GREETING: ${GREETING:-hello}) so the prompt fires, then run up
without --yes and decline, which keeps it hermetic — checksForRemoteStack is
the first statement in runUp, so nothing is created.

Verified in both directions locally with a real registry:

  • with the fix, TestPublish passes;
  • with remoteLoaders reverted to api.OCIOptions{}, it fails on exactly the
    regression:
publish_test.go:227: assertion failed:
  strings.Contains(res.Combined(), "server gave HTTP response to HTTPS client") is true:
  Your compose stack "oci://localhost:32769/test:interpolated" is stored in "..."
  failed to pull OCI resource "localhost:32769/test:interpolated": failed to do request:
  Head "https://localhost:32769/v2/test/manifests/interpolated":
  http: server gave HTTP response to HTTPS client

The "stack is stored in" line confirms the first load succeeded and only the
ToModel re-load went out over HTTPS.

Per review feedback, the earlier unit tests and the ociRemoteLoader.InsecureRegistries()
getter they needed are dropped — pkg/remote/oci.go is unchanged from main.

🤖 Generated with Claude Code

@ptrdom
ptrdom requested a review from a team as a code owner July 2, 2026 16:59
@ptrdom
ptrdom requested review from glours and ndeloof July 2, 2026 16:59
@ptrdom
ptrdom requested a review from a team as a code owner July 7, 2026 12:48
@ptrdom

ptrdom commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@glours Is this PR good to merge? I guess since there is a workaround for the issue then it is not the most urgent, but it seems to me that it would still be good to merge a fix.

@glours glours left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice catch, and the fix itself (consolidating both sites into ociOptions()) is exactly right.

One request before merge: I'd prefer covering this with an e2e test rather than the current unit tests, which require exposing ociRemoteLoader.InsecureRegistries() as a public method just for test introspection, I'd rather not widen the production API for that.

TestPublish in pkg/e2e/publish_test.go already spins up an insecure registry:3 and does an oci:// round-trip — perfect base to build on. The gap is that it loads back via config (the ToProject path, which always worked), so it never hits checksForRemoteStack → ToModel where this bug lives.

To guard the real regression, add a case that goes through the re-load:

  • publish a fixture with an interpolation variable (e.g. GREETING: ${GREETING:-hello}) so the prompt fires;
  • run up without --yes against the oci:// ref with --insecure-registry, feeding n to stay hermetic;
  • assert it reaches operation cancelled by user and not server gave HTTP response to HTTPS client.
cmd := c.NewDockerComposeCmd(t, "--project-name=oci-reload",
    "--insecure-registry", registry,
    "-f", fmt.Sprintf("oci://%s/test:test", registry), "up")
cmd.Stdin = strings.NewReader("n\n")
res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
    cmd.Env = append(cmd.Env, "XDG_CACHE_HOME="+t.TempDir())
})
assert.Assert(t, !strings.Contains(res.Combined(), "server gave HTTP response to HTTPS client"))
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "operation cancelled by user"})

make build-and-e2e-compose runs it locally. With this in place you can drop the getter and compose_remote_loaders_test.go, keeping the PR to the real fix + one focused e2e. Thanks!

`docker compose -f oci://<insecure-registry>/... up` failed against a
plain-HTTP registry unless --yes was passed:

    failed to pull OCI resource "localhost:5000/test:interpolated":
    Head "https://localhost:5000/v2/test/manifests/interpolated":
    http: server gave HTTP response to HTTPS client

`up` loads the project twice. The first load goes through ToProject,
which built its OCI options from --insecure-registry correctly. Without
--yes, checksForRemoteStack then calls promptForInterpolatedVariables,
which re-loads the project through ToModel to list the interpolation
variables. That second load builds its own resource loaders via
remoteLoaders, and those passed an empty api.OCIOptions{}, dropping the
flag. Since the OCI loader always performs a network resolve, the
re-load spoke HTTPS to a plain-HTTP registry and failed before the
prompt could be shown.

The two construction sites had drifted apart, so rather than patching
the second one, both now share ProjectOptions.ociOptions(). `config`
and `viz` use the same ToModel path and are fixed as well.

Covered by an e2e case in TestPublish, which already runs an insecure
registry and an oci:// round-trip: it publishes a fixture carrying an
interpolation variable so the prompt fires, then runs `up` without
--yes and declines, asserting the re-load does not fail with
"server gave HTTP response to HTTPS client".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Domantas Petrauskas <dom.petrauskas@gmail.com>
@ptrdom
ptrdom force-pushed the fix/oci-up-insecure-registry branch from f25807e to ee2c2cf Compare July 21, 2026 13:30
@ptrdom
ptrdom requested a review from glours July 21, 2026 13:34
@ptrdom

ptrdom commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@glours Thank you for a detailed response, pushed the fixes as requested, PR is ready for review again.

@glours glours left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sounds good to me, thanks @ptrdom 🙏

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@glours
glours merged commit 5534be0 into docker:main Jul 21, 2026
80 of 81 checks 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.

2 participants