Kodi y plataformas de streaming (Netflix, HBO, Amazon, Disney+)

Actualizado a 13 de Octubre de 2022

Es habitual ver centros multimedia en Raspberry Pi, lo que no es tan habitual es montarlos en una imagen Docker, con las mejoras que ha habido en hardware en Raspberri ya es posible tener varios servicios a la vez en una sola Rpi, por ejemplo servidores de vídeo, audio etc..

En este caso se van a detallar los pasos para tener una imagen Docker con Kodi (v19.x) en la última versión Debian disponible en este momento (Bullseye).

Esta imagen Docker contendrá los plugins de HBO, Netflix, Amazon y Disney+ (aunque es posible que se puedan configurar otros).

Configuración Host

Antes de empezar con la imagen Docker necesitaremos una Raspberry operativa, como versión de Raspberry Pi OS podemos usar cualquiera (incluso Lite) una vez instalado Raspberry Pi OS actualizamos el sistema con:

sudo apt-get update
sudo apt-get upgrade

También hace falta instalar el paquete lirc.

apt-get install lirc

Y tendremos también que instalar Docker siguiendo estos pasos sino está ya instalado, en ese mismo enlace se dan algunas recomendaciones sobre rendimiento que pueden ser útiles.

Vídeo

Hay que hacer una pequeña modificación en el fichero /boot/config.txt para que el vídeo se reproduzca correctamente, tenemos que tener esto en el fichero:

[all]
dtoverlay=vc4-fkms-v3d

Y después solo queda reiniciar. A partir de aquí es posible que queramos hacer algunas tareas adicionales, por ejemplo igual queremos añadir como biblioteca de vídeo o audio un USB conectado o crear carpetas de unidad de red para acceder a ficheros remotos.

Docker

Aquí tenemos la imagen Docker, solo hay un parámetro para build que indica que salida de audio se va a utilizar, 1 para HDMI y 0 para la salida Jack.

FROM balenalib/raspberrypi3-debian:bullseye

ARG DEBIAN_FRONTEND=noninteractive
# 1 for HDMI, 0 for headphones
ARG     AUDIO=1

RUN	apt-get update				&& \
	apt-get -y purge openssl		&& \
	apt-get -y --purge autoremove		&& \
	apt-get dist-upgrade			&& \
	apt-get install	-y			\			
	uuid-dev		\
	upower			\
	alsa-base		\
	alsa-utils		\
	alsa-tools		\
	dbus-x11		\
	libraspberrypi0		\
	xterm			\
	xinput			\
	xinput-calibrator	\
	evemu-tools		\	
	libnspr4		\
	libwidevinecdm0		\
	libc6			\
	avahi-daemon		\
	libnss-mdns		\
	nano			\
	lirc			\
	lirc-compat-remotes	\
	bluez			\
	dumb-init		\
# Required for AirPlay Mirroring
	cmake					\
        libavahi-compat-libdnssd-dev		\
        libplist-dev				\
        libgstreamer1.0-dev			\
        libx264-dev				\
        libjpeg-dev				\
        libgstreamer-plugins-base1.0-dev	\
        libgstreamer-plugins-bad1.0-dev		\
        gstreamer1.0-plugins-ugly		\
        gstreamer1.0-tools			\
        gstreamer1.0-gl				\
        gstreamer1.0-gtk3			\
        git					\
        git-svn					\
        libssl-dev				\
# Python required for Netflix, Amazon...
	python3-pip		\
	build-essential		&& \
