Skip to content

Conversation

@mikesir87
Copy link
Member

Resolves #13572

And AI disclaimer - Claude performed the troubleshooting and fix in this PR.

I have built the Compose binary locally and validated the fix does work on my Windows machine.

Problem

When using Docker Compose with an OCI artifact on Windows (e.g., docker compose -f oci://dockersamples/welcome-to-docker up), the following error occurred:

CreateFile C:\Users\username\oci:\dockersamples\.env: The filename, directory name, or volume label syntax is incorrect.                                                                                           

This error was introduced between v5.0.0 and v5.0.1, specifically by commit 6c04392 which fixed error handling in setEnvWithDotEnv. The bug existed in v5.0.0 but was silently ignored due to improper error
handling.

Root Cause

The issue occurred in the setEnvWithDotEnv function in cmd/compose/compose.go:

  1. When setEnvWithDotEnv is called with an OCI artifact path, it creates ProjectOptions without registering any remote loaders
  2. The compose-go library's GetWorkingDir() method doesn't recognize the path as a remote resource
  3. It tries to treat the OCI reference as a local file path by calling filepath.Abs() on it
  4. On Windows, filepath.Abs("oci://dockersamples/...") produces an invalid path like C:\Users\username\oci:\dockersamples
  5. When trying to load .env files, it appends to this malformed path, resulting in: C:\Users\username\oci:\dockersamples\.env
  6. Windows rejects this path because colons are only valid after drive letters

Code Flow

In compose-go's cli/options.go, the GetWorkingDir() method:

func (o *ProjectOptions) GetWorkingDir() (string, error) {                                                                                                                                                         
    if o.WorkingDir != "" {                                                                                                                                                                                        
        return filepath.Abs(o.WorkingDir)                                                                                                                                                                          
    }                                                                                                                                                                                                              
PATH:                                                                                                                                                                                                              
    for _, path := range o.ConfigPaths {                                                                                                                                                                           
        if path != "-" {                                                                                                                                                                                           
            for _, l := range o.ResourceLoaders {                                                                                                                                                                  
                if l.Accept(path) {                                                                                                                                                                                
                    break PATH  // Skip filepath.Abs for remote resources                                                                                                                                          
                }                                                                                                                                                                                                  
            }                                                                                                                                                                                                      
            absPath, err := filepath.Abs(path)  // BUG: Called for OCI paths when no loaders registered                                                                                                            
            if err != nil {                                                                                                                                                                                        
                return "", err                                                                                                                                                                                     
            }                                                                                                                                                                                                      
            return filepath.Dir(absPath), nil                                                                                                                                                                      
        }                                                                                                                                                                                                          
    }                                                                                                                                                                                                              
    return os.Getwd()                                                                                                                                                                                              
}                                                                                                                                                                                                                  

Without remote loaders registered, OCI paths fall through to filepath.Abs(), causing the issue.

Solution

Modified setEnvWithDotEnv to detect remote config paths and skip environment loading for them:

func setEnvWithDotEnv(opts ProjectOptions) error {                                                                                                                                                                 
    // Check if we're using a remote config (OCI or Git)                                                                                                                                                           
    // If so, skip env loading as remote loaders haven't been initialized yet                                                                                                                                      
    for _, path := range opts.ConfigPaths {                                                                                                                                                                        
        if strings.HasPrefix(path, remote.OciPrefix) || strings.Contains(path, "://") {                                                                                                                            
            return nil                                                                                                                                                                                             
        }                                                                                                                                                                                                          
    }                                                                                                                                                                                                              
    // ... rest of the function unchanged                                                                                                                                                                          
}                                                                                                                                                                                                                  

This approach:

  • Detects remote config paths early (OCI: oci://, Git: URLs with ://)
  • Skips the problematic .env loading for remote configs
  • Allows normal processing for local compose files
  • Environment loading for remote configs happens later in the flow when remote loaders are properly initialized

Testing

  • Added tests in cmd/compose/compose_oci_test.go for OCI artifacts, Git remotes, and local paths
    • Verified fix works on Windows ARM64
    • All existing tests pass

Files Changed

  • cmd/compose/compose.go: Modified setEnvWithDotEnv function
  • cmd/compose/compose_oci_test.go: Added tests for the fix"

@mikesir87 mikesir87 requested a review from a team as a code owner February 5, 2026 16:00
@mikesir87 mikesir87 requested review from glours and ndeloof February 5, 2026 16:00
@codecov
Copy link

codecov bot commented Feb 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@thaJeztah
Copy link
Member

⚠️ canned reply ahead ⚠️

Thank you for contributing! It appears your commit message is missing a DCO sign-off,
causing the DCO check to fail.

We require all commit messages to have a Signed-off-by line with your name
and e-mail (see "Sign your work"
in the CONTRIBUTING.md in this repository), which looks something like:

Signed-off-by: YourFirsName YourLastName <yourname@example.org>

There is no need to open a new pull request, but to fix this (and make CI pass),
you need to amend the commit(s) in this pull request, and "force push" the amended
commit.

Unfortunately, it's not possible to do so through GitHub's web UI, so this needs
to be done through the git commandline.

You can find some instructions in the output of the DCO check (which can be found
in the "checks" tab on this pull request), as well as in the Moby contributing guide.

Steps to do so "roughly" come down to:

  1. Set your name and e-mail in git's configuration:

    git config --global user.name "YourFirstName YourLastName"
    git config --global user.email "yourname@example.org"

    (Make sure to use your real name (not your GitHub username/handle) and e-mail)

  2. Clone your fork locally

  3. Check out the branch associated with this pull request

  4. Sign-off and amend the existing commit(s)

    git commit --amend --no-edit --signoff

    If your pull request contains multiple commits, either squash the commits (if
    needed) or sign-off each individual commit.

  5. Force push your branch to GitHub (using the --force or --force-with-lease flags) to update the pull request.

Sorry for the hassle (I wish GitHub would make this a bit easier to do), and let me know if you need help or more detailed instructions!

When using OCI artifacts (e.g., `docker compose -f oci://dockersamples/welcome-to-docker up`)
on Windows, users encountered the following error:

  CreateFile C:\Users\username\oci:\dockersamples\.env: The filename, directory name,
  or volume label syntax is incorrect.

This issue was introduced between v5.0.0 and v5.0.1, specifically by commit 6c04392
which fixed error handling in setEnvWithDotEnv. The bug existed in v5.0.0 but was
silently ignored due to improper error handling.

Root Cause:
-----------
The setEnvWithDotEnv function creates ProjectOptions without registering remote loaders.
Without remote loaders, the compose-go library doesn't recognize OCI paths as remote
resources. It falls through to filepath.Abs() which treats the OCI reference as a
relative path.

On Windows, filepath.Abs("oci://dockersamples/...") produces an invalid path like:
  C:\Users\username\oci:\dockersamples

Windows rejects this path because colons are only valid after drive letters.

Solution:
---------
Modified setEnvWithDotEnv to detect remote config paths and skip environment loading
for them. Instead of hardcoding string checks, the fix uses the actual remote loaders'
Accept() method to determine if a config path is remote. This is more maintainable
and consistent with how the compose-go library identifies remote resources.

The function now:
- Accepts a dockerCli parameter to access remote loaders
- Uses opts.remoteLoaders(dockerCli) to get loader instances
- Checks if any loader accepts the config path using loader.Accept()
- Skips .env loading for remote configs (happens later when loaders are initialized)
- Allows normal processing for local compose files

Testing:
--------
- Added tests for OCI artifacts, Git remotes, and local paths
- Verified fix works on Windows ARM64
- All existing tests pass

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Michael Irwin <mikesir87@gmail.com>
@mikesir87 mikesir87 force-pushed the fix-oci-windows-path-error branch from 9696b02 to 6cbb80d Compare February 5, 2026 17:03
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.

[BUG] Compose with OCI artifacts is failing on Windows

3 participants