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 caddyI'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 caddyUse 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=debianThen, we need to create a directory /run/debian for storing the socket files.
sudo mkdir /run/debian
sudo chown debian:debian /run/debianHowever, 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 debianAnd the Caddyfile looks like this:
:8888
reverse_proxy /jlab/* unix//run/debian/jlab.sock
reverse_proxy /notebook/* unix//run/debian/notebook.sockConfiguring Supervisor #
After installing Supervisor
apt install supervisorWe 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 tolocalhoststopasgroup=true:uvdoesn'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/jupyterlabThen we run the following command to reload:
sudo supervisorctl reread
sudo supervisorctl update