# Install some Python packages
	pip install setuptools pycryptodome pycryptodomex wheel pycrypto				&& \
	apt-get -y --purge autoremove									&& \
	rm -rf /var/lib/apt/lists/*

# besides kodi, we will install a few extra packages:
#  - ca-certificates              allows Kodi to properly establish HTTPS connections
#  - kodi-eventclients-kodi-send  allows us to shut down Kodi gracefully upon container termination
#  - kodi-game-libretro           allows Kodi to utilize Libretro cores as game add-ons
#  - kodi-game-libretro-*         Libretro cores
#  - kodi-inputstream-*           input stream add-ons
#  - kodi-peripheral-*            enables the use of gamepads, joysticks, game controllers, etc.
#  - kodi-pvr-*                   PVR add-ons
#  - kodi-screensaver-*           additional screensavers
#  - lirc,lirc-compat-remotes     enables the use of IR Remotes
#  - locales                      additional spoken language support (via x11docker --lang option)
#  - pulseaudio                   in case the user prefers PulseAudio instead of ALSA
#  - tzdata                       necessary for timezone selection
RUN packages="                                               \
    fbset                                                    \
    ca-certificates                                          \
    kodi                                                     \
    kodi-eventclients-kodi-send                              \
    kodi-inputstream-adaptive                                \
    kodi-inputstream-rtmp                                    \
    kodi-peripheral-joystick                                 \
    kodi-pvr-argustv                                         \
    kodi-pvr-dvblink                                         \
    kodi-pvr-dvbviewer                                       \
    kodi-pvr-filmon                                          \
    kodi-pvr-hdhomerun                                       \
    kodi-pvr-hts                                             \
    kodi-pvr-iptvsimple                                      \
    kodi-pvr-mediaportal-tvserver                            \
    kodi-pvr-mythtv                                          \
    kodi-pvr-nextpvr                                         \
    kodi-pvr-njoy                                            \
    kodi-pvr-pctv                                            \
    kodi-pvr-sledovanitv-cz                                  \
    kodi-pvr-stalker                                         \
    kodi-pvr-teleboy                                         \
    kodi-pvr-vbox                                            \
    kodi-pvr-vdr-vnsi                                        \
#    kodi-pvr-vuplus                                          \ # Not yet available
    kodi-pvr-wmc                                             \
    kodi-pvr-zattoo                                          \
    kodi-screensaver-biogenesis                              \
    kodi-screensaver-matrixtrails                            \
    kodi-screensaver-pyro                                    \
    kodi-screensaver-stars                                   \
    lirc                                                     \
    lirc-compat-remotes                                      \
    locales                                                  \
    libnss3                                                  \
    tzdata"                                               && \
                                                             \
    apt-get update                                        && \
    apt-get install -y $packages                          


# Audio settings
RUN     echo            \
"pcm.!default {         \n\
  type asym             \n\
  playback.pcm {        \n\
  type plug             \n\
  slave.pcm "output"    \n\
}                       \n\
capture.pcm {           \n\
  type plug             \n\
  slave.pcm "input"     \n\
  }                     \n\
}                       \n\
pcm.output {            \n\
  type hw               \n\
  card $AUDIO           \n\
}                       \n\
ctl.!default {          \n\
  type hw               \n\
  card $AUDIO           \n\
}" >> /etc/asound.conf

# Adding user
ENV	KODI_ID=1000
RUN     adduser \
	--uid $KODI_ID \
	--disabled-password \
	--gecos '' \
	--ingroup sudo \
	kodi 
RUN     echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN	usermod -a -G audio,video,input,dialout,plugdev,netdev,users,cdrom,tty kodi

# Install RpiPlay
WORKDIR /opt/vc
RUN     git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/include
RUN     git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/src
RUN	git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/lib

WORKDIR /repos
COPY    rpiplay.tar.gz .
RUN     tar -zxvf rpiplay.tar.gz
RUN	ls -la
WORKDIR /repos/rpiplay/build
RUN	ls -la
RUN	cmake ..
RUN	make -j 4
WORKDIR /
RUN	rm -rf /repos
RUN	rm -rf /rpiplay.tar.gz

# Plugins

WORKDIR	/plugins
ADD	--chown=kodi:sudo https://github.com/castagnait/repository.castagnait/raw/kodi/repository.castagnait-2.0.0.zip .
ADD	--chown=kodi:sudo https://k.slyguy.xyz/repository.slyguy.zip .
ADD	--chown=kodi:sudo https://github.com/Sandmann79/xbmc/releases/download/Repository/repository.sandmann79.plugins-1.0.4.zip .

# Configuration
EXPOSE	8080 9777/udp

VOLUME	/home/kodi
USER	kodi

COPY entrypoint.sh /usr/local/bin
CMD ["/usr/local/bin/entrypoint.sh"]

También usaremos el siguiente fichero Docker compose:

version: "3.7"
services:
  rpi-kodi:
    image: joursain/rpi-kodi:buster
    build:
      context: .
      dockerfile: Dockerfile
    container_name: "kodi"
    user: kodi
    network_mode: host
    restart: "no"
    privileged: true
    devices:
      - /dev/fb0:/dev/fb0
      - /dev/vchiq:/dev/vchiq
      - /dev/tty0:/dev/tty0
      - /dev/tty2:/dev/tty2
      - /dev/input:/dev/input
      - /dev/snd:/dev/snd
      - /dev/shm:/dev/shm
    volumes:
      - /var/run/dbus:/var/run/dbus
      - /mnt/media/kodi:/home/kodi
      - "/etc/timezone:/etc/timezone:ro"
      - "/etc/localtime:/etc/localtime:ro"
      - "/usr/bin/tvservice:/usr/bin/tvservice:ro"
    tmpfs:
      - /tmp
    environment:
      - DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket

Para arrancar lo haremos de la forma habitual (opcionalmente podemos añadir -d si queremos que arranque en segundo plano)

docker-compose up [-d]

Veremos la salida (si no hemos seleccionado -d) de la ejecución y en cuanto haya terminado aparecerá Kodi en pantalla. Esta imagen permite el uso de teclado así que en cuanto esté operativa podremos usar el teclado y escuchar sonidos a medida que se seleccionen diferentes opciones, esto verificará que el sonido está bien configurado.

La carpeta home del usuario kodi (/home/kodi) que contiene la configuración de Kodi está compartida en un volumen por si hiciera falta hacer una copia de seguridad de la configuración o para añadir algún plugin externo se puede dejar en esta carpeta para poder instalarlo desde Kodi.

Al arrancar posiblemente saldrán varias ventanas relacionadas con plugins pidiendo activación, no hace falta hacerlo en un primer momento y se puede hacer más adelante.

Servicios

Es posible configurar algunos servicios de Kodi como por ejemplo UPnP o AirPlay de la siguiente forma:

  1. Vamos a configuración (el icono de la rueda)
  2. Icono System
  3. Services
  4. Aquí podemos habilitar UPnP/DLNA o AirPlay

En la parte de abajo hay una opción para poder ampliar las opciones de configuración (el nivel máximo es Expert)

Sonido

La imagen Docker por defecto al arrancar ya tiene el sonido habilitado, la configuración que deberíamos ver es la que está en:

  1. Vamos a configuración (el icono de la rueda)
  2. Icono System
  3. Opción audio
  4. Audio output device
  5. Marcamos Default (bcm2835 Headphones bcm2835 Headphones) ()

Como detalle si se están compartiendo los altavoces entre Raspberry y otro dispositivo es posible que la imagen arranque sin sonido, solo un dispositivo puede estar habilitado a la vez.

Plugins

Ahora vamos a configurar los diferentes plugins, la imagen Docker los coloca en la carpeta /plugins, antes de instalarlos hay que realizar algunas acciones en la imagen Docker.

  1. Vamos a configuración (el icono de la rueda).
  2. Add-ons.
  3. My add-ons.
  4. VideoPlayer InputStream.
  5. InputStream Adaptive.
  6. Enable.

Ahora tenemos que habilitar la opción de orígenes desconocidos para poder instalar los plugins

  1. Vamos a configuración (el icono de la rueda).
  2. System.
  3. Add-ons.
  4. Habilitamos Unknown sources y confirmamos el mensaje.

Para instalar los diferentes plugins tendremos que seguir estos pasos.

  1. Vamos a configuración (el icono de la rueda).
  2. Add-ons.
  3. Install from zip file (igual tenemos que usar los .. para subir a un nivel superior y poder ver la opción) y confirmamos el mensaje que saldrá, este mensaje solo nos indica que estos plugins no se actualizan automáticamente y habrá que hacerlo manualmente.
  4. Nos movemos a la carpeta /plugins (Root filesystem/plugins).
  5. Aquí se encuentran todos los plugins que iremos instalando.

Instalación

Todos los plugins se instalan de la misma forma, el procedimiento es:

  1. Desde la carpeta plugins del paso anterior buscamos el plugin que queramos instalar, habrá los siguientes:
    1. CastagnaIT para Netflix
    2. SlyGuy para HBO y Disney+
    3. Sandman para Amazon
  2. Una vez seleccionado e instalado, vamos a la opción Install From Repository (al mismo nivel que Install from zip File) y veremos varios repositorios, seleccionamos por el mismo nombre que antes el que interese.
  3. Si solicita componentes adicionales los instalamos también.

Al ejecutar algunos plugins es posible que solicite instalar Widevine CDM, es necesario instalarlo para que funcione el vídeo. Este proceso puede llevar un rato. Es posible también que falle la instalación o de un error al abrir el vídeo, en ese caso hay que apagar el contenedor y volverlo a levantar.

Configuración

Ahora que están los plugins instalados vamos a configurarlos, simplemente hay que acceder a ellos a través de:

  1. Vamos a configuración (el icono de la rueda)
  2. Add-ons
  3. My addons / Video add ons / (plug-in de vídeo)

Básicamente todos los plugins tienen el mismo sistema, algunos pedirán los datos de la cuenta al principio y otros tendrán una sección especial. Cada plugin tiene varias opciones de configuración, por ejemplo idioma o subtítulos, merece la pena dedicarle un rato.

Televisión

Para poder ver la televisión tenemos que ir al apartado de Add-Ons en Sistema y luego en My-AddOns y finalmente instalamos PVR IPTV Simple Client, en la parte de Configure donde dice M3U playlist URL ponemos https://www.tdtchannels.com/lists/tv.m3u8 o https://www.tdtchannels.com/lists/tvradio.m3u8 si también queremos radio.

Ahora solo tenemos que ir al icono de TV que tenemos en la página principal en un lateral y esperar a que cargue los canales, saldrán muchos así que usar el buscador es una buena opción.

Notas

El código fuente aquí.

Como dato adicional se puede usar este mismo docker compose para cambiar Plex por Kodi.

No parece que de buen resultado usar zswap aquí, produce algunos errores al menos en Rpi3, es mejor usar zram.

De igual forma no podemos tener la memoría gráfica por debajo de 160, así que lo dejamos en 256GB.

Bonus

Esta imagen Docker admite AirPlay y Bluetooth, también el uso de infrarrojos si hay uno conectado aunque habría que incluirlo en docker-compose añadiendo – dev/lirc:/dev/lirc o – dev/lirc0:/dev/lirc0

Referencias

https://forum.kodi.tv/showthread.php?tid=365714
https://mundokodi.com/addon-hbo-max-en-kodi/
https://support.zoom.us/hc/en-us/articles/203680359-Protocols-for-Apple-screen-mirroring-AirPlay-
https://forums.raspberrypi.com/viewtopic.php?t=295008
https://github.com/CodaFog/kodi-rpi
https://github.com/rimago/rpi-kodi
https://kodi.wiki/view/AirPlay
https://www.genbeta.com/multimedia/como-ver-netflix-kodi-que-puede-ser-mejor-que-hacerlo-sus-aplicaciones-oficiales
https://www-genbeta-com.cdn.ampproject.org/v/s/www.genbeta.com/multimedia/netflix-hbo-pluto-tv-twitch-youtube-todas-grandes-plataformas-que-puedes-ver-kodi-como-hacerlo/amp?amp_gsa=1&amp_js_v=a6&usqp=mq331AQIKAGwASCAAgM%3D#amp_tf=De%20%251%24s&aoh=16301408042642&csi=0&referrer=https%3A%2F%2Fwww.google.com&ampshare=https%3A%2F%2Fwww.genbeta.com%2Fmultimedia%2Fnetflix-hbo-pluto-tv-twitch-youtube-todas-grandes-plataformas-que-puedes-ver-kodi-como-hacerlo
https://www.balena.io/docs/learn/more/masterclasses/services-masterclass/#5-running-systemd-in-a-service
https://wiki.ubuntu.com/LircHowto
https://www.linuxquestions.org/questions/linux-from-scratch-13/blfs-svn-dbus-won%27t-start-886338/
https://forums.balena.io/t/dbus-failed-to-connect-to-socket-host-run-dbus-system/3107
https://www.balena.io/docs/learn/develop/runtime/#dbus-communication-with-host-os

Kodi y plataformas de streaming (Netflix, HBO, Amazon, Disney+)

Docker Raspberry: Servidor multimedia con Compose

Actualizado el 19 de Octubre de 2021

Con la llegada de Raspberry surgieron muchas distribuciones multimedia como Kodi, OSMC, OpenELEC,… todas tienen en común que vienen preparadas para ser utilizadas sin ninguna instalación y con su propia interfaz gráfica y proporcionan plugins para ampliar la funcionalidad, por ejemplo emisoras de radio o clientes Torrent.

Estas distribuciones al estar dedicadas en exclusiva a funciones multimedia no permiten realizar otras acciones. Puede ser que no estemos todo el tiempo usándola de este modo y queramos darle más usos, sobre todo con los últimos modelos que son más potentes así que una alternativa es usar contenedores Docker, en este caso se van a crear tres contenedores:

  • Transmission: Un cliente torrent personalizable.
  • Mopidy: Servidor de audio compatible con Spotify.
  • Plex: Un servidor de streaming de video.
Docker

Dockerfile

Todos los contenedores se pueden ejecutar por separado usando los siguientes ficheros Dockerfile o usando el fichero compose que se indica al final. El código fuente está aquí. Vamos a suponer que las carpetas locales están bajo el raíz /mnt/media

Docker Mopidy

El fichero Docker para Mopidy está basado en este artículo, tiene consejos y recomendaciones sobre la configuración de sonido, la variable de entorno AUDIO maneja la salida de audio. Hay que tener en cuenta que si los altavoces están compartidos, por ejemplo tanto un ordenador como una Raspberry están conectados solo uno puede estar usándolo a la vez y el otro dispositivo tendrá que cambiar su salida de audio para liberar el canal.

# 0. Official armv32 image
FROM debian:latest

LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.0.0"
LABEL "guru.raraavis.release-date"="20/03/2021"
LABEL "guru.raraavis.description"="Raspberry Pi 3 image with Mopidy"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
        # Use 1 for headphones, 0 for HDMI
ARG AUDIO=1

# 2. Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y \
                wget \
                lsb-release \
                gnupg2 \
                gnupg \
                gnupg1 \
                python3-dev \
                python3-pip \
                libffi-dev \
                sudo \
                gosu \
                dumb-init && \
                cron && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 3. Add mopidy repository
RUN     wget -q -O - https://apt.mopidy.com/mopidy.gpg | apt-key add -
RUN     DEBIAN_CODENAME=`lsb_release -sc` && \
        wget -vO /etc/apt/sources.list.d/mopidy.list "https://apt.mopidy.com/$DEBIAN_CODENAME.list"

# 4. Install pip packages and Mopidy
RUN     apt-get update && \
        apt-get install -y \
                mopidy \
                libspotify-dev && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*
RUN     python3 -m pip install \
                pyspotify \
                Mopidy-Local \
                Mopidy-Iris \
                Mopidy-Mobile \
                Mopidy-Spotify \
                Mopidy-Mopify

# 5. Configure user
RUN     usermod -d /mnt/mopidy mopidy
RUN     echo "mopidy ALL=NOPASSWD: /usr/local/lib/python3.7/dist-packages/mopidy_iris/system.sh" >> /etc/sudoers

# 6. Create folders
WORKDIR /mnt/mopidy
RUN     mkdir /mnt/mopidy/data
RUN     mkdir /mnt/mopidy/cache
COPY    scripts/ scripts/

# 7. Configuration local scan
RUN     (crontab -l 2>/dev/null || true; echo "0 */1 * * * mopidy --config  /mnt/mopidy/config/mopidy.conf local scan") | crontab -
RUN     ln -sf /mnt/mopidy/config/mopidy.conf /etc/mopidy/mopidy.conf

