Repository: jupyter/docker-stacks

Issue:
[BUG] - Healthcheck fails when using proxy
### What docker image(s) are you using?

base-notebook

### Host OS system and architecture running docker image

Windows 11 as host and linux/amd64 for docker

### What Docker command are you running?

docker compose up with the following dockerfile:

```Dockerfile
version: '3.4'

services:
  datamining:
    container_name: xxxx
    image: xxxx
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - "8888:8888"
    volumes:
      - xxxx:/home/jovyan/work
    environment:
      - DOCKER_STACKS_JUPYTER_CMD=lab
    restart: on-failure
```

### How to Reproduce the problem?

Precondition is that the machine has to operate in a corporate environment using the companies proxy.
Start the container as above.
Check the state of the container with ```docker container ls```
The container is marked as unhealthy.

### Command output

```bash session
abcdefghijk    "tini -g -- start-no…"   x hours ago     Up x hours (unhealthy)        0.0.0.0:8888->8888/tcp                             xxxx
```


### Expected behavior

```abcdedfghi   abcdefghijk    "tini -g -- start-no…"   x hours ago     Up x hours (healthy)        0.0.0.0:8888->8888/tcp                             xxxx```

### Actual behavior

After investigating the issue the problem is that docker_healthcheck.py does not run successfully giving the following error message:
```
Traceback (most recent call last):
  File "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py", line 790, in urlopen
    response = self._make_request(
               ^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py", line 536, in _make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/urllib3/connection.py", line 461, in getresponse
    httplib_response = super().getresponse()
                       ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/http/client.py", line 1378, in getresponse
    response.begin()
  File "/opt/conda/lib/python3.11/http/client.py", line 318, in begin
    version, status, reason = self._read_status()
                              ^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/http/client.py", line 287, in _read_status
    raise RemoteDisconnected("Remote end closed connection without"
http.client.RemoteDisconnected: Remote end closed connection without response

The above exception was the direct cause of the following exception:

urllib3.exceptions.ProxyError: ('Unable to connect to proxy', RemoteDisconnected('Remote end closed connection without response'))

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/opt/conda/lib/python3.11/site-packages/requests/adapters.py", line 486, in send
    resp = conn.urlopen(
           ^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py", line 844, in urlopen
    retries = retries.increment(
              ^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/urllib3/util/retry.py", line 515, in increment
    raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='host.docker.internal', port=9000): Max retries exceeded with url: http://7702f0e1c7d4:8888/api (Caused by ProxyError('Unable to connect to proxy', RemoteDisconnected('Remote end closed connection without response')))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/etc/jupyter/docker_healthcheck.py", line 19, in <module>
    r = requests.get(url, verify=False)  # request without SSL verification
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/requests/api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/requests/api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/requests/sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/requests/sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/requests/adapters.py", line 513, in send
    raise ProxyError(e, request=request)
requests.exceptions.ProxyError: HTTPConnectionPool(host='host.docker.internal', port=9000): Max retries exceeded with url: http://7702f0e1c7d4:8888/api (Caused by ProxyError('Unable to connect to proxy', RemoteDisconnected('Remote end closed connection without response')))
```

### Anything else?

After investigating the issue further I came to the conclusion that using the proxy will be the problem.  So I applied the following fix to ```docker_healthcheck.py```:
```python
proxies = {
  "http": None,
  "https": None,
}

r = requests.get(url, proxies=proxies, verify=False)  # request without SSL verification
```
Now the healthcheck works!

### Latest Docker version

- [X] I've updated my Docker version to the latest available, and the issue still persists


Discussion:
@MatthiasJobst could you please send a PR to fix this?
If it's possible, please, also add a test, which works with your fix and doesn't work in current state.
Yes, I will do. The change is fairly basic. But I am unexperienced using the test framework, such that I might not be able to comply with your request on including tests.
Nevertheless I will give it a try.

Resolving patch:
diff --git a/base-notebook/docker_healthcheck.py b/base-notebook/docker_healthcheck.py
--- a/base-notebook/docker_healthcheck.py
+++ b/base-notebook/docker_healthcheck.py
@@ -16,6 +16,11 @@
 url = json.loads(json_file.read_bytes())["url"]
 url = url + "api"
 
-r = requests.get(url, verify=False)  # request without SSL verification
+proxies = {
+    "http": "",
+    "https": "",
+}
+
+r = requests.get(url, proxies=proxies, verify=False)  # request without SSL verification
 r.raise_for_status()
 print(r.content)


Test patch:
diff --git a/tests/base-notebook/test_healthcheck.py b/tests/base-notebook/test_healthcheck.py
--- a/tests/base-notebook/test_healthcheck.py
+++ b/tests/base-notebook/test_healthcheck.py
@@ -69,6 +69,53 @@ def test_health(
     assert get_health(running_container) == "healthy"
 
 
+@pytest.mark.parametrize(
+    "env,cmd,user",
+    [
+        (
+            ["HTTPS_PROXY=host.docker.internal", "HTTP_PROXY=host.docker.internal"],
+            None,
+            None,
+        ),
+        (
+            [
+                "NB_USER=testuser",
+                "CHOWN_HOME=1",
+                "JUPYTER_PORT=8123",
+                "HTTPS_PROXY=host.docker.internal",
+                "HTTP_PROXY=host.docker.internal",
+            ],
+            ["start-notebook.sh", "--ServerApp.base_url=/test"],
+            "root",
+        ),
+    ],
+)
+def test_health_proxy(
+    container: TrackedContainer,
+    env: Optional[list[str]],
+    cmd: Optional[list[str]],
+    user: Optional[str],
+) -> None:
+    running_container = container.run_detached(
+        tty=True,
+        environment=env,
+        command=cmd,
+        user=user,
+    )
+
+    # sleeping some time to let the server start
+    time_spent = 0.0
+    wait_time = 0.1
+    time_limit = 15
+    while time_spent < time_limit:
+        time.sleep(wait_time)
+        time_spent += wait_time
+        if get_health(running_container) == "healthy":
+            return
+
+    assert get_health(running_container) == "healthy"
+
+
 @pytest.mark.parametrize(
     "env,cmd,user",
     [