I use uv
to manage different virtual environments for Python. And I also use it for managing JupyterLab environments. One day, I want to run two different instances of JupyterLab on the same server. It's easy for the environment part thanks to uv
. But if I don't want to open too many ports, I have to use a reverse proxy to connect. Caddy seems a good choice since its simplicity. Furthermore, a process manager is favorable, so I plan to add Supervisor.
The set-up should be simple, but it turns out that it wasn't.
Modify Caddy config #
I'm using Debian 12 for the server, and the getting started guide of Caddy is misleading. After installing Caddy with
sudo apt install caddy
I'm having problem with modifying the config. I created a Caddyfile
in the current directory and run caddy adapt
. It shows
INFO using adjacent Caddyfile
{"apps":{"http":{"servers":{"srv0"...
From the output, I thought the config had taken effect, but in fact it didn't. Instead, I have to use caddy reload
to update the config. However, the config updated like this will not survive after system restart. A better way is to directly modify /etc/caddy/Caddyfile
and run:
sudo systemctl restart caddy
Use Unix socket for communication #
I don't want to open too many ports, and fortunately, both JupyterLab and Caddy supports using Unix sockets. I want to make JupyterLab run as the default user (debian
). Therefore, to enable communications with Caddy, the easiest way is to configure Caddy to run as user debian
. Edit /lib/systemd/system/caddy.service
and change the User
and Group
:
User=debian
Group=debian
Then, we need to create a directory /run/debian
for storing the socket files.
sudo mkdir /run/debian
sudo chown debian:debian /run/debian
However, the /run
directory is cleared after restart, we need to automate the creation of /run/debian
by putting the following content into /etc/tmpfiles.d/debian-run.conf
:
d /run/debian 0755 debian debian
And the Caddyfile
looks like this:
:8888
reverse_proxy /jlab/* unix//run/debian/jlab.sock
reverse_proxy /notebook/* unix//run/debian/notebook.sock
Configuring Supervisor #
After installing Supervisor
apt install supervisor
We store the configs in /etc/supervisor/conf.d/jupyterlab.conf
:
[program:jupyterlab]
command=/home/debian/.cargo/bin/uv run jupyter-lab --no-browser --sock=/run/debian/jlab.sock --IdentityProvider.token='...' --ServerApp.base_url='/jlab' --ServerApp.allow_remote_access=True
directory=/home/debian/jupyterlab
user=debian
stopasgroup=true
autostart=true
autorestart=true
stderr_logfile=/var/log/jupyterlab/jupyterlab.err.log
[program:notebook]
...
Explanation:
--no-browser
: Skip opening a browser (useful if there are any browsers installed on the system)--sock=...
: Set the Unix socket location--ServerApp.allow_remote_access=True
: Allow accessing via Internet, not limited tolocalhost
stopasgroup=true
:uv
doesn't pass signals to children. This makes Supervisor terminate the entire process tree.
After editing the config file, we need to create the log directory:
sudo mkdir -p /var/log/jupyterlab
sudo chown debian:debian /var/log/jupyterlab
Then we run the following command to reload:
sudo supervisorctl reread
sudo supervisorctl update