# 8. Audio settings
RUN     echo            \
"pcm.!default {         \n\
  type asym             \n\
  playback.pcm {        \n\
  type plug             \n\
  slave.pcm "output"    \n\
}                       \n\
capture.pcm {           \n\
  type plug             \n\
  slave.pcm "input"     \n\
  }                     \n\
}                       \n\
pcm.output {            \n\
  type hw               \n\
  card $AUDIO           \n\
}                       \n\
ctl.!default {          \n\
  type hw               \n\
  card $AUDIO           \n\
}" >> /etc/asound.conf


# 9. Container
EXPOSE  6680
VOLUME  /mnt/mopidy/config
VOLUME  /mnt/mopidy/local

ENTRYPOINT ["/usr/bin/dumb-init","--"]
CMD ["/bin/sh", "scripts/entry_point.sh", "mopidy", "--config", "/mnt/mopidy/config/mopidy.conf"]

Instalamos algunos paquetes de sistema necesarios para poder instalar Mopidy, el paquete curl se instala para poder usar el comando HEALTHCHECK en el fichero docker-compose.yml

Instalamos Mopidy usando el repositorio oficial, en este caso usamos Debian porque el repositorio está orientado a esta distribución de tal forma que al usar el comando lsb_release podemos parametrizar el acceso al repositorio.

