Alpine website, then run the following command to pull it.
sudo machinectl pull-tar --verify=checksum http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.3-x86_64.tar.gz alpine
This will download the tarball, check sha, and put it into /var/lib/machines/alpine
. machinectl
comes with Arch by default, and can be installed on Ubuntu with sudo apt install systemd-container
. After it done (this should be fast since the tarball is only 5M), enter the container by systemd-nspawn -M alpine
.
The package manager of Alpine is very simple and fast (faster than pacman
IMO!). Install basic packages as following:
apk add python3 python3-dev # for python
apk add R R-dev # for R
apk add g++ linux-headers zeromq # for building jupyter kernels
apk add npm # for jupyter extensions
There are several packages not in the official registar. For example, frp
and julia
. I had to download the binary mannually and put them into /usr
. Note the official julia
builds are based on glibc. Luckily there is a musl build here, and it works fine for me.
After installing these packages, we can start installing "scientific" packages with pip3
and the built-in package managers of julia
and R
. Note that since most pre-built wheels are depend on glibc, pip3
needs to compile pretty much everything, which is very slow (It takes more than 30 min to get sklearn
).
Finally, after the container is filled with high end packages, export it with
sudo machinectl export-tar jupyter jupyter.tar.xz
My image is shared on Google Drive and served by CloudFlare, it includes
frps
);To install the container, just run
sudo machinectl pull-tar --verify=no https://gdrive.ylxdzsw.com/container-images/jupyter.tar.xz
Then start the jupyter service with
sudo systemd-run systemd-nspawn -M jupyter jupyter lab --allow-root --ip=0.0.0.0 --port=8848
Now jupyter is running at http://<server>:8848
with default password fuck
. Though I'm totally fine with this password, it can be easily changed in /root/.jupyter/jupyter_notebook_config.py
. If the server is behind NAT, frp can be used to allow external access.
It's well known that anyone capable of chroot
also has the ability to break out. But (not so) recently Linux got a new feature called namespace. With it we can run our container without root access.
To run the container, just download and untar, then use unshare
to simulate chroot
:
wget https://gdrive.ylxdzsw.com/container-images/jupyter.tar.xz
tar -Jxvf jupyter.tar.xz
unshare -r -m --propagation slave bash -c "mount -R /proc proc; mount -R /dev dev; chroot . /bin/sh -l"
Now we are in the container! unshare
is a thin wrapper around the unshare(2)
, which create a new namespace where the user id is 0. The mount
s allow us to see the outside world and interact with the kernel. The full command to run and detach jupyter from host without systemd is:
setsid unshare -r -m --propagation slave bash -c 'mount -R /proc proc; mount -R /dev dev; chroot . /bin/sh -l -c "HOME=/root jupyter lab --allow-root --ip=0.0.0.0 --port=8848"' > /dev/null 2>&1
To access the Internet inside the container, an additional mount -R /etc/resolv.conf etc/resolv.conf
maybe needed.
Warning: RCall.jl: Error: package or namespace load failed for "methods" in dyn.load(file, DLLpath = DLLpath, ...):
unable to load shared object '/usr/lib/R/library/methods/libs/methods.so':
Error loading shared library libR.so: No such file or directory (needed by /usr/lib/R/library/methods/libs/methods.so)
Error: package or namespace load failed for "utils" in dyn.load(file, DLLpath = DLLpath, ...):
unable to load shared object '/usr/lib/R/library/utils/libs/utils.so':
Error loading shared library libR.so: No such file or directory (needed by /usr/lib/R/library/utils/libs/utils.so)
Error: package or namespace load failed for "grDevices" in dyn.load(file, DLLpath = DLLpath, ...):
unable to load shared object '/usr/lib/R/library/grDevices/libs/grDevices.so':
Error loading shared library libR.so: No such file or directory (needed by /usr/lib/R/library/grDevices/libs/grDevices.so)
Error: package or namespace load failed for "graphics" in dyn.load(file, DLLpath = DLLpath, ...):
unable to load shared object '/usr/lib/R/library/grDevices/libs/grDevices.so':
Error loading shared library libR.so: No such file or directory (needed by /usr/lib/R/library/grDevices/libs/grDevices.so)
Error: package or namespace load failed for "stats" in dyn.load(file, DLLpath = DLLpath, ...):
unable to load shared object '/usr/lib/R/library/grDevices/libs/grDevices.so':
Error loading shared library libR.so: No such file or directory (needed by /usr/lib/R/library/grDevices/libs/grDevices.so)
Error: package or namespace load failed for "methods" in dyn.load(file, DLLpath = DLLpath, ...):
unable to load shared object '/usr/lib/R/library/methods/libs/methods.so':
Error loading shared library libR.so: No such file or directory (needed by /usr/lib/R/library/methods/libs/methods.so)
During startup - Warning messages:
1: package "methods" in options("defaultPackages") was not found
2: package "utils" in options("defaultPackages") was not found
3: package "grDevices" in options("defaultPackages") was not found
4: package "graphics" in options("defaultPackages") was not found
5: package "stats" in options("defaultPackages") was not found
6: package "methods" in options("defaultPackages") was not found
@ RCall /root/.julia/packages/RCall/g7dhB/src/io.jl:113
InitError: REvalError: Error in options(rcalljl_device = png) : object 'png' not found
during initialization of module RCall
Solution: it is because libR.so
is put on a strange place /usr/lib/R/lib
, along with the fact that jupyter doesn't load /etc/profile
, so julia can't find it. I had to mannually set "env": {"LD_LIBRARY_PATH": "/usr/lib:/usr/local/lib:/usr/lib/R/lib"}
in ~/.local/share/jupyter/kernels/julia-1.2/kernel.json