2

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.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
KawaLo
  • 1,783
  • 3
  • 16
  • 34

2 Answers2

2

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,
    },
},
icza
  • 389,944
  • 63
  • 907
  • 827
  • Seems indeed a better practice to use NewPort. However it doesn't solve my issue, I still cannot use the range syntax inside the PortBinding section for some reason :/ (using your suggestion works in ExposedPorts but I still have the same error from PortBindings, only the splitted up syntax suggested by @NeoAnderson works for now) – KawaLo Aug 03 '20 at 21:20
1

Try to add each port individually instead of the ports range:

cont, err := cli.ContainerCreate(
    context.Background(),
    &container.Config{
        Image: "couchbase",
        ExposedPorts: nat.PortSet{
            "2001": struct{}{},
            "2002": struct{}{},
            "2003": struct{}{},
            "2004": struct{}{},
            "2005": struct{}{},
            "2006": struct{}{},
            "11210": struct{}{},
            "11211": struct{}{}
        },
    },
    &container.HostConfig{
        PortBindings: nat.PortMap{
            "8091": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2001",
                },
            },
            "8092": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2002",
                },
            },
            "8093": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2003",
                },
            },
            "8094": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2004",
                },
            },
            "8095": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2005",
                },
            },
            "8096": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "2006",
                },
            },
            "11210": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "11210",
                },
            },
            "11211": []nat.PortBinding{
                {
                    HostIP: "0.0.0.0",
                    HostPort: "11211",
                },
            }
        },
        Resources: container.Resources{
            Memory: 1024 * 1000000,
        },
    },
    nil,
    "cdb1",
)
Neo Anderson
  • 5,957
  • 2
  • 12
  • 29
  • Thanks ! That's indeed a solution but it looks kind of "forced" to me. I'd especially love a condensed solution similar to what I can do from the terminal, since I plan to make the ports configurable (replacing `2001-2006` with a variable). Is their a reason why the range notation `xxxx-xxxx` works (or at least seems to work) in the `ExposedPorts` field but not in the `PortBindings` one ? – KawaLo Aug 03 '20 at 21:04