Una vez instalado Mopidy instalamos algunos plugins (se podrían instalar más). Usamos el usuario creado mopidy para arrancar el servicio y configuramos las carpetas donde guardar la configuración (/config) y la carpeta de archivos locales (/local) para el plugin local.

Como el plugin local tiene que escanear si hay cambios en las carpetas añadimos una tarea cron que se ejecutará cada hora para refrescar carpetas. Este mismo comando se puede ejecutar desde la opción Start local scan para eso añadimos al usuario mopidy al fichero sudoers para que pueda lanzar el comando. Este comando lanzado a través de la web utilizar el fichero de configuración por defecto así que creamos un enlace simbólico al que vamos a usar.

En este caso el usuario mopidy no hace falta que tenga derechos de root por seguridad, aunque es verdad que sería necesario para actualizar algunos plugins en este caso es más práctico actualizar el contenedor.

También se usa el paquete dump-init, el proceso mopidy no está orientado a ser usado como proceso padre así que es difícil gestionar su ciclo de vida si sucede algo y es necesario reiniciarlo, por eso gestionamos el contenedor a través de dumb-init que es el proceso padre del contenedor y el que arranca el proceso de mopidy.

Para obtener un id de cliente y un secret de Spotify podemos ir aquí.

Compilación y ejecución

Para compilar podemos usar:
docker build –tag user/mopidy -f Dockerfile.mopidy .

Para ejecutar podemos usar:
docker container run \
–init \
–publish 6680:6680 \
–device /dev/snd \
–volume /mnt/media/mopidy/config:/mnt/mopidy/config \
–volume /mnt/media/mopidy/local:/mnt/mopidy/local \
–env RA_UUID=1000 \
–env RA_GUID=1000 \
–env RA_SRVC=mopidy \
–env RA_FLDR=/mnt/mopidy \
–detach \
user/mopidy

Un detalle importante es usar –device /dev/snd para que el dispositivo de sonido sea utilizado dentro del contenedor. Una vez que arranquemos el contenedor tendremos que hacer login en Spotify y puede que también refrescar el token y la lista de reproducción.

Migración

Si ya tenemos una instalación de Mopidy podemos coger el fichero de configuración buscándolo con:
sudo find / -name mopidy*
Normalmente estará en el home del usuario en la carpeta .config, por ejemplo /home/pi/.config/mopidy/mopidy.conf

Docker Transmission

El fichero Docker para Transmission está basado en este artículo aunque también existe una variante de linuxserver.

# 0. Official armv32 image
FROM ubuntu:latest

LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.0.0"
LABEL "guru.raraavis.release-date"="20/03/2021"
LABEL "guru.raraavis.description"="Raspberry Pi 3 image with Transmission"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ

# 2. Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y \
                gosu \
                software-properties-common \
                gnupg2 \
                gnupg \
                gnupg1 && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 3. Add transmission repository and install Transmission
RUN     add-apt-repository ppa:transmissionbt/ppa && \
        apt-get update && \
        apt-get install -y \
                transmission-cli \
                transmission-common \
                transmission-daemon && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 4. Configure user
RUN     usermod -d /mnt/transmission debian-transmission

# 5. Create folder
WORKDIR /mnt/transmission
COPY    scripts/ scripts/

# 6. Container
EXPOSE  9091
VOLUME  /mnt/transmission/config
VOLUME  /mnt/transmission/watch
VOLUME  /mnt/transmission/download
VOLUME  /mnt/transmission/temp

ENTRYPOINT ["/bin/sh", "scripts/entry_point.sh"]
CMD ["/usr/bin/transmission-daemon", "-g", "/mnt/transmission/config", "-f", "-x", "/mnt/transmission/config/trans.PID"]

Para evitar un error parecido a este:
UDP Failed to set receive buffer: requested 4194304, got 425984 (tr-udp.c:84)

Tenemos que editar algunos valores del kernel en el host estos valores no se pueden modificar en el contenedor al ser valores compartidos. Hay varias formas de editar estos valores, una forma sería la siguiente:

Primero editamos el fichero sysctl.conf y añadimos lo siguiente (hay varias formas de editar sysctl).

net.core.rmem_max = 16777216
net.core.wmem_max = 4194304

Y refrescamos los valores con sysctl -p

Tenemos también varios volúmenes configurados:

  • /mnt/transmission/config para la configuración
  • /mnt/transmission/watch la carpeta watch donde buscar nuevos ficheros torrent para descargar
  • /mnt/transmission/download carpeta de descargas
  • /mnt/transmission/temp la carpeta temporal

