Instalar medialab Server en un contenedor

medialab Server se publica como imagen de contenedor para linux/amd64 y linux/arm64:

ghcr.io/cristiancasapu/medialab-server

La imagen se compila a partir de los mismos paquetes Debian firmados que el repositorio apt (medialab-server, medialab-server-web, jellyfin-ffmpeg8); sus archivos de compilación son públicos en github.com/CristianCasapu/medialab-server-docker (GPL-2.0).

Etiquetas:

Etiqueta Significado
12.2-ml.10 una versión concreta; recomendada si quieres elegir cuándo actualizar
12.2 la versión más reciente basada en Jellyfin 12.2
latest la versión más reciente
12.2-ml10-deb12 la versión exacta de apt dentro de la imagen

Qué necesitas

  • Docker (o Podman, Unraid, Portainer, Kubernetes) en una máquina x86 o ARM de 64 bits.
  • Una carpeta para los ajustes (/config), otra para la caché (/cache) y tu carpeta de medios (/media).
  • El usuario y el grupo numéricos que deben ser propietarios de los ajustes: id -u e id -g de esa cuenta (PUID, PGID).

docker run

docker run -d --name medialab-server --restart unless-stopped \
  -p 8096:8096 -p 7359:7359/udp \
  -v /srv/medialab/config:/config \
  -v /srv/medialab/cache:/cache \
  -v /srv/media:/media:ro \
  -e PUID=1000 -e PGID=1000 -e TZ=Europe/Bucharest \
  ghcr.io/cristiancasapu/medialab-server:latest

Abre http://<server>:8096. En un servidor nuevo, el asistente de configuración pide el primer administrador, el idioma y tus bibliotecas (añádelas desde /media).

Para saltarte el asistente, deja que el contenedor cree el administrador. Pon la contraseña en un archivo, no en la línea de comandos:

printf '%s' 'a-long-password' > /srv/medialab/admin_password
docker run -d ... \
  -v /srv/medialab/admin_password:/run/secrets/admin_password:ro \
  -e MLW_ADMIN_USER=admin \
  -e MLW_ADMIN_PASSWORD_FILE=/run/secrets/admin_password \
  ghcr.io/cristiancasapu/medialab-server:latest

Esto solo funciona en un servidor nuevo; en un servidor ya configurado las variables se ignoran. La contraseña nunca aparece en los registros del contenedor.

Docker Compose

compose.yaml:

services:
  medialab-server:
    image: ghcr.io/cristiancasapu/medialab-server:12.2-ml.10
    container_name: medialab-server
    restart: unless-stopped
    ports:
      - "8096:8096"
      - "7359:7359/udp"
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /srv/media:/media:ro
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Europe/Bucharest
      MLW_SERVER_NAME: Home media
      MLW_ADMIN_USER: admin
      MLW_ADMIN_PASSWORD_FILE: /run/secrets/admin_password
    secrets:
      - admin_password

secrets:
  admin_password:
    file: ./secrets/admin_password
mkdir -p secrets && printf '%s' 'a-long-password' > secrets/admin_password
docker compose up -d
docker compose logs -f

En el repositorio hay un ejemplo completo con un archivo .env (examples/compose.yaml, examples/.env.example).

Unraid

  1. En la pestaña Docker, elige Add Container y pon en Template la URL https://raw.githubusercontent.com/CristianCasapu/medialab-server-docker/main/examples/unraid/medialab-server.xml (o copia el archivo a /boot/config/plugins/dockerMan/templates-user/).
  2. Comprueba las rutas: /config → /mnt/user/appdata/medialab-server, /media → tu recurso compartido de medios.
  3. PUID 99 y PGID 100 son los valores habituales de Unraid (nobody / users).
  4. GPU Intel o AMD: añade --device=/dev/dri en Extra Parameters y pon Hardware acceleration en qsv o vaapi. NVIDIA: instala el plugin Nvidia-Driver, añade --runtime=nvidia y ponlo en nvenc.
  5. Aplica y abre la WebUI.

