Investigate if we need the implicit "ExposedPorts" for every "PortBindings"
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 72.1k
- Forks
- 19.2k
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 164
Description
Description
When looking at https://github.com/moby/moby/issues/45199, I realized that the "port publishing" API is rather awkward to use. When mapping a port, the API expects both HostConfig.PortBindings and .ExposedPorts (Config.ExposedPorts) to have an entry for each container port that is mapped. Currently this logic has to be implemented in clients, which makes using PortBindings "less trivial". The CLI does this handling currently;
docker create --name foo -p 80:80 alpine
Notice that the API request not only has the PortBinding, but also implicitly sets ExposedPorts;
DEBU[2023-03-23T09:19:41.786405173Z] Calling POST /v1.42/containers/create?name=foo
DEBU[2023-03-23T09:19:41.786524715Z] form data: {"AttachStderr":true,"AttachStdin":false,"AttachStdout":true,"Cmd":null,"Domainname":"","Entrypoint":null,"Env":null,"ExposedPorts":{"80/tcp":{}},"HostConfig":{"AutoRemove":false,"Binds":null,"BlkioDeviceReadBps":[],"BlkioDeviceReadIOps":[],"BlkioDeviceWriteBps":[],"BlkioDeviceWriteIOps":[],"BlkioWeight":0,"BlkioWeightDevice":[],"CapAdd":null,"CapDrop":null,"Cgroup":"","CgroupParent":"","CgroupnsMode":"","ConsoleSize":[60,216],"ContainerIDFile":"","CpuCount":0,"CpuPercent":0,"CpuPeriod":0,"CpuQuota":0,"CpuRealtimePeriod":0,"CpuRealtimeRuntime":0,"CpuShares":0,"CpusetCpus":"","CpusetMems":"","DeviceCgroupRules":null,"DeviceRequests":null,"Devices":[],"Dns":[],"DnsOptions":[],"DnsSearch":[],"ExtraHosts":null,"GroupAdd":null,"IOMaximumBandwidth":0,"IOMaximumIOps":0,"IpcMode":"","Isolation":"","Links":null,"LogConfig":{"Config":{},"Type":""},"MaskedPaths":null,"Memory":0,"MemoryReservation":0,"MemorySwap":0,"MemorySwappiness":-1,"NanoCpus":0,"NetworkMode":"default","OomKillDisable":false,"OomScoreAdj":0,"PidMode":"","PidsLimit":0,"PortBindings":{"80/tcp":[{"HostIp":"","HostPort":"80"}]},"Privileged":false,"PublishAllPorts":false,"ReadonlyPaths":null,"ReadonlyRootfs":false,"RestartPolicy":{"MaximumRetryCount":0,"Name":"no"},"SecurityOpt":null,"ShmSize":0,"UTSMode":"","Ulimits":null,"UsernsMode":"","VolumeDriver":"","VolumesFrom":null},"Hostname":"","Image":"alpine","Labels":{},"NetworkingConfig":{"EndpointsConfig":{}},"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":{},"WorkingDir":""}
ExposedPorts is a field that was introduced a long time ago, so there may be some history behind this (which should be dug into);
- This field is mostly "metadata"; users can set it in
EXPOSEin a Dockerfile to indicate "something in the container will be listening on this port (and protocol) - The metadata from this field is used when using the
-P/--publish-alloption to automatically map ephemeral ports for each exposed port - The metadata is shown in the
PORTScolumn ondocker ps(sometimes)
We should look;
- Do we need
ExposedPortsfor docker networking to do its job? - If we do need it, can we move the special handling to the API (add
ExposedPortsentries for eachPortBinding) to make the API more easy to consume
There also is some odd behavior on when these ExposedPorts are used. This may be a bug (or by design), but needs to be looked into;
docker create -it --name exposed --expose 80/tcp alpine
docker run -dit --name exposed2 --expose 80/tcp alpine
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2d014e221b15 alpine "/bin/sh" 4 seconds ago Up 2 seconds 80/tcp exposed2
610c95c8e4c2 alpine "/bin/sh" 11 seconds ago Created exposed
Notice that the (unmapped, but exposed) ports do NOT show for the non-running container. Stopping the exposed2 container also makes the ports disappear;
docker stop exposed2
exposed2
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2d014e221b15 alpine "/bin/sh" 58 seconds ago Exited (137) 2 seconds ago exposed2
610c95c8e4c2 alpine "/bin/sh" About a minute ago Created exposed
But the ExposedPorts property is not modified;
docker inspect --format '{{.Config.ExposedPorts}}' exposed2
map[80/tcp:{}]
Possibly this was "by design", but if so, we need to look "why". Currently it's confusing (the port is still "exposed", but not mapped, so why are we not showing it?)
curl -s --unix-socket /var/run/docker.sock 'http://foo/containers/json?all=1' | jq '.[] | .Ports'
[]
[]
docker start exposed2
curl -s --unix-socket /var/run/docker.sock 'http://foo/containers/json?all=1' | jq '.[] | .Ports'
[
{
"PrivatePort": 80,
"Type": "tcp"
}
]
[]
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the docker create, docker run, docker ps, docker inspect, and Unix-socket API examples in the issue, then trace how PortBindings and ExposedPorts are handled. Determine whether ExposedPorts is required for networking, whether the API should populate it, and why port listings change with container state; done means recording a clear decision and its expected behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, go
- Domain
- backend-api-design, cli, networking
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100