Search code examples
dockergoportdocker-run

Unable to port forward with the port range syntax in Docker SDK


I am trying to run a Docker container using Go SDK. From the terminal, I can run the following command with no issues :

docker run -d --memory 1024M --name "cdb1" -p 2001-2006:8091-8096 -p 11210-11211:11210-11211 couchbase

I want to achieve the same thing using the Docker SDK for Go but cannot find how to reproduce the -p 2001-2006:8091-8096 part. Here is my ContainerCreate call :

cont, err := cli.ContainerCreate(
    context.Background(),
    &container.Config{
        Image: "couchbase",
        ExposedPorts: nat.PortSet{
            "2001-2006": struct{}{},
            "11210-11211": struct{}{},
        },
    },
    &container.HostConfig{
        PortBindings: nat.PortMap{
            "8091-8096": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2001-2006",
                },
            },
            "11210-11211": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "11210-11211",
                },
            },
        },
        Resources: container.Resources{
            Memory: 1024 * 1000000,
        },
    },
    nil,
    "cdb1",
)

But running this always throw the same error :

Error response from daemon: invalid port specification: "8091-8096"

Doing some more testing, the error seems to come specifically from the PortBindings part (if I remove this one and leave the exposed ports, it works fine).

I couldn't find anything about this on Docker documentation.


Solution

  • nat.PortSet is a map with nat.Port being its key type:

    type PortSet map[Port]struct{}
    

    Your "port" specification of "2001-2006" syntactically works because it's an untyped string constant and can be converted to nat.Port which has string as its underlying type, but this string literal lacks the protocol specification (e.g. tcp or udp).

    Instead use the nat.NewPort() function to create the key:

    ports1, err := nat.NewPort("tcp", "2001-2006")   // check err
    ports2, err := nat.NewPort("tcp", "11210-11211") // check err
    
    ExposedPorts: nat.PortSet{
        ports1: struct{}{},
        ports2: struct{}{},
    },
    

    Note that the expected "raw" string format is "2001-2006/tcp" which would also be accepted, but it's better to leave this internal detail to nat.NewPort().

    And to construct a nat.PortMap, use the nat.ParsePortSpec() utility function. This is how you can assemble your PortBindings:

    portBindings := nat.PortMap{}
    
    for _, rawMapping := range []string{
        "0.0.0.0:2001-2006:8091-8096",
        "0.0.0.0:11210-11211:11210-11211",
    } {
        mappings, err := nat.ParsePortSpec(rawMapping)
        if err != nil {
            panic(err)
        }
        for _, pm := range mappings {
            portBindings[pm.Port] = []nat.PortBinding{pm.Binding}
        }
    
    }
    

    Then you can use the portBindings above for HostConfig.PortBindings field:

    &container.HostConfig{
        PortBindings: portBindings ,
        Resources: container.Resources{
            Memory: 1024 * 1000000,
        },
    },