Portainer

  1. Stacks → Add stack → Web editor y pega examples/portainer/stack.yml del repositorio.
  2. En Environment variables, carga examples/portainer/stack.env (o añade las variables a mano) y ajusta las rutas, PUID, PGID y TZ.
  3. Deploy the stack.

Las pilas de Portainer no pueden usar Docker secrets en un host independiente, así que el ejemplo pasa MLW_ADMIN_PASSWORD como variable de la pila. Solo se usa una vez; puedes borrarla tras el primer arranque.

Kubernetes

examples/kubernetes/medialab-server.yaml contiene un ConfigMap, dos PersistentVolumeClaims, un Deployment (una réplica, estrategia Recreate, health probes en /health), un Service y un Ingress:

kubectl create namespace medialab
kubectl -n medialab create secret generic medialab-secrets \
  --from-literal=admin-password='a-long-password' --from-literal=license-key='' --from-literal=repo-key=''
kubectl -n medialab apply -f medialab-server.yaml

Ajusta primero los tamaños de almacenamiento, el volumen de medios (se incluye un ejemplo de NFS) y el nombre de host. Ejecuta solo una réplica: el servidor guarda su base de datos en /config.

Aceleración por hardware

Intel (QSV / VAAPI) y AMD (VAAPI)

docker run ... --device /dev/dri:/dev/dri -e MLW_HW_ACCEL=qsv ...     # Intel
docker run ... --device /dev/dri:/dev/dri -e MLW_HW_ACCEL=vaapi ...   # AMD o Intel antigua

