I’ve been trying to reach an externally managed service from Jupyterhub that runs in a couple of docker containers. I have been working with a minimal configuration (the jupyterhub-docker/basic-example repo) in my dev environment for the last few days and am unsure what is causing me to get a 503 in hub when I attempt to reach the service. The hub logs don’t really shed any light. If I look at the logs of the external docker container I don’t see any requests actually hitting the webserver, so I suspect something else needs to be configured on the Jupyterhub side but so far, I’ve been unsuccessful in finding an example or figuring it out myself. All of the containers are using the same docker network that is defined in the example compose file, and if I exec into the hub container the other service responds to pings and shows the target port as open.
I’ve tried various combinations of arguments with in the JupyterHub.services definition, though in the attached example I’ve got the simplest form. I’ve tried to set the url parameter to use the container name and trust that the docker network is going to resolve where that container is, I’ve tried setting it to both 127.0.0.1 and 0.0.0.0 and neither seem to have any impact.
My ultimate goal is to allow users to access this other service from within jhub to make it easier for them to access from a single interface, as well as ensure that if I deploy somewhere that doesn’t have a similar service available, they will have at least one option to use.
Here is the jupyterhub-config.py
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
# Configuration file for JupyterHub
import os
c = get_config() # noqa: F821
# We rely on environment variables to configure JupyterHub so that we
# avoid having to rebuild the JupyterHub container every time we change a
# configuration parameter.
# Spawn single-user servers as Docker containers
c.JupyterHub.spawner_class = "dockerspawner.DockerSpawner"
# prevent jumping directly into singleuser server
c.JupyterHub.redirect_to_server = False
# not sure if this is necessary, but...jic
c.JupyterHub.allow_named_servers = True
c.JupyterHub.services = [
{
"name": "regexr-service",
"url": "http://regexr-phpapi:9000",
}
]
# Spawn containers from this image
c.DockerSpawner.image = os.environ["DOCKER_NOTEBOOK_IMAGE"]
# Connect containers to this Docker network
network_name = os.environ["DOCKER_NETWORK_NAME"]
c.DockerSpawner.use_internal_ip = True
c.DockerSpawner.network_name = network_name
c.DockerSpawner.ip = "0.0.0.0"
# Explicitly set notebook directory because we'll be mounting a volume to it.
# Most `jupyter/docker-stacks` *-notebook images run the Notebook server as
# user `jovyan`, and set the notebook directory to `/home/jovyan/work`.
# We follow the same convention.
notebook_dir = os.environ.get("DOCKER_NOTEBOOK_DIR", "/home/jovyan/work")
c.DockerSpawner.notebook_dir = notebook_dir
# Mount the real user's Docker volume on the host to the notebook user's
# notebook directory in the container
c.DockerSpawner.volumes = {"jupyterhub-user-{username}": notebook_dir}
# Remove containers once they are stopped
c.DockerSpawner.remove = True
# For debugging arguments passed to spawned containers
c.DockerSpawner.debug = True
# User containers will access hub by container name on the Docker network
c.JupyterHub.hub_ip = "jhub"
c.JupyterHub.hub_port = 8080
# Persist hub data on volume mounted inside container
c.JupyterHub.cookie_secret_file = "/data/jupyterhub_cookie_secret"
c.JupyterHub.db_url = "sqlite:////data/jupyterhub.sqlite"
# Allow all signed-up users to login
c.Authenticator.allow_all = True
# Authenticate users with Native Authenticator
c.JupyterHub.authenticator_class = "dummy"
# Allowed admins
admin = os.environ.get("JUPYTERHUB_ADMIN")
if admin:
c.Authenticator.admin_users = [admin]
Here is the docker-compose.yml
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
# JupyterHub docker compose configuration file
version: "3"
services:
hub:
build:
context: .
dockerfile: Dockerfile.jupyterhub
args:
JUPYTERHUB_VERSION: latest
restart: always
image: jupyterhub
container_name: jhub
networks:
- jupyterhub-network
volumes:
# The JupyterHub configuration file
- "./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py:ro"
# Bind Docker socket on the host so we can connect to the daemon from
# within the container
- "/var/run/docker.sock:/var/run/docker.sock:rw"
# Bind Docker volume on host for JupyterHub database and cookie secrets
- "jupyterhub-data:/data"
ports:
- "8000:8000"
environment:
# This username will be a JupyterHub admin
JUPYTERHUB_ADMIN: admin
# All containers will join this network
DOCKER_NETWORK_NAME: jupyterhub-network
# JupyterHub will spawn this Notebook image for users
DOCKER_NOTEBOOK_IMAGE: quay.io/jupyter/base-notebook:latest
# Notebook directory inside user image
DOCKER_NOTEBOOK_DIR: /home/jovyan/work
regexr:
image: dstack4273/regexr-app:0.1
container_name: regexr-app
networks:
- jupyterhub-network
depends_on:
- phpapi
phpapi:
image: dstack4273/regexr-phpapi:0.1
container_name: regexr-phpapi
networks:
- jupyterhub-network
ports:
- "9000:80"
volumes:
jupyterhub-data:
networks:
jupyterhub-network:
name: jupyterhub-network
Here is the repo’s Dockerfile dependency for the compose file
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
ARG JUPYTERHUB_VERSION
FROM quay.io/jupyterhub/jupyterhub:$JUPYTERHUB_VERSION
# Install dockerspawner, nativeauthenticator
# hadolint ignore=DL3013
RUN python3 -m pip install --no-cache-dir \
dockerspawner
CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"]
The external service isn’t one I understand well so that could possibly be part of the problem?
If this is helpful, here are the logs from the hub
(base) dstack@ROGtop:~/projects/docker_jhub$ docker logs jhub
[I 2025-12-01 21:15:25.556 JupyterHub app:3359] Running JupyterHub version 5.4.2
[I 2025-12-01 21:15:25.556 JupyterHub app:3389] Using Authenticator: jupyterhub.auth.DummyAuthenticator-5.4.2
[I 2025-12-01 21:15:25.556 JupyterHub app:3389] Using Spawner: dockerspawner.dockerspawner.DockerSpawner-14.0.0
[I 2025-12-01 21:15:25.556 JupyterHub app:3389] Using Proxy: jupyterhub.proxy.ConfigurableHTTPProxy-5.4.2
/srv/venv/lib/python3.12/site-packages/jupyter_events/schema.py:68: JupyterEventsVersionWarning: The `version` property of an event schema must be a string. It has been type coerced, but in a future version of this library, it will fail to validate. Please update schema: https://schema.jupyter.org/jupyterhub/events/server-action
validate_schema(_schema)
[I 2025-12-01 21:15:25.567 JupyterHub app:1880] Writing cookie_secret to /data/jupyterhub_cookie_secret
[I 2025-12-01 21:15:25.600 alembic.runtime.migration migration:211] Context impl SQLiteImpl.
[I 2025-12-01 21:15:25.600 alembic.runtime.migration migration:214] Will assume non-transactional DDL.
[I 2025-12-01 21:15:25.613 alembic.runtime.migration migration:622] Running stamp_revision -> 4621fec11365
[I 2025-12-01 21:15:25.745 JupyterHub proxy:556] Generating new CONFIGPROXY_AUTH_TOKEN
[W 2025-12-01 21:15:25.755 JupyterHub auth:1541] Using testing authenticator DummyAuthenticator! This is not meant for production!
[I 2025-12-01 21:15:25.760 JupyterHub roles:281] Adding role admin for User: admin
[I 2025-12-01 21:15:25.765 JupyterHub roles:281] Adding role user for User: admin
[I 2025-12-01 21:15:25.785 JupyterHub app:2893] Creating service regexr-service with oauth_client_id=service-regexr-service
[I 2025-12-01 21:15:25.788 JupyterHub provider:661] Creating oauth client service-regexr-service
[I 2025-12-01 21:15:25.827 JupyterHub app:3429] Initialized 0 spawners in 0.004 seconds
[I 2025-12-01 21:15:25.831 JupyterHub metrics:425] Found 0 active users in the last ActiveUserPeriods.twenty_four_hours
[I 2025-12-01 21:15:25.831 JupyterHub metrics:425] Found 0 active users in the last ActiveUserPeriods.seven_days
[I 2025-12-01 21:15:25.832 JupyterHub metrics:425] Found 0 active users in the last ActiveUserPeriods.thirty_days
[W 2025-12-01 21:15:25.832 JupyterHub proxy:748] Running JupyterHub without SSL. I hope there is SSL termination happening somewhere else...
[I 2025-12-01 21:15:25.832 JupyterHub proxy:752] Starting proxy @ http://:8000
2025-12-01T21:15:26.123Z [ConfigProxy] info: Proxying http://*:8000 to (no default)
2025-12-01T21:15:26.124Z [ConfigProxy] info: Proxy API at http://127.0.0.1:8001/api/routes
2025-12-01T21:15:26.326Z [ConfigProxy] info: 200 GET /api/routes
[I 2025-12-01 21:15:26.328 JupyterHub app:3752] Hub API listening on http://jhub:8080/hub/
[I 2025-12-01 21:15:26.329 JupyterHub app:3637] Adding external service regexr-service at http://regexr-phpapi:9000
[W 2025-12-01 21:15:27.267 JupyterHub app:3666] Cannot connect to external service regexr-service at http://regexr-phpapi:9000. Is it running?
2025-12-01T21:15:27.269Z [ConfigProxy] info: 200 GET /api/routes
[I 2025-12-01 21:15:27.270 JupyterHub proxy:477] Adding route for Hub: / => http://jhub:8080
[W 2025-12-01 21:15:27.270 JupyterHub proxy:445] Adding missing route for regexr-service (Server(url=http://regexr-phpapi:9000/services/regexr-service/, bind_url=http://regexr-phpapi:9000/services/regexr-service/))
[I 2025-12-01 21:15:27.271 JupyterHub proxy:312] Adding service regexr-service to proxy /services/regexr-service/ => http://regexr-phpapi:9000
2025-12-01T21:15:27.272Z [ConfigProxy] info: Adding route / -> http://jhub:8080
2025-12-01T21:15:27.273Z [ConfigProxy] info: Route added / -> http://jhub:8080
2025-12-01T21:15:27.274Z [ConfigProxy] info: 201 POST /api/routes/
2025-12-01T21:15:27.275Z [ConfigProxy] info: Adding route /services/regexr-service -> http://regexr-phpapi:9000
2025-12-01T21:15:27.275Z [ConfigProxy] info: Route added /services/regexr-service -> http://regexr-phpapi:9000
2025-12-01T21:15:27.276Z [ConfigProxy] info: 201 POST /api/routes/services/regexr-service
[I 2025-12-01 21:15:27.276 JupyterHub app:3783] JupyterHub is now running at http://:8000
[I 2025-12-01 21:16:22.292 JupyterHub log:192] 302 GET / -> /hub/ (@::ffff:172.19.0.1) 1.20ms
[I 2025-12-01 21:16:22.305 JupyterHub log:192] 302 GET /hub/ -> /hub/login?next=%2Fhub%2F (@::ffff:172.19.0.1) 0.90ms
[I 2025-12-01 21:16:22.315 JupyterHub _xsrf_utils:130] Setting new xsrf cookie for b'None:cX9IBTtHcamkkkcTy21ebGML6YFEJa9R8YazI0lHNj8=' {'path': '/hub/', 'max_age': 3600}
[I 2025-12-01 21:16:22.315 JupyterHub _xsrf_utils:130] Setting new xsrf cookie for b'None:cX9IBTtHcamkkkcTy21ebGML6YFEJa9R8YazI0lHNj8=' {'path': '/hub/', 'max_age': 3600}
[I 2025-12-01 21:16:22.336 JupyterHub log:192] 200 GET /hub/login?next=%2Fhub%2F (@::ffff:172.19.0.1) 22.72ms
[W 2025-12-01 21:16:23.355 JupyterHub app:2967] Cannot connect to external service regexr-service at http://regexr-phpapi:9000
[I 2025-12-01 21:16:31.073 JupyterHub _xsrf_utils:130] Setting new xsrf cookie for b'cX9IBTtHcamkkkcTy21ebGML6YFEJa9R8YazI0lHNj8=:8a8f5052eba84630ac2e3d5e529fb8cf' {'path': '/hub/'}
[I 2025-12-01 21:16:31.073 JupyterHub base:973] User logged in: admin
[I 2025-12-01 21:16:31.074 JupyterHub log:192] 302 POST /hub/login?next=%2Fhub%2F -> /hub/ (admin@::ffff:172.19.0.1) 13.91ms
[I 2025-12-01 21:16:31.090 JupyterHub log:192] 302 GET /hub/ -> /hub/home (admin@::ffff:172.19.0.1) 8.60ms
[I 2025-12-01 21:16:31.102 JupyterHub _xsrf_utils:130] Setting new xsrf cookie for b'6faeb9d79ccb44bea9c3afda79814d6e:8a8f5052eba84630ac2e3d5e529fb8cf' {'path': '/hub/'}
[I 2025-12-01 21:16:31.108 JupyterHub log:192] 200 GET /hub/home (admin@::ffff:172.19.0.1) 10.52ms
2025-12-01T21:16:31.927Z [ConfigProxy] error: 503 GET /services/regexr-service/ read ECONNRESET
[I 2025-12-01 21:16:31.929 JupyterHub _xsrf_utils:130] Setting new xsrf cookie for b'None:ID3Rg_K7ouo9ue3vQfuXGV-PP0Iso9jSL3qisogg9cg=' {'path': '/hub/', 'max_age': 3600}
[I 2025-12-01 21:16:31.933 JupyterHub log:192] 200 GET /hub/error/503?url=%2Fservices%2Fregexr-service%2F (@172.19.0.2) 4.88ms
[W 2025-12-01 21:17:23.405 JupyterHub app:2967] Cannot connect to external service regexr-service at http://regexr-phpapi:9000
[W 2025-12-01 21:18:23.458 JupyterHub app:2967] Cannot connect to external service regexr-service at http://regexr-phpapi:9000