moby / moby/moby

Investigate if we need the implicit "ExposedPorts" for every "PortBindings"

Open
#45,201 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area/api area/networking kind/enhancement
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 EXPOSE in 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-all option to automatically map ephemeral ports for each exposed port
  • The metadata is shown in the PORTS column on docker ps (sometimes)

We should look;

  • Do we need ExposedPorts for docker networking to do its job?
  • If we do need it, can we move the special handling to the API (add ExposedPorts entries for each PortBinding) 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.