Se usa un script especial para arrancar el contenedor y configurar los permisos, los detalles aquí.

Compilación y ejecución

Para compilar podemos usar:
docker build –tag user/transmission -f Dockerfile.transmission .

Para ejecutar podemos usar:
docker container run –init -p 9091:9091 -p 51413:51413 \
–volume /mnt/media/transmission/config:/mnt/transmission/config \
–volume /mnt/media/transmission/download:/mnt/transmission/download \
–volume /mnt/media/transmission/temp:/mnt/transmission/temp \
–volume /mnt/media/transmission/watch:/mnt/transmission/watch \
–env RA_UUID=1000 \
–env RA_GUID=1000 \
–env RA_SRVC=debian-transmission \
–env RA_FLDR=/mnt/transmission
\
–detach user/transmission

Migración

La carpeta de Transmission tiene no solo la configuración en el fichero settings.json también otras carpetas donde se guardan los ficheros torrent descargados y las estadísticas, se puede encontrar con sudo find / -name settings.json y normalmente estará ubicada en /var/lib/transmission-daemon
Hay que copiar todo este contenido dentro de la carpeta /mnt/transmission/config y actualizar el fichero settings.json para que apunte a las carpetas del contenedor (/mnt/transmission/download, ….)

Docker Plex

En este caso se ha usado un contenedor ya existente de Plex suministrado por linuxserver.

Compilación y ejecución

En este caso no hace falta compilar al ser una imagen sacada del registro de contenedores. Para ejecutar podemos ver los detalles aquí.

Migración

Si tenemos cuenta en Plex con hacer login se puede recuperar gran parte de la configuración, la carpeta con el resto de la información se puede sacar de aquí /var/lib/plexmediaserver/Library/Application Support/Plex Media Server tal y como se cuenta aquí.

Docker Compose

Ahora todos los contenedores se arrancan a la vez usando este fichero:

version: "3"
services:  
  mopidy:    
    container_name: mopidy    
    image: raraavis/mopidy    
    build:      
      context: ..
      dockerfile: media/mopidy/Dockerfile.mopidy
    ports:      
      - 6680:6680    
    volumes:      
      - /mnt/media/mopidy/config:/mnt/mopidy/config
      - /mnt/media/mopidy/local:/mnt/mopidy/local
    devices:
      - /dev/snd
    environment:      
      - RA_UUID=1000      
      - RA_GUID=1000      
      - RA_SRVC=mopidy
      - RA_FLDR=/mnt/mopidy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:6680 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'"]      
      interval: 30m
      timeout: 30s
      retries: 3
      start_period: 30s
    restart: unless-stopped
  transmission:    
    container_name: transmission    
    image: raraavis/transmission    
    build:      
      context: ..
      dockerfile: media/transmission/Dockerfile.transmission
    ports:      
      - 9091:9091      
      - 51413:51413    
    volumes:      
      - /mnt/media/transmission/config:/mnt/transmission/config
      - /mnt/media/temp:/mnt/transmission/temp
      - /mnt/media/download:/mnt/transmission/download
      - /mnt/media/watch:/mnt/transmission/watch
    environment:      
      - RA_UUID=1000      
      - RA_GUID=1000      
      - RA_SRVC=debian-transmission
      - RA_FLDR=/mnt/transmission
  plex:    
    container_name: plex    
    image: ghcr.io/linuxserver/plex    
    container_name: plex    
    network_mode: host    
    environment:      
      - PUID=105      
      - PGID=106      
      - VERSION=docker      
    #- PLEX_CLAIM= see .env file
    volumes:      
      - /mnt/media/plex/config:/config      
     #- /mnt/media/plex/tv:/tv      
      - /mnt/media/movies:/movies    
  restart: unless-stopped

Algunos aspectos a destacar son que el servidor Mopidy usa un health check para verificar si sigue activo, esto es debido a un bug reportado a Mopidy, el comando lo que hace es verificar si el servidor sigue activo y sino lo está mata el contenedor para que se reinicie usando el comando restart, más información aquí.

Para rellenar PLEX_CLAIM puede usarse esta página, esta variable está en un fichero de configuración separado .env otra opción sería usar secrets pero esto condiciona a la creación de un swarm.

Para docker transmission se pueden configurar varias carpetas pero si algunas de ellas está anidadas dentro de otras pueden ocultarse entre ellas de tal forma que no verá el contenedor los ficheros que dejemos, esto no es algo que suceda usando la línea de comandos de docker. La forma de evitarlo es indicar solo la carpeta raíz.

La estructura de carpetas es la misma que aparece en el código fuente.

Una vez que las imágenes han sido creadas y subidas al repositorio se puede comentar la sección de build.

Compose: compilación y ejecución

El fichero está configurado para compilar los ficheros dockerfile indicados usando docker-compose build el nombre de la imagen será el indicado en image.

Para ejecutar simplemente usamos docker-compose up y podremos ir viendo los mensajes o si lo queremos en segundo plano agregamos la opción –detach y quedaría docker-compose up –detach

Puede pasar que algunos valores del contenedor se mantengan entre ejecuciones y veamos mensajes como:
WARNING: Service «transmission» is using volume «/watch» from the previous container. Host mapping «/watch» has no effect. Remove the existing containers (with docker-compose rm transmission) to use the host volume mapping.

Solo hay que fijarse en el mensaje y ejecutar la instrucción que indica docker-compose rm transmission

Posible arquitectura

Se puede plantear lo siguiente:

  • Los contenedores Docker se colocan en la misma Rpi usando Docker compose.
  • Las carpetas de descarga apuntan a un almacenamiento externo, para saber como hacerlo se puede ver aquí.
  • La carpeta watch se puede crear como un recurso compartido vía Samba para poder dejar los ficheros torrent desde cualquier otra máquina y que se vayan descargando sin tener que hacerlo a través del cliente web.
  • Haría falta un cliente Plex que puede ser una SmartTv, u otra Raspberry Pi con esta distribución.
  • Opcionalmente se puede colocar por delante el proxy Apache como se indica más abajo.

(Opcional) Proxy Apache

Es posible usar un servidor Apache que sirva de proxy para poder acceder a los servicios de forma más cómoda usando una URL con nombre en lugar de a través de la IP y del puerto, en cualquier caso tiene que haber un DNS que pueda resolver los nombres.

Tenemos que habilitar algunos módulos, para permitir que funcionen los websockets cors.

a2enmod headers
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
a2enmod headers

Vamos a suponer que la IP del contenedor es 192.168.1.2, y que el dominio es rpi.local, hay que crear los siguientes ficheros de configuración de apache y colocarlos en la carpeta /etc/apache2/sites-available

001-transmission.conf

<VirtualHost *:80>
      ServerName torrent.rpi.local
      ServerAlias torrent
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:9091/
      ProxyPassReverse / http://192.168.1.2:9091/
      CustomLog /var/log/apache2/transmission.log combined
      ErrorLog /var/log/apache2/transmission.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 001-transmission.conf

002-mopidy.conf

<VirtualHost *:80>
      ServerName spotify.rpi.local
      ServerAlias spotify
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:6680/
      ProxyPassReverse / http://192.168.1.2:6680/
      CustomLog /var/log/apache2/mopidy.log combined
      ErrorLog /var/log/apache2/mopidy.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 002-mopidy.conf

003-plex.conf

<VirtualHost *:80>
      ServerName plex.rpi.local
      ServerAlias plex
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:32400/
      ProxyPassReverse / http://192.168.1.2:32400/
      CustomLog /var/log/apache2/plex.log combined
      ErrorLog /var/log/apache2/plex.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos este comando para los diferentes virtual host, por ejemplo:

sudo a2ensite 003-plex.conf

Docker Raspberry: Servidor multimedia con Compose

Docker Raspberry: Jupyter kernels

Actualizado el 29 de Octubre de 2022

Docker

Jupyter tiene un sistema basado en kernels, que son procesos de ejecución independientes en un lenguaje de programación concreto, de tal forma que en Jupyter pueden convivir kernels ejecutándose en el mismo o en varios lenguajes.

En Python es posible crear entornos virtuales con configuraciones concretas de versiones de Python y paquetes, podemos usar esta idea junto con los kernels de Jupyter para crear diferentes kernels de Python.

Por ejemplo, vamos a suponer para este caso que queremos crear cuatro entornos de Python, cada una de ellas orientado a un propósito diferente con diferentes paquetes.

  • Un entorno con Python 3.9 orientado a IA pero usando la versión de TensorFlow lite que es la recomendada para Raspberry PI incluyendo los paquetes más habituales en ML (torch, pandas, keras, scikit, numpy, …) y algunos paquetes de utilidad (split-folder, BeautifulSoup4, …)
  • Un segundo entorno similar al anterior pero con la versión de TensorFlow completa compilada para Raspberry PI junto con algunos paquetes adicionales como keras-tuner.
  • Un tercer entorno similar al anterior pero orientado a Reinforcement Learning (gym, keras-rl, etc…)
  • Un cuarto entorno de ámbito más matemático (plotly, sympy, …)

En general los comandos que se usarán a continuación están pensados para Python >= 3.3, más adelante se mostrará una configuración para Docker.

Configuración

La configuración incluye dos pasos, la creación del entorno virtual en Python y la creación del kernel asociado al mismo, en este ejemplo vamos a lanzar los comandos directamente desde la consola de Jupyter.

Entorno virtual

Si no lo estuviera ya instalamos el paquete venv para poder crear los entornos, esto hay que hacerlo para cada uno de las versiones de Python que queramos usar:

apt-get install python3-venv

Si usamos la consola de Jupyter es más útil arrancar el shell bash, simplemente ejecutando bash.

Hay que tener en cuenta que si queremos crear los entornos de Python con una versión específica, por ejemplo por compatibilidad de librerías tendremos que usar el binario exacto de esa versión, por ejemplo python3.7 o python3.8, etc…

Nos movemos a la carpeta donde queramos crear el entorno para el primer escenario, para crear el entorno virtual usamos:

python3 -m venv env

En general los ficheros .gitignore están configurados para ignorar la carpeta env por lo que usar este nombre es la opción recomendada.

Esto lo hacemos para todos los demás entornos, nos movemos a la carpeta donde queramos crear este entorno y ejecutamos el mismo comando que antes para que nos cree una carpeta env igual que antes.

Ahora si queremos activar un entornos concreto, dentro de la carpeta env correspondiente ejecutamos:

source env/bin/activate

El entorno se activa para cualquier código Python que se ejecute a partir de ese momento dentro de esa carpeta por lo que se puede crear un entorno y usarlo para varios proyectos.

Si intentamos lanzar Python veremos que se ejecuta con la versión que hemos configurado para esa carpeta, por ejemplo si queremos ver la versión podemos usar:

python3 --version

Cuando queramos desactivar un entorno hacemos:

deactivate

Para eliminar el entorno virtual simplemente tenemos que borrar la carpeta que se ha creado.

rm -r env

Kernel

Para cada uno de los kernels que vamos a crear tendremos que hacer los siguientes pasos, esto habrá que hacerlo dentro de la carpeta de cada entorno y habiendo activado previamente el entorno.

Instalamos el paquete ipykernel que nos proporcionará el kernel de Python para Jupyter, este paquete a su vez requiere del paquete wheel.

python3 -m pip install --user wheel
python3 -m pip install --user ipykernel

Con esta opción –user instalaríamos los paquetes dentro de la carpeta del usuario que es la opción recomendada.

Puede ser que tengamos un error al ejecutar desde el entorno de Jupyter (ERROR: Can not perform a ‘–user’ install. User site-packages are not visible in this virtualenv) en ese caso se puede ejecutar el mismo comando sin la opción –user.

Una vez hecho esto nos movemos a la carpeta de uno de ellos, en concreto vamos a empezar por el primer escenario y vamos a instalar el kernel correspondiente.

python3 -m ipykernel install --user --display-name='Python AI' --name=envpyai

–name se refiere al nombre del kernel que veremos al usar el comando list y –display-name se refiere al nombre que veremos en Jupyter Notebook, usamos –user para instalar el kernel en la carpeta del usuario y no tener problemas de permisos, si quisiéramos instalarlo a nivel global para todos los usuarios tendríamos que ejecutarlo como root.

El comando anterior emitirá una respuesta similar a la siguiente para indicar que todo ha ido bien, en la ruta que se muestra hay un archivo llamado kernel.json que contiene toda la configuración del kernel.

Installed kernelspec env in /home/jupyter/.local/share/jupyter/kernels/env

Ahora hacemos lo mismo para el resto de configuraciones, nos movemos a la carpeta y activamos el entorno correspondiente antes de ejecutar el comando anterior.

Hay que tener en cuenta que al ser entornos virtuales vienen vacíos por lo que habrá que instalar todos los paquetes que sean necesarios para poder desarrollar.