El contenedor añade por sí solo el usuario del servidor a los grupos propietarios de /dev/dri/*, así que no necesitas buscar los números de los grupos render y video. Los controladores vienen con jellyfin-ffmpeg8; la imagen amd64 también contiene el runtime OpenCL de Intel de Debian 12 para el mapeo de tonos HDR (las GPUs Intel muy recientes pueden necesitar un runtime más reciente que el que tiene Debian 12).

NVIDIA (NVENC / NVDEC)

Instala en el host el controlador de NVIDIA y el NVIDIA Container Toolkit, y después:

docker run ... --gpus all -e MLW_HW_ACCEL=nvenc ...

Con Compose, añade examples/compose.nvidia.yaml; en Kubernetes solicita nvidia.com/gpu: 1.

Tras el primer arranque, comprueba Panel de control → Reproducción → Transcodificación y reproduce un vídeo que necesite transcodificación; el registro de ffmpeg en /config/log muestra si se usó la GPU.

Plugins, ajustes y licencias desde fuera

Todo lo siguiente es opcional; la lista completa de variables está en docs/ENVIRONMENT.md del repositorio.

Ajustes del servidor. Por ejemplo:

-e MLW_PUBLISHED_URL=https://media.example.org \
-e MLW_KNOWN_PROXIES=172.16.0.0/12 \
-e MLW_UI_CULTURE=en-US -e MLW_METADATA_COUNTRY=US \
-e MLW_REMOTE_ACCESS=true

Se escriben en system.xml, network.xml y encoding.xml antes de que arranque el servidor. Con el valor predeterminado MLW_CONFIG_MODE=first-run, un valor se escribe en un servidor nuevo y de nuevo solo cuando cambias la variable, así que lo que cambies después en el panel se conserva. MLW_CONFIG_MODE=enforce los escribe en cada arranque. Cualquier otra propiedad puede establecerse con MLW_SYSTEM__<Property>, MLW_NETWORK__<Property> y MLW_ENCODING__<Property>.

Catálogo de plugins. El catálogo público de medialab.works y el repositorio oficial de Jellyfin se añaden por defecto (MLW_PLUGIN_REPOS=medialab,jellyfin). Si tu cuenta tiene una clave de repositorio, añádela con MLW_REPO_KEY (o MLW_REPO_KEY_FILE); queda enmascarada en los registros.

Plugins. Enuméralos en MLW_PLUGINS; se instalan antes de que arranque el servidor:

-e MLW_PLUGINS="jf-rosub,jf-registration@1.0.2"

Un plugin se encuentra por su slug (jf-rosub), su nombre o su GUID; @version fija una versión. El archivo se comprueba contra la suma de control del catálogo y se conserva la firma medialab (.dll.sig), así el panel muestra el plugin como verificado. Los plugins ya instalados no se descargan de nuevo; MLW_PLUGINS_UPDATE=true instala versiones más recientes al arrancar.

Ajustes de plugins. Monta el archivo de configuración del plugin (/config/plugins/configurations/<Assembly>.xml, p. ej. Rosub.xml) o define valores sueltos:

-e MLW_PLUGIN_ROSUB__ArchiveCacheDays=14

Claves de licencia. MLW_LICENSE_KEY, MLW_LICENSE_KEY_<SLUG> (p. ej. MLW_LICENSE_KEY_JF_ROSUB), sus formas _FILE y Docker secrets llamados mlw_license_key o mlw_license_key_<slug> se pasan sin cambios al proceso del servidor. Las versiones de los plugins que lo admiten las leen de ahí; con una versión antigua del plugin, introduce la clave en la página del plugin en el panel (se guarda en /config).

Toda variable también acepta <NAME>_FILE=/path/to/file, para los secrets de Docker y Kubernetes.

Actualizaciones

Descarga la nueva imagen y recrea el contenedor; los ajustes de /config se conservan:

docker pull ghcr.io/cristiancasapu/medialab-server:12.2-ml.10
docker stop medialab-server && docker rm medialab-server
docker run -d ... ghcr.io/cristiancasapu/medialab-server:12.2-ml.10     # mismas opciones que antes

Con Compose: cambia la etiqueta en compose.yaml (o conserva latest) y ejecuta docker compose pull && docker compose up -d. En Unraid: Check for Updates en la pestaña Docker. En Portainer: Recreate con Re-pull image.

El actualizador de la aplicación del paquete Debian no forma parte de la imagen. Self-Update by MediaLab muestra las versiones disponibles en un contenedor pero no instala paquetes; actualiza descargando la nueva etiqueta de la imagen (12.2-ml.N) y recreando el contenedor.

Copia de seguridad

/config guarda todo lo importante (ajustes, usuarios, base de datos, plugins, metadatos). Detén el contenedor y cópialo:

docker stop medialab-server
tar -C /srv/medialab -czf medialab-config-$(date +%F).tar.gz config
docker start medialab-server

/cache puede reconstruirse y no necesita copia de seguridad. Para restaurar, vuelve a poner config en su sitio y arranca la misma etiqueta de la imagen o una más reciente.

Resolución de problemas

  • Ver qué hizo el contenedor: docker logs medialab-server. Las líneas que empiezan por [medialab] proceden del script de arranque del contenedor (ajustes escritos, plugins instalados, administrador creado).
  • El contenedor se detiene enseguida con [medialab] ERROR: una variable tiene un valor no válido; el mensaje la indica.
  • «Permission denied»: PUID/PGID no coinciden con el propietario de las carpetas. El contenedor arregla /config y /cache por sí mismo, nunca /media. Si se arranca con --user, no cambia nada: las carpetas deben pertenecer ya a ese usuario.
  • Un ajuste del entorno no se aplica: en el modo first-run, un valor se escribe en un servidor nuevo o cuando cambia. Usa MLW_CONFIG_MODE=enforce para imponerlo.
  • Un plugin no se instala: el registro dice por qué (catálogo inaccesible, nombre no encontrado, suma de control incorrecta, ninguna versión para este servidor). El servidor arranca igualmente, salvo que MLW_PLUGINS_STRICT=true.
  • El contenedor aparece como unhealthy: el health check llama a /health en el puerto 8096 (o el puerto y la URL base de network.xml). El primer arranque puede tardar uno o dos minutos.
  • Detrás de un proxy inverso, los clientes reciben una dirección incorrecta: pon MLW_KNOWN_PROXIES con la dirección del proxy y MLW_PUBLISHED_URL con la URL pública.