hello-127 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Go Nakamaru
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: hello-127
3
+ Version: 0.1.1
4
+ Summary: Carry TCP over a container's standard streams to a service on the Mac's loopback
5
+ Author: Go Nakamaru
6
+ Author-email: Go Nakamaru <gonakamaru@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Operating System :: MacOS
10
+ Requires-Python: >=3.14
11
+ Project-URL: Repository, https://github.com/east-van-ai/hello-127
12
+ Project-URL: Changelog, https://github.com/east-van-ai/hello-127/blob/main/CHANGELOG.md
13
+ Description-Content-Type: text/markdown
14
+
15
+ # hello-127
16
+
17
+ ![CI](https://github.com/east-van-ai/hello-127/actions/workflows/ci.yaml/badge.svg)
18
+
19
+ Can a Docker or Apple Container with no network access still reach a service on
20
+ the Mac's 127.0.0.1, such as Ollama? Yes, through a pipe: the standard streams
21
+ of `docker exec -i` or `container exec -i`. hello-127 is a small command that
22
+ carries TCP through that pipe.
23
+
24
+ hello-127 is a reusable CLI tool for this purpose. This page covers its design,
25
+ limitations, install, behaviour, and known failure modes.
26
+
27
+ ## How a container reaches loopback today
28
+
29
+ A program on a Mac binds to 127.0.0.1 to stay private. Ollama on a Mac does
30
+ this by default. Only programs on the same machine should reach it.
31
+
32
+ Docker Desktop bends that. When a Docker container connects to the Mac, Docker
33
+ Desktop's own process, `com.docker.backend`, opens a fresh connection to
34
+ 127.0.0.1 on the Mac. Ollama sees a local client. The macOS firewall sees an
35
+ outgoing connection, and never asks.
36
+
37
+ Measured on macOS with Docker Desktop 4.92.0 and Ollama 0.33.3:
38
+
39
+ - During a run, `lsof` shows `com.docker.backend` as Ollama's only client.
40
+ - A test web server bound to 127.0.0.1 refused the Mac's own LAN address.
41
+ A container on Docker's default network read from it anyway.
42
+
43
+ That is a gap. "Bound to loopback" stops meaning "local programs only" the
44
+ moment Docker Desktop is running.
45
+
46
+ Apple Container has no such hop. Its traffic arrives over a network
47
+ interface, so reaching Ollama there needs a firewall exception or sudo.
48
+
49
+ ## The same job, on purpose
50
+
51
+ Every container runtime can already run a program inside a container and
52
+ hand you its standard input and output: `docker exec -i`, or
53
+ `container exec -i`. That is a pipe. hello-127 carries TCP through it.
54
+
55
+ ```text
56
+ container (--network none) Mac
57
+ ┌──────────────────────────────┐ ┌──────────────────────────┐
58
+ │ your program │ │ ┌──────────┐ │
59
+ │ │ connects to │ │ ┌─ │ Ollama │◄─┐ │
60
+ │ ▼ 127.0.0.1:11434 │ stdin │ ▼ └──────────┘ │ │
61
+ │ helper ◄─────────────────┼─────────┼── hello-127 │ │
62
+ │ └─────────────────────────►┼─────────┼──► dials 127.0.0.1 ─┘ │
63
+ │ │ stdout │ │
64
+ └──────────────────────────────┘ └──────────────────────────┘
65
+ ```
66
+
67
+ - **Inside the container**, a small helper listens on 127.0.0.1:11434. It
68
+ shares the container's network, so your program sees Ollama as local.
69
+ - **Over the pipe**, each connection becomes frames: open, data, close, each
70
+ tagged with a connection number. Many connections share one pipe.
71
+ - **On the Mac**, hello-127 reads the frames and dials Ollama on loopback.
72
+ Every connection goes out, none comes in. No firewall prompt, no sudo.
73
+
74
+ The helper is not installed in the image. hello-127 sends its source as an
75
+ argument to `python3 -c`, so the image needs only `python3`, 3.10 or newer.
76
+
77
+ None of this is new. SSH's `ProxyCommand` carries one TCP stream over a
78
+ program's standard streams. `ssh -L` and `kubectl port-forward` carry many
79
+ connections through one channel.
80
+
81
+ ## What it is not
82
+
83
+ It carries bytes, and never reads them. Whatever the service lets a local
84
+ client do, a program in the container can do too. The tunnel opens the whole
85
+ service, not one part of it.
86
+
87
+ It also changes how a connection ends. TCP has two endings: a clean close,
88
+ and a reset when something breaks. The pipe has no frame for a reset, so
89
+ every ending arrives as a clean close. A protocol that marks its own end,
90
+ as HTTP does, still notices a cut. A protocol that simply stops at the end
91
+ of the connection cannot tell a crash from a finish.
92
+
93
+ A gatekeeper is the other design. It understands each request and decides
94
+ what to allow. hello-127 makes the pipe on purpose and leaves the deciding
95
+ out.
96
+
97
+ ## Install
98
+
99
+ - A Mac.
100
+ - [uv](https://docs.astral.sh/uv/), which installs hello-127 and fetches
101
+ its Python.
102
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop/), or
103
+ [Apple Container](https://github.com/apple/container).
104
+ - hello-127, from PyPI:
105
+
106
+ ```bash
107
+ uv tool install hello-127
108
+ ```
109
+
110
+ hello-127 itself needs nothing beyond Python's standard library.
111
+
112
+ ## Try it yourself
113
+
114
+ A container started with `--network none` streams a chat from Ollama
115
+ through hello-127. Without the tunnel, the same chat is refused.
116
+
117
+ It needs [Ollama](https://ollama.com), running, with Cohere's Tiny Aya
118
+ pulled, a 2.1 GB download:
119
+
120
+ ```bash
121
+ ollama pull hf.co/CohereLabs/tiny-aya-global-GGUF:Q4_K_M
122
+ ```
123
+
124
+ It takes three terminals. In the first, start a container with no network,
125
+ then the tunnel into it:
126
+
127
+ ```bash
128
+ docker run -d --rm --name h127 --network none python:3.12-slim sleep infinity
129
+ hello-127 tunnel docker h127
130
+ ```
131
+
132
+ - `-d` runs the container in the background.
133
+ - `--rm` deletes the container once it stops.
134
+ - `--name h127` gives it the name the other commands use.
135
+ - `--network none` leaves it with loopback only.
136
+ - `sleep infinity` keeps it running with nothing else to do.
137
+
138
+ ```text
139
+ hello-127: outside: relaying 127.0.0.1:11434 to 127.0.0.1:11434
140
+ hello-127: outside: Ctrl-C to stop
141
+ hello-127: inside: listening on 127.0.0.1:11434
142
+ ```
143
+
144
+ In the second, chat from inside the container:
145
+
146
+ ```bash
147
+ hello-127 ollama-query docker h127
148
+ ```
149
+
150
+ ```text
151
+ Containerized applications are a revolutionary way to deploy and manage software, ...
152
+ ```
153
+
154
+ `ollama-query` runs a small Python program in the container with
155
+ `docker exec`. It asks Cohere's Tiny Aya one fixed question at
156
+ 127.0.0.1:11434 and prints the answer as it streams. It knows nothing else
157
+ about the Mac.
158
+
159
+ Run it before the tunnel is up, and the same program is refused:
160
+
161
+ ```text
162
+ hello-127: nothing answers on 127.0.0.1:11434 here. Is the tunnel running?
163
+ hello-127: the query exited with code 1
164
+ ```
165
+
166
+ In the third, while the answer streams, ask who holds Ollama's port:
167
+
168
+ ```bash
169
+ lsof -nP -iTCP:11434 -sTCP:ESTABLISHED
170
+ ```
171
+
172
+ - `-n` and `-P` print addresses and ports as numbers, not names.
173
+ - `-iTCP:11434` picks TCP sockets on Ollama's port, 11434.
174
+ - `-sTCP:ESTABLISHED` keeps only live connections, in the ESTABLISHED state.
175
+
176
+ Once the answer ends, only Ollama's listening socket is left, so the list
177
+ comes back empty.
178
+
179
+ ```text
180
+ COMMAND PID USER FD TYPE ... NAME
181
+ ollama 803 you 7u IPv4 ... TCP 127.0.0.1:11434->127.0.0.1:52235 (ESTABLISHED)
182
+ python3.1 55300 you 6u IPv4 ... TCP 127.0.0.1:52235->127.0.0.1:11434 (ESTABLISHED)
183
+ ```
184
+
185
+ Ollama's only client is hello-127's Python, which `lsof` cuts to
186
+ `python3.1`. It is not `com.docker.backend`.
187
+
188
+ Back in the first terminal, Ctrl-C closes the helper's input, and the helper
189
+ exits:
190
+
191
+ ```text
192
+ hello-127: outside: stopping, closing the helper's input
193
+ hello-127: inside: input ended, helper exiting
194
+ ```
195
+
196
+ Then `docker stop h127` removes the container.
197
+
198
+ Apple Container works the same way, with the same flags:
199
+
200
+ ```bash
201
+ container run -d --rm --name h127 --network none python:3.14-slim sleep infinity
202
+ hello-127 tunnel container h127
203
+ ```
204
+
205
+ The chat is `hello-127 ollama-query container h127`, and `lsof` again
206
+ names hello-127's Python. Then `container stop h127` removes the container.
207
+
208
+ Measured on macOS with Docker 29.8.0, Apple Container 1.6.0, and Ollama
209
+ 0.33.3.
210
+
211
+ ## Other ports
212
+
213
+ Ollama is only the specimen. The tunnel carries any TCP service on the
214
+ Mac's loopback. `--forward` picks both ends, in the shape `ssh -L` uses:
215
+
216
+ ```bash
217
+ hello-127 tunnel docker h127 --forward 127.0.0.2:80:127.0.0.1:11434
218
+ hello-127 ollama-query docker h127 --connect 127.0.0.2:80
219
+ ```
220
+
221
+ - `127.0.0.2:80` is where the helper listens inside. Leave the address out,
222
+ and it is 127.0.0.1.
223
+ - `127.0.0.1:11434` is what the Mac dials.
224
+ - `--connect` points `ollama-query` at the helper.
225
+
226
+ `--forward` repeats, and one helper carries every listener:
227
+
228
+ ```bash
229
+ hello-127 tunnel docker h127 --forward 11434:127.0.0.1:11434 --forward 8080:127.0.0.1:8000
230
+ ```
231
+
232
+ Two forwards may dial the same target. Two listeners on the same address
233
+ and port would never both bind, so hello-127 refuses them.
234
+
235
+ Every address must be loopback, on both ends. A wider address on the Mac
236
+ would make it a relay to the LAN for a container with no network. A wider
237
+ one inside, such as 0.0.0.0, would publish the Mac's service to any
238
+ network the container does have. hello-127 refuses both before anything
239
+ runs:
240
+
241
+ ```text
242
+ usage: hello-127 tunnel RUNTIME CONTAINER [--forward SPEC]...
243
+ hello-127 tunnel: error: argument --forward: not a loopback address: '10.0.0.5'
244
+ ```
245
+
246
+ ## How it fails
247
+
248
+ Each of these was run on both runtimes. The tunnel is left as it is.
249
+
250
+ - **One slow reader stalls everyone.** All connections share one pipe. A
251
+ client that leaves about a megabyte unread freezes every other connection.
252
+ When it reads again, they all finish, and nothing is lost.
253
+ - **A cut-off stream can look complete.** If the tunnel dies mid-stream,
254
+ the client sees a clean close, not an error. Whether it notices depends on
255
+ the protocol. Against Ollama, the stock `ollama` library raises an error,
256
+ and Python's built-in `http.client` does not. So `ollama-query` also
257
+ waits for Ollama's own `done` line.
258
+ - **A crash on the Mac reads as a finish.** A test server sent 64 KiB, then
259
+ reset the connection. A client that reads until the end got an error on
260
+ the Mac itself. Through the tunnel, it got 64 KiB and a clean end: a
261
+ short file that looks complete. Docker's default network does the same,
262
+ so `com.docker.backend` hides the reset too.
263
+ - **A service that is down looks like one that hung up.** The helper
264
+ accepts a connection before the Mac dials. With nothing listening on the
265
+ Mac, Python's `smtplib` is refused on the Mac and on Docker's default
266
+ network. Through the tunnel, it connects, then reads "Connection
267
+ unexpectedly closed". With the server up, its greeting arrives as usual.
268
+ - **No `python3` in the image.** The tunnel stops at once. Apple Container
269
+ says why. Docker prints its reason on the exec's output, which is the
270
+ pipe, so hello-127 swallows it and shows only "code 127".
271
+ - **The container is killed mid-stream.** The tunnel notices, reports it,
272
+ and exits. Nothing is left connected on the Mac.
273
+ - **A Mac address nobody answers on.** Linux answers on all of 127.x, so
274
+ the helper can listen on 127.0.0.2 inside. macOS answers only on the
275
+ addresses its loopback carries, by default 127.0.0.1 alone. A forward to
276
+ 127.0.0.2 on the Mac holds each connection for 76 seconds, until macOS
277
+ gives up. `ollama-query` then blames Ollama: "the tunnel closed the
278
+ connection".
279
+ - **A dead forward stalls the live ones.** The Mac finishes each dial
280
+ before it reads the next frame, so no data can overtake its own
281
+ connection. A dial that hangs holds up every forward. Behind a forward to
282
+ 127.0.0.2, a chat on a healthy forward waited about 75 seconds for its
283
+ first word, then streamed in full.
284
+
285
+ And one thing that works: a client that has never heard of hello-127. It
286
+ connects to 127.0.0.1 as usual, and the tunnel does the rest. The stock
287
+ `ollama` Python library, with its default settings, works unchanged from a
288
+ container with no network.
289
+
290
+ ## More documentation
291
+
292
+ - [Changelog](https://github.com/east-van-ai/hello-127/blob/main/CHANGELOG.md): project history and releases
293
+
294
+ ## Use of AI
295
+
296
+ Code and documentation are written in collaboration with remote and local AI; design
297
+ decisions, code review, semantic and auditory review, and final judgement stay human.
298
+
299
+ ---
300
+
301
+ **East Van AI** · AI for the rest of us! · Vancouver, BC, Canada
302
+
303
+ [github.com/east-van-ai](https://github.com/east-van-ai) · <east-van-ai@proton.me>
304
+
305
+ Copyright (c) 2026 Go Nakamaru
@@ -0,0 +1,291 @@
1
+ # hello-127
2
+
3
+ ![CI](https://github.com/east-van-ai/hello-127/actions/workflows/ci.yaml/badge.svg)
4
+
5
+ Can a Docker or Apple Container with no network access still reach a service on
6
+ the Mac's 127.0.0.1, such as Ollama? Yes, through a pipe: the standard streams
7
+ of `docker exec -i` or `container exec -i`. hello-127 is a small command that
8
+ carries TCP through that pipe.
9
+
10
+ hello-127 is a reusable CLI tool for this purpose. This page covers its design,
11
+ limitations, install, behaviour, and known failure modes.
12
+
13
+ ## How a container reaches loopback today
14
+
15
+ A program on a Mac binds to 127.0.0.1 to stay private. Ollama on a Mac does
16
+ this by default. Only programs on the same machine should reach it.
17
+
18
+ Docker Desktop bends that. When a Docker container connects to the Mac, Docker
19
+ Desktop's own process, `com.docker.backend`, opens a fresh connection to
20
+ 127.0.0.1 on the Mac. Ollama sees a local client. The macOS firewall sees an
21
+ outgoing connection, and never asks.
22
+
23
+ Measured on macOS with Docker Desktop 4.92.0 and Ollama 0.33.3:
24
+
25
+ - During a run, `lsof` shows `com.docker.backend` as Ollama's only client.
26
+ - A test web server bound to 127.0.0.1 refused the Mac's own LAN address.
27
+ A container on Docker's default network read from it anyway.
28
+
29
+ That is a gap. "Bound to loopback" stops meaning "local programs only" the
30
+ moment Docker Desktop is running.
31
+
32
+ Apple Container has no such hop. Its traffic arrives over a network
33
+ interface, so reaching Ollama there needs a firewall exception or sudo.
34
+
35
+ ## The same job, on purpose
36
+
37
+ Every container runtime can already run a program inside a container and
38
+ hand you its standard input and output: `docker exec -i`, or
39
+ `container exec -i`. That is a pipe. hello-127 carries TCP through it.
40
+
41
+ ```text
42
+ container (--network none) Mac
43
+ ┌──────────────────────────────┐ ┌──────────────────────────┐
44
+ │ your program │ │ ┌──────────┐ │
45
+ │ │ connects to │ │ ┌─ │ Ollama │◄─┐ │
46
+ │ ▼ 127.0.0.1:11434 │ stdin │ ▼ └──────────┘ │ │
47
+ │ helper ◄─────────────────┼─────────┼── hello-127 │ │
48
+ │ └─────────────────────────►┼─────────┼──► dials 127.0.0.1 ─┘ │
49
+ │ │ stdout │ │
50
+ └──────────────────────────────┘ └──────────────────────────┘
51
+ ```
52
+
53
+ - **Inside the container**, a small helper listens on 127.0.0.1:11434. It
54
+ shares the container's network, so your program sees Ollama as local.
55
+ - **Over the pipe**, each connection becomes frames: open, data, close, each
56
+ tagged with a connection number. Many connections share one pipe.
57
+ - **On the Mac**, hello-127 reads the frames and dials Ollama on loopback.
58
+ Every connection goes out, none comes in. No firewall prompt, no sudo.
59
+
60
+ The helper is not installed in the image. hello-127 sends its source as an
61
+ argument to `python3 -c`, so the image needs only `python3`, 3.10 or newer.
62
+
63
+ None of this is new. SSH's `ProxyCommand` carries one TCP stream over a
64
+ program's standard streams. `ssh -L` and `kubectl port-forward` carry many
65
+ connections through one channel.
66
+
67
+ ## What it is not
68
+
69
+ It carries bytes, and never reads them. Whatever the service lets a local
70
+ client do, a program in the container can do too. The tunnel opens the whole
71
+ service, not one part of it.
72
+
73
+ It also changes how a connection ends. TCP has two endings: a clean close,
74
+ and a reset when something breaks. The pipe has no frame for a reset, so
75
+ every ending arrives as a clean close. A protocol that marks its own end,
76
+ as HTTP does, still notices a cut. A protocol that simply stops at the end
77
+ of the connection cannot tell a crash from a finish.
78
+
79
+ A gatekeeper is the other design. It understands each request and decides
80
+ what to allow. hello-127 makes the pipe on purpose and leaves the deciding
81
+ out.
82
+
83
+ ## Install
84
+
85
+ - A Mac.
86
+ - [uv](https://docs.astral.sh/uv/), which installs hello-127 and fetches
87
+ its Python.
88
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop/), or
89
+ [Apple Container](https://github.com/apple/container).
90
+ - hello-127, from PyPI:
91
+
92
+ ```bash
93
+ uv tool install hello-127
94
+ ```
95
+
96
+ hello-127 itself needs nothing beyond Python's standard library.
97
+
98
+ ## Try it yourself
99
+
100
+ A container started with `--network none` streams a chat from Ollama
101
+ through hello-127. Without the tunnel, the same chat is refused.
102
+
103
+ It needs [Ollama](https://ollama.com), running, with Cohere's Tiny Aya
104
+ pulled, a 2.1 GB download:
105
+
106
+ ```bash
107
+ ollama pull hf.co/CohereLabs/tiny-aya-global-GGUF:Q4_K_M
108
+ ```
109
+
110
+ It takes three terminals. In the first, start a container with no network,
111
+ then the tunnel into it:
112
+
113
+ ```bash
114
+ docker run -d --rm --name h127 --network none python:3.12-slim sleep infinity
115
+ hello-127 tunnel docker h127
116
+ ```
117
+
118
+ - `-d` runs the container in the background.
119
+ - `--rm` deletes the container once it stops.
120
+ - `--name h127` gives it the name the other commands use.
121
+ - `--network none` leaves it with loopback only.
122
+ - `sleep infinity` keeps it running with nothing else to do.
123
+
124
+ ```text
125
+ hello-127: outside: relaying 127.0.0.1:11434 to 127.0.0.1:11434
126
+ hello-127: outside: Ctrl-C to stop
127
+ hello-127: inside: listening on 127.0.0.1:11434
128
+ ```
129
+
130
+ In the second, chat from inside the container:
131
+
132
+ ```bash
133
+ hello-127 ollama-query docker h127
134
+ ```
135
+
136
+ ```text
137
+ Containerized applications are a revolutionary way to deploy and manage software, ...
138
+ ```
139
+
140
+ `ollama-query` runs a small Python program in the container with
141
+ `docker exec`. It asks Cohere's Tiny Aya one fixed question at
142
+ 127.0.0.1:11434 and prints the answer as it streams. It knows nothing else
143
+ about the Mac.
144
+
145
+ Run it before the tunnel is up, and the same program is refused:
146
+
147
+ ```text
148
+ hello-127: nothing answers on 127.0.0.1:11434 here. Is the tunnel running?
149
+ hello-127: the query exited with code 1
150
+ ```
151
+
152
+ In the third, while the answer streams, ask who holds Ollama's port:
153
+
154
+ ```bash
155
+ lsof -nP -iTCP:11434 -sTCP:ESTABLISHED
156
+ ```
157
+
158
+ - `-n` and `-P` print addresses and ports as numbers, not names.
159
+ - `-iTCP:11434` picks TCP sockets on Ollama's port, 11434.
160
+ - `-sTCP:ESTABLISHED` keeps only live connections, in the ESTABLISHED state.
161
+
162
+ Once the answer ends, only Ollama's listening socket is left, so the list
163
+ comes back empty.
164
+
165
+ ```text
166
+ COMMAND PID USER FD TYPE ... NAME
167
+ ollama 803 you 7u IPv4 ... TCP 127.0.0.1:11434->127.0.0.1:52235 (ESTABLISHED)
168
+ python3.1 55300 you 6u IPv4 ... TCP 127.0.0.1:52235->127.0.0.1:11434 (ESTABLISHED)
169
+ ```
170
+
171
+ Ollama's only client is hello-127's Python, which `lsof` cuts to
172
+ `python3.1`. It is not `com.docker.backend`.
173
+
174
+ Back in the first terminal, Ctrl-C closes the helper's input, and the helper
175
+ exits:
176
+
177
+ ```text
178
+ hello-127: outside: stopping, closing the helper's input
179
+ hello-127: inside: input ended, helper exiting
180
+ ```
181
+
182
+ Then `docker stop h127` removes the container.
183
+
184
+ Apple Container works the same way, with the same flags:
185
+
186
+ ```bash
187
+ container run -d --rm --name h127 --network none python:3.14-slim sleep infinity
188
+ hello-127 tunnel container h127
189
+ ```
190
+
191
+ The chat is `hello-127 ollama-query container h127`, and `lsof` again
192
+ names hello-127's Python. Then `container stop h127` removes the container.
193
+
194
+ Measured on macOS with Docker 29.8.0, Apple Container 1.6.0, and Ollama
195
+ 0.33.3.
196
+
197
+ ## Other ports
198
+
199
+ Ollama is only the specimen. The tunnel carries any TCP service on the
200
+ Mac's loopback. `--forward` picks both ends, in the shape `ssh -L` uses:
201
+
202
+ ```bash
203
+ hello-127 tunnel docker h127 --forward 127.0.0.2:80:127.0.0.1:11434
204
+ hello-127 ollama-query docker h127 --connect 127.0.0.2:80
205
+ ```
206
+
207
+ - `127.0.0.2:80` is where the helper listens inside. Leave the address out,
208
+ and it is 127.0.0.1.
209
+ - `127.0.0.1:11434` is what the Mac dials.
210
+ - `--connect` points `ollama-query` at the helper.
211
+
212
+ `--forward` repeats, and one helper carries every listener:
213
+
214
+ ```bash
215
+ hello-127 tunnel docker h127 --forward 11434:127.0.0.1:11434 --forward 8080:127.0.0.1:8000
216
+ ```
217
+
218
+ Two forwards may dial the same target. Two listeners on the same address
219
+ and port would never both bind, so hello-127 refuses them.
220
+
221
+ Every address must be loopback, on both ends. A wider address on the Mac
222
+ would make it a relay to the LAN for a container with no network. A wider
223
+ one inside, such as 0.0.0.0, would publish the Mac's service to any
224
+ network the container does have. hello-127 refuses both before anything
225
+ runs:
226
+
227
+ ```text
228
+ usage: hello-127 tunnel RUNTIME CONTAINER [--forward SPEC]...
229
+ hello-127 tunnel: error: argument --forward: not a loopback address: '10.0.0.5'
230
+ ```
231
+
232
+ ## How it fails
233
+
234
+ Each of these was run on both runtimes. The tunnel is left as it is.
235
+
236
+ - **One slow reader stalls everyone.** All connections share one pipe. A
237
+ client that leaves about a megabyte unread freezes every other connection.
238
+ When it reads again, they all finish, and nothing is lost.
239
+ - **A cut-off stream can look complete.** If the tunnel dies mid-stream,
240
+ the client sees a clean close, not an error. Whether it notices depends on
241
+ the protocol. Against Ollama, the stock `ollama` library raises an error,
242
+ and Python's built-in `http.client` does not. So `ollama-query` also
243
+ waits for Ollama's own `done` line.
244
+ - **A crash on the Mac reads as a finish.** A test server sent 64 KiB, then
245
+ reset the connection. A client that reads until the end got an error on
246
+ the Mac itself. Through the tunnel, it got 64 KiB and a clean end: a
247
+ short file that looks complete. Docker's default network does the same,
248
+ so `com.docker.backend` hides the reset too.
249
+ - **A service that is down looks like one that hung up.** The helper
250
+ accepts a connection before the Mac dials. With nothing listening on the
251
+ Mac, Python's `smtplib` is refused on the Mac and on Docker's default
252
+ network. Through the tunnel, it connects, then reads "Connection
253
+ unexpectedly closed". With the server up, its greeting arrives as usual.
254
+ - **No `python3` in the image.** The tunnel stops at once. Apple Container
255
+ says why. Docker prints its reason on the exec's output, which is the
256
+ pipe, so hello-127 swallows it and shows only "code 127".
257
+ - **The container is killed mid-stream.** The tunnel notices, reports it,
258
+ and exits. Nothing is left connected on the Mac.
259
+ - **A Mac address nobody answers on.** Linux answers on all of 127.x, so
260
+ the helper can listen on 127.0.0.2 inside. macOS answers only on the
261
+ addresses its loopback carries, by default 127.0.0.1 alone. A forward to
262
+ 127.0.0.2 on the Mac holds each connection for 76 seconds, until macOS
263
+ gives up. `ollama-query` then blames Ollama: "the tunnel closed the
264
+ connection".
265
+ - **A dead forward stalls the live ones.** The Mac finishes each dial
266
+ before it reads the next frame, so no data can overtake its own
267
+ connection. A dial that hangs holds up every forward. Behind a forward to
268
+ 127.0.0.2, a chat on a healthy forward waited about 75 seconds for its
269
+ first word, then streamed in full.
270
+
271
+ And one thing that works: a client that has never heard of hello-127. It
272
+ connects to 127.0.0.1 as usual, and the tunnel does the rest. The stock
273
+ `ollama` Python library, with its default settings, works unchanged from a
274
+ container with no network.
275
+
276
+ ## More documentation
277
+
278
+ - [Changelog](https://github.com/east-van-ai/hello-127/blob/main/CHANGELOG.md): project history and releases
279
+
280
+ ## Use of AI
281
+
282
+ Code and documentation are written in collaboration with remote and local AI; design
283
+ decisions, code review, semantic and auditory review, and final judgement stay human.
284
+
285
+ ---
286
+
287
+ **East Van AI** · AI for the rest of us! · Vancouver, BC, Canada
288
+
289
+ [github.com/east-van-ai](https://github.com/east-van-ai) · <east-van-ai@proton.me>
290
+
291
+ Copyright (c) 2026 Go Nakamaru
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "hello-127"
3
+ version = "0.1.1"
4
+ description = "Carry TCP over a container's standard streams to a service on the Mac's loopback"
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ classifiers = ["Operating System :: MacOS"]
10
+ dependencies = []
11
+
12
+ [[project.authors]]
13
+ name = "Go Nakamaru"
14
+ email = "gonakamaru@gmail.com"
15
+
16
+ [project.urls]
17
+ Repository = "https://github.com/east-van-ai/hello-127"
18
+ Changelog = "https://github.com/east-van-ai/hello-127/blob/main/CHANGELOG.md"
19
+
20
+ [project.scripts]
21
+ hello-127 = "hello_127.cli:main"
22
+
23
+ [build-system]
24
+ requires = ["uv_build>=0.13.0,<0.14.0"]
25
+ build-backend = "uv_build"
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "hello-127"
3
+ version = "0.1.1"
4
+ description = "Carry TCP over a container's standard streams to a service on the Mac's loopback"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Go Nakamaru", email = "gonakamaru@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.14"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ classifiers = ["Operating System :: MacOS"]
13
+ dependencies = []
14
+
15
+ [project.urls]
16
+ Repository = "https://github.com/east-van-ai/hello-127"
17
+ Changelog = "https://github.com/east-van-ai/hello-127/blob/main/CHANGELOG.md"
18
+
19
+ [project.scripts]
20
+ hello-127 = "hello_127.cli:main"
21
+
22
+ [build-system]
23
+ requires = ["uv_build>=0.13.0,<0.14.0"]
24
+ build-backend = "uv_build"
@@ -0,0 +1 @@
1
+ """hello-127: carry TCP over a container's standard streams to Ollama on loopback."""