Para ver los kernel instalados podemos usar este comando, como detalle no se pueden tener dos kernel con el mismo nombre.

jupyter kernelspec list

SI queremos eliminar algún kernel, por ejemplo la versión para Python AI haríamos:

jupyter kernelspec uninstall envpyai

Verificación

Sí todo ha ido bien veremos algo como:

Si abrimos una consola de Python veremos la versión directamente, y para verificarla en un notebook podemos ejecutar el siguiente código:

import sys
print(sys.version)

No hay que usar el siguiente código que mostrará la versión global de Python.

!python3 --version

Desde la parte superior derecha o desde el menú podremos cambiar de kernel.

Dockerfile

Este sería un ejemplo de Dockerfile que resume todos los conceptos anteriores, puede que en Docker tener los entornos virtuales no tenga sentido, pero por comodidad vamos a hacerlo en este caso.

En este caso como estamos heredando de la imagen de Jupyter creada previamente cambiamos al usuario root ya que vamos a instalar algunos paquetes.

Como cada comando RUN se ejecuta de forma aislada de los demás una forma es emular lo que haría el entorno virtual de Python para poder activarlos, otra forma es activar los entornos usando el mismo comando que es lo que se ha hecho en este caso.

FROM joursain/rpi-jupyterlab:bullseye
LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.3.0"
LABEL "guru.raraavis.release-date"="23/05/2022"
LABEL "guru.raraavis.description"="Raspberry Pi image with JupyterLab Kernels"

# 0. Prepare system
# 0.1 Required for Matplotlib
USER    root
RUN     apt-get update -y && apt-get install -y \
                libopenjp2-7 \
                libjpeg62-turbo \
                libtiff5 \
                libhdf5-dev \
                libpng-dev \
                libavcodec-dev \
                libavformat-dev \
                libswscale-dev \
                libgtk-3-dev \
                unrar-free \
                # Required for nbconvert (to PDF)
                pandoc \
                libxslt1.1 \
                texlive-xetex \
                # Required for RL
                ffmpeg \
                mediainfo \
                xvfb \
                openmpi-bin \
                openmpi-common \
                libxcb1 \
                libopenmpi3 \
                libpomp-dev \
                libomp5 \
                libopenblas-dev \
                libopenmpi-dev && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*

# 1. Create virtual environments
WORKDIR /home/jupyter/ai
COPY --chown=jupyter    wheels/tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl .
COPY --chown=jupyter    wheels/torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl .
COPY --chown=jupyter    wheels/ale_py-0.7.5+8f3bc3b-cp39-cp39-linux_aarch64.whl .

RUN     chown -R jupyter:jupyter /home/jupyter/ai
USER    jupyter

RUN     python3 -m venv envailite
RUN     source envailite/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                dask[complete]                  \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                scikit-image                    \
                pandas                          \
                matplotlib                      \
                split-folders                   \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                mlflow                          \
                pyforest                        \
                html5lib                        \
                BeautifulSoup4                  \
                scrapy                          \
                requests                        \
                bokeh                           \
                plotly                          \
                lxml                            \
                graphviz                        \
                split-folders                   \
                nltk                            && \
        python3 -m pip install --extra-index-url https://google-coral.github.io/py-repo/ tflite_runtime && \
        python3 -m pip install torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl && \
        python3 -m ipykernel install --user --display-name='Python AI Lite' --name=envpyailite && \
        deactivate


RUN     python3 -m venv envai
RUN     source envai/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                dask[complete]                  \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                scikit-image                    \
                pandas                          \
                matplotlib                      \
                split-folders                   \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                mlflow                          \
                pyforest                        \
                html5lib                        \
                BeautifulSoup4                  \
                scrapy                          \
                requests                        \
                bokeh                           \
                plotly                          \
                sympy                           \
                lxml                            \
                graphviz                        \
                h5py                            \
                kaggle                          \
                split-folders                   \
                keras-tuner                     \
                nltk                            && \
        python3 -m pip install  \
                torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl \
                tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl && \
        python3 -m ipykernel install --user --display-name='Python AI' --name=envpyai && \
        deactivate

RUN     python3 -m venv envpyrl
RUN     source envpyrl/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install  \
                torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl \
                tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                dask[complete]                  \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                scikit-image                    \
                pandas                          \
                matplotlib                      \
                split-folders                   \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                mlflow                          \
                pyforest                        \
                html5lib                        \
                BeautifulSoup4                  \
                scrapy                          \
                requests                        \
                bokeh                           \
                plotly                          \
                sympy                           \
                lxml                            \
                graphviz                        \
                nltk                            \
                gym                             \
                h5py                            \
                pillow                          \
                keras-rl2                       \
                pyvirtualdisplay                \
                keras-tuner                     && \
        python3 -m pip install  \
                ale_py-0.7.5+8f3bc3b-cp39-cp39-linux_aarch64.whl \
                torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl \
                tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl && \
        python3 -m ipykernel install --user --display-name='Python RL' --name=envpyrl && \
        deactivate


RUN     python3 -m venv envmaths
RUN     source envmaths/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                pandas                          \
                matplotlib                      \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                requests                        \
                bokeh                           \
                plotly                          \
                sympy                           \
                lxml                            && \
        python3 -m ipykernel install --user --display-name='Python Maths' --name=envmaths && \
        deactivate

RUN     rm torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl
RUN     rm tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl

Compilación

La compilación no tiene ninguna novedad, en este caso el proceso es un poco más largo.

docker image build --tag user/image_name -f Dockerfile.ai .

Ejecución

La ejecución tampoco tiene modificaciones.

docker container run --init -p 8888:8888 --detach --volume /home/pi/jupyter:/home/jupyter/notebooks id_imagen

Referencias

https://janakiev.com/blog/jupyter-virtual-envs/
https://queirozf.com/entries/jupyter-kernels-how-to-add-change-remove

Docker Raspberry: Jupyter kernels

Docker Raspberry: Jupyter TensorFlow

Docker

Es normal que al usar Jupyter se use Tensorflow, siguiendo con el ejemplo de Docker anterior voy a poner dos ejemplos, uno para usar una configuración de Docker con la última versión de TensorFlow y otra para tener dos versiones de TensorFlow, la correspondiente a la rama 1.x y otra con la versión 2.x.

TensorFlow 1.x y 2.x

El siguiente DockerFile está basado en el anterior así que solo se comentarán las diferencias.

FROM ubuntu:latest

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
ENV NODE_OPTIONS=--max-old-space-size=768

