Guided Problem
MiniDock Build 25: Publish One Private Service Port
- Time
- 25m
- Level
- intermediate
- Artifacts
- not specified
Publishing Ports
Design claim: Port publishing is an explicit host ingress rule that forwards selected traffic to a private container endpoint.
Starting model
- You can connect same-host containers and outbound traffic while preserving private network stacks.
- The useful vocabulary at this point is deliberately small: published port, DNAT, conntrack, host endpoint.
Private addressing needs a deliberate door
The gateway must accept host traffic on one chosen port while the rest of its container network remains private. Connectivity is a set of scoped attachments, routes, and names, not an intrinsic property of a container record.
The tempting shortcut is straightforward: expose the container's private address directly to external clients. The shortcut works only while addresses are static and every participant shares one host or trust zone. Private addresses may be unroutable or change on replacement, and exposure must be limited to an operator-selected host address and port. Endpoint churn, isolation, or a second host exposes the missing scope and ownership information.
Publish a host tuple, not a container identity
The engine reserves a host binding and installs forwarding or proxy rules from that public tuple to the container endpoint. The mechanism separates membership from packet movement so drivers can implement the same connection contract at different boundaries.
Read the topology by following an endpoint from its sandbox through the selected network boundary, keeping control information separate from packet flow.
flowchart LR
CL["external client
→ 203.0.113.7:8080"] --> NIC["host NIC"]
NIC --> DNAT["DNAT rule
dst → 172.18.0.2:80"]
DNAT --> CT[("conntrack table
remembers the rewrite")]
DNAT --> BR["bridge"] --> C["container web
172.18.0.2:80"]
C -- reply --> BR2["bridge"] --> UN["conntrack: reverse-rewrite
src → 203.0.113.7:8080"] --> CL
It separates public host binding from private container endpoint. Its central claim is that only explicitly published host tuples create external reachability to private container ports; the labels therefore describe authority rather than decorative grouping.
Forward ingress and preserve the return path
External clients can only address the host's public IP. Publishing a port makes the host a doorman: the engine installs a DNAT (destination NAT) rule — "TCP to my port 8080 → rewrite destination to 172.18.0.2:80" — in the kernel's packet pipeline. The rewritten packet is then routed over the bridge like any other. The kernel records each rewrite in its connection-tracking table, so reply packets are automatically reverse-rewritten: the client sees replies from host:8080 and never learns the container exists. Port 8080 is claimed on the host — publishing the same host port for two containers fails; two containers each listening on container port 80 is fine (different sandboxes, different stacks).
A client targets the host, ingress policy matches the published tuple, destination translation selects the container address and port, and replies traverse the reverse mapping. Trace both the forward and cleanup paths; connectivity is correct only when allocation and removal agree about ownership.
The second figure tests the same model in motion: it traces matching, translation, delivery, and reverse traffic.
sequenceDiagram
participant CL as client
participant K as host kernel (NAT)
participant C as container :80
CL->>K: SYN to 203.0.113.7:8080
K->>K: DNAT match → rewrite dst to 172.18.0.2:80, record in conntrack
K->>C: deliver via bridge
C-->>K: reply from 172.18.0.2:80
K->>K: conntrack hit → rewrite src back to 203.0.113.7:8080
K-->>CL: reply appears to come from host:8080
note over CL: the container's private IP
never leaks to the client
It traces matching, translation, delivery, and reverse traffic. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.
Host bindings must remain unique
Two containers request the same host address and port, so the second publication must be refused before ambiguous forwarding exists. A stale or conflicting attachment must be refused or withdrawn locally rather than corrupting unrelated network state.
Only explicitly published host tuples create external reachability to private container ports. The rule binds reachability to explicit, current membership within the correct scope.
Open one MiniDock ingress path
MiniDock exposes one gateway port without turning every container endpoint into a public address. MiniDock now needs that connectivity property without being told which component to edit.
What carries forward
- Only explicitly published host tuples create external reachability to private container ports.
- Expose one private service endpoint without changing what the external client sees.
- The rejected shortcut remains a diagnostic: if the design starts depending on it again, the original constraint has probably been lost.
See what actually stuck.
Take the practice scenarios now.