# 2. Install packages
# 2.1 Update system and install Jupyter
RUN apt-get update && apt-get -y upgrade && \
        apt-get install -y --no-install-recommends libhdf5-dev && \
        apt-get install -y --no-install-recommends tzdata && \
        apt-get install -y --no-install-recommends libzbar-dev libzbar0 && \
        apt-get install -y --no-install-recommends build-essential python3-pip python3-dev python3-venv && \
        apt-get install -y --no-install-recommends git && \
        apt-get install -y --no-install-recommends software-properties-common && \
        python3 -m pip install --upgrade virtualenv && \
        python3 -m pip install --upgrade wheel && \
        python3 -m pip install --upgrade ipykernel && \
        python3 -m pip install --upgrade pip && \
        python3 -m pip install --upgrade setuptools && \
# 2.2 Build git extension
        python3 -m pip install jupyter && \
        apt-get install -y --no-install-recommends nodejs && \
        apt-get install -y --no-install-recommends npm && \
        python3 -m pip install jupyterlab && \
        python3 -m pip install --upgrade jupyterlab-git && \
        jupyter lab build --minimize=False && \
# 2.3 Clean
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*

# 3 Install TensorFlow
RUN python3 -m pip install --no-cache-dir --force-reinstall grpcio
# 3.1 Install Tensorflow 1 for Python 3.7
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get install -y python3.7 python3.7-dev python3.7-venv
RUN python3.7 -m pip install --upgrade pip && python3.7 -m pip install setuptools
COPY tensorflow-1.14.0-cp37-none-linux_armv7l.whl ./
RUN python3.7 -m pip install tensorflow-1.14.0-cp37-none-linux_armv7l.whl
# 3.2 Install Tensorflow 2 for Python 3.8
COPY tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl ./
RUN python3 -m pip install tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl

# 4. Add Jupyter user
RUN adduser --disabled-password --gecos '' jupyter
RUN adduser jupyter sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER jupyter
WORKDIR /home/jupyter/
RUN chmod a+rwx /home/jupyter/

# 5. Folder to download git files
RUN mkdir /home/jupyter/notebooks

# 6. Execute jupyter
CMD ["jupyter", "lab", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/home/jupyter/notebooks", "--NotebookApp.token=''", "--NotebookApp.password]

Hay que instalar algunos paquetes nuevos como wheel, virtualenv o ipykernel para poder usar varias versiones de Python en Jupyter a través del uso de kernels, esto se comentará más adelante para configuraciones con varias versiones de TensorFlow.

Instalamos libhdf5-dev y actualizamos el paquete setuptools para evitar un error durante la instalación del paquete grpcio y para evitar posibles errores en la creación de entornos virtuales.

El paquete software-properties-common es necesario para poder agregar repositorios personalizados que harán falta más adelante.

Install TensorFlow (3)

Este código ejecuta las dos versiones de Python, la correspondiente a la 1.14 y 2.0rc4 en el caso de grpcio lo instalamos previamente ya que va a ser usado por ambas versiones de TensorFlow y además necesita una configuración especial para no producir un error durante la instalación.

Install TensorFlow 1 para Python 3.7 (3.1)

La versión Ubuntu de RaspberryPi Buster incorpora la versión 3.8 de Python pero la última versión disponible de TensorFlow 1.x (1.4) solo funciona con Python 3.7 que no se puede instalar a través de los repositorios oficiales de Ubuntu, por lo que configuramos un repositorio alternativo (3.1) donde se encuentran versiones de Python para varias versiones de Ubuntu.

Instalamos los mismos paquetes Python que hemos instalado para la versión 3.8 pero en este caso para la versión 3.7 usando el repositorio anterior y copiamos el paquete de TensorFlow 1.4 (3.1) para poder instalarlo, el paquete lo podemos compilar o cogerlo de aquí.

Install Tensorflow 2 for Python 3.8

En el caso de TensorFlow 2 solo tenemos que copiar (3.2) la versión correspondiente que podemos copiarla del mismo repositorio anterior o compilarla como se describe aquí.

TensorFlow 2.x

La configuración de Docker sería muy similar a la anterior, simplemente quitamos la parte relacionada con TensorFlow 1.x

FROM ubuntu:latest

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
ENV NODE_OPTIONS=--max-old-space-size=768

# 2. Install packages
# 2.1 Update system and install Jupyter
RUN apt-get update && apt-get -y upgrade && \
        apt-get install -y --no-install-recommends libhdf5-dev && \
        apt-get install -y --no-install-recommends tzdata && \
        apt-get install -y --no-install-recommends libzbar-dev libzbar0 && \
        apt-get install -y --no-install-recommends build-essential python3-pip python3-dev python3-venv && \
        apt-get install -y --no-install-recommends git && \
        apt-get install -y --no-install-recommends software-properties-common && \
        python3 -m pip install --upgrade virtualenv && \
        python3 -m pip install --upgrade wheel && \
        python3 -m pip install --upgrade ipykernel && \
        python3 -m pip install --upgrade pip && \
        python3 -m pip install --upgrade setuptools && \
# 2.2 Build git extension
        python3 -m pip install jupyter && \
        apt-get install -y --no-install-recommends nodejs && \
        apt-get install -y --no-install-recommends npm && \
        python3 -m pip install jupyterlab && \
        python3 -m pip install --upgrade jupyterlab-git && \
        jupyter lab build --minimize=False && \
# 2.3 Clean
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*

# 3 Install TensorFlow
RUN python3 -m pip install --no-cache-dir --force-reinstall grpcio
COPY tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl ./
RUN python3 -m pip install tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl

# 4. Add Jupyter user
RUN adduser --disabled-password --gecos '' jupyter
RUN adduser jupyter sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER jupyter
WORKDIR /home/jupyter/
RUN chmod a+rwx /home/jupyter/

# 5. Folder to download git files
RUN mkdir /home/jupyter/notebooks

# 6. Execute jupyter
CMD ["jupyter", "lab", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/home/jupyter/notebooks", "--NotebookApp.token=''", "--NotebookApp.password=''"]

Compilación

La compilación no tiene ninguna novedad, en este caso el proceso es mucho más largo y para la configuración con dos versiones de TensorFlow puede llevar entre 8-10 horas en una Raspberry Pi 3.

docker image prune -f
docker image build --tag user/image_name .

Ejecución

La ejecución tampoco tiene modificaciones.

docker container run --init -p 8888:8888 --detach --volume /home/pi/jupyter:/home/jupyter/notebooks id_imagen
Docker Raspberry: Jupyter TensorFlow