Cómo actualizar Jellyfin 12 con Docker y poder volver atrás
Índice de contenidos
- Puntos clave
- ¿Qué cambia en Jellyfin 12 si lo tienes en Docker?
- ¿Desde qué versión puedes saltar a la 12?
- Cómo he probado la actualización
- Tres comprobaciones antes de cambiar la etiqueta
- Usuarios que solo se diferencian en mayúsculas
- Complementos de terceros
- Scripts y clientes que llaman a la API
- La copia de seguridad que te permite volver
- Actualizar Jellyfin 12 paso a paso
- ¿Cuánto tarda la migración y el primer escaneo?
- Qué cambia en tu servidor después de actualizar
- ¿Cómo vuelvo a la 10.11 si algo sale mal?
- Watchtower y la etiqueta latest
- Preguntas frecuentes
- ¿Puedo pasar de la 10.10.3 directamente a Jellyfin 12?
- ¿Se pierden los vistos, los favoritos y las listas al actualizar?
- ¿Conviene esperar a una 12.0.1?
- Conclusión
- Fuentes
Jellyfin 12.0 salió el 8 de septiembre de 2026 y reescribe la base de datos al arrancar, así que no hay vuelta atrás sin copia. En Docker, el camino seguro es parar el contenedor, copiar /config, quitar los complementos de terceros, cambiar la etiqueta a 12.0, migrar y lanzar un escaneo completo.
Jellyfin 12.0 ya es la versión estable, y la etiqueta latest de Docker apunta a ella desde el 8 de septiembre de 2026. Si seguiste la guía para instalar Jellyfin con Docker, tienes la 10.11.11 fijada y un aviso de esperar a la 12. La espera terminó, pero esta versión reescribe la base de datos en el primer arranque y la 10.11 ya no puede leerla después. Esta guía explica el camino para actualizar Jellyfin 12 que probé de principio a fin: comprobaciones previas, copia, migración, escaneo y vuelta atrás, con lo que pasó en cada paso.
Puntos clave
- La 12.0 se publicó en GitHub el 8 de septiembre de 2026 y a 14 de septiembre no hay ninguna 12.0.x. Las etiquetas
latest,12y12.0comparten imagen;10,10.11y10.11.11siguen en la 10.11.11. - El salto directo solo está admitido desde la 10.10.7 o cualquier 10.11.x. Antes de cambiar la etiqueta, quita los complementos de terceros y revisa que no haya usuarios que solo se distingan por mayúsculas.
- Con una biblioteca de prueba de 1144 elementos,
--mode MigrateSystemterminó en 3,66 s de mediana, y el primer escaneo posterior tardó 12 s frente a los 2 s del siguiente. - Una 10.11.11 arrancada sobre datos ya migrados figura como sana en Docker, pero responde con error 500 a cualquier petición autenticada. Lo único que me devolvió un servidor funcional fue la copia de
/confighecha con el servidor parado. - Los scripts que usan
X-Emby-Tokenoapi_keyreciben 401 en la 12.0, mientras queApiKeyy la cabeceraAuthorizationsiguen funcionando.
¿Qué cambia en Jellyfin 12 si lo tienes en Docker?
La 12.0 es la versión que se habría llamado 10.12.0. El proyecto quitó el "10." fijo del número para que el primer dígito cambie cuando la versión es grande, y el servidor se identifica como 12.0.0. El anuncio pone como ejemplo la 10.11.0, que reescribió la base de datos de la biblioteca con un número que parecía menor.
Para quien fija etiquetas en un compose, el cambio práctico está en Docker Hub. A 14 de septiembre de 2026, latest, 12 y 12.0 apuntan a la misma imagen (sha256:baba6304), actualizada el 8 de septiembre a las 01:36 UTC.
En cambio, 10, 10.11 y 10.11.11 comparten la imagen de la 10.11.11 del 6 de junio. No existen etiquetas 12.0.0, 12.0.1 ni 12.1. La documentación del contenedor lo avisa: latest sigue a la última estable "including through major and minor version bumps".
Esto es lo que cambia por dentro, comprobado en las dos imágenes arm64 y en las notas de la versión:
| 10.11.11 | 12.0 | |
|---|---|---|
| Entorno .NET | 9.0.16 | 10.0.11 |
| FFmpeg que detecta al arrancar | 7.1.4 | 8.1.2 |
| Imagen arm64 comprimida | 404 MB | 423 MB |
| Diseño web por defecto | Clásico, hoy llamado Legacy | Modern |
Rutas /emby/ |
Responden | 404 |
EnableLegacyAuthorization |
true |
false tras la migración |
El salto a .NET 10 es la razón de fondo del problema con los complementos: las notas de la versión piden que se recompilen contra el nuevo entorno.
¿Desde qué versión puedes saltar a la 12?
Solo hay dos puntos de partida admitidos: la 10.10.7 o cualquier 10.11.x, sin pasos intermedios. Si estás por debajo de la 10.10.7, las notas piden actualizar primero a esa versión y después a la 12.0.
No lo supongas, compruébalo. En el hilo de Hacker News del lanzamiento, un usuario creyó estar por debajo de la 10.10.7 y arrancó esa versión como paso intermedio. Las migraciones fallaron, la 12.0 tampoco arrancó después y, sin copia, acabó borrando la configuración. La versión real se consulta sin iniciar sesión:
curl -s http://localhost:8096/System/Info/Public \
| grep -o '"Version":"[^"]*"'
"Version":"10.11.11"
Cómo he probado la actualización
Monté la 10.11.11 en un proyecto de Compose desechable, con una biblioteca pequeña pero preparada con las trampas que la 12.0 anuncia. La máquina es un contenedor de desarrollo linux/arm64 con 18 núcleos, compartido con otras cargas. La carga media osciló entre 24 y 33 durante las mediciones, así que cada tiempo va con su contexto.
La biblioteca tenía 5 películas, una de ellas (Sintel) con dos versiones en la misma carpeta y otra (Elephants Dream) en .ogg. Añadí una serie real de 6 episodios y 40 series sintéticas de 25 episodios cada una. En total, 1012 ficheros de 30 s generados con el propio FFmpeg de la imagen, que en la base de datos suman 1144 elementos.
Creé los usuarios ana y carlos, marqué 3 episodios como vistos y Sintel como favorita, y armé una lista de reproducción de 6 episodios. También instalé dos complementos: Playback Reporting 17.0.0.0, que es oficial, y AniSearch 6.0.0.0.
Un aviso sobre el entorno: en esta máquina, cuya CPU declara las extensiones sme y sme2, la imagen arm64 de la 10.11.11 moría una y otra vez con código 132 (instrucción ilegal). La variable DOTNET_EnableArm64Sve=0 arregló el arranque, pero no los cierres posteriores, así que los pasos con la 10.11.x los terminé con la imagen amd64 emulada. La 12.0 arm64 no se cayó en ninguna prueba. No probé transcodificación por GPU ni clientes de terceros.
Tres comprobaciones antes de cambiar la etiqueta
Hay tres cosas que conviene revisar con la 10.11 todavía en marcha. La primera puede tumbar la migración, la segunda depende de código de terceros y la tercera rompe tus scripts sin que el servidor avise.
Usuarios que solo se diferencian en mayúsculas
La 12.0 guarda el nombre de usuario normalizado en una columna con índice único, así que dos cuentas como ana y Ana hacen fallar la migración. Probé a crear ese duplicado por la interfaz de programación (API) en la 10.10.7, la 10.11.9 y la 10.11.11, y las tres lo rechazaron con A user with the name 'Ana' already exists. Para reproducir el fallo tuve que forzarlo con SQL sobre una base de datos de la 10.11.9. Su esquema ya no impide ese duplicado, mientras que el de la 10.10.7 lo impedía con COLLATE NOCASE.
El resultado al migrar a la 12.0 fue este:
Perform migration 20260524120336_AddUniqueNormalizedUsernameIndex
CREATE UNIQUE INDEX "IX_Users_NormalizedUsername" ON "Users"
("NormalizedUsername");
[FTL] Error: SQLite Error 19: 'UNIQUE constraint failed:
Users.NormalizedUsername'.
Attempt to rollback JellyfinDb.
[FTL] Main: Error while starting server
La base de datos volvió a su estado anterior y quedó una copia en data/SQLiteBackups. Ese fallo esconde dos trampas.
En modo servidor el contenedor sigue vivo con /health devolviendo 503, y con --mode MigrateSystem el proceso sale con código 0 aunque la migración haya fallado. Renombré el usuario a ana.maria desde la 10.11.9 y la migración pasó a la primera. Si ya estás en la 10.11.10 o la 10.11.11, tu base de datos tiene ese índice desde entonces. Las notas de la 10.11.10 incluyen el cambio, y la base de datos de mi 10.11.11 ya lo traía.
Complementos de terceros
Las notas de la versión piden desinstalar antes de migrar todos los complementos que no vienen de serie. El blog añade que los compilados para la 10.11 no cargarán en la 12.0. En el catálogo oficial del 14 de septiembre, 33 de los 36 complementos ya tenían versión para la 12.0; los que no la tenían eran Bookshelf, AniSearch y Folio.
Quise ver qué pasa si no haces caso, así que migré una copia con los dos complementos instalados. Dos segundos después de arrancar, la tarea programada de actualización de complementos descargó Playback Reporting 19.0.0.0 y marcó la 17 como sustituida; tras un reinicio, la 19 quedó activa.
AniSearch 6.0.0.0, sin versión para la 12.0, cargó y figuraba como activo, e incluso se registró como proveedor de metadatos de series. Nada de eso demuestra que funcione con las interfaces que cambiaron, así que la recomendación oficial sigue en pie. Apunta cuáles tienes, desinstálalos desde Complementos en la 10.11, reinicia y vuelve a instalarlos en la 12.0.
Scripts y clientes que llaman a la API
La 12.0 desactiva los métodos antiguos de autenticación también en servidores existentes: la migración DisableLegacyAuthorization cambia EnableLegacyAuthorization a false en system.xml. Creé una clave de API en la 10.11.11 y la probé contra los dos servidores:
| Forma de enviar la clave | 10.11.11 | 12.0 |
|---|---|---|
Cabecera X-Emby-Token |
200 | 401 |
Parámetro ?api_key= |
200 | 401 |
Cabecera X-Emby-Authorization |
200 | 401 |
Parámetro ?ApiKey= |
200 | 200 |
Cabecera Authorization: MediaBrowser Token="…" |
200 | 200 |
Prefijo /emby/System/Info |
200 | 404 |
Si un widget de panel o un script tuyo usa X-Emby-Token, cámbialo a la cabecera Authorization antes de actualizar. Hay una salida temporal: poner EnableLegacyAuthorization a true en config/system.xml devolvió el 200 a X-Emby-Token y a api_key, aunque no a las rutas /emby/. El autor del cambio lo planteó así en su propuesta:
This PR will change the EnableLegacyAuthorization option to false for all installs, allowing a user to (temporarily) keep using the legacy method if they use an client that is not updated yet.
La misma propuesta anuncia que la opción y los métodos antiguos desaparecerán en una versión futura.
La copia de seguridad que te permite volver
La copia que sirve es la de ficheros, hecha con Jellyfin parado. La documentación lo dice sin rodeos: "Jellyfin does not have a downgrade mechanism", y el blog de la 12.0 pide en mayúsculas una copia manual completa de datos y configuración. En la imagen oficial, datos y configuración viven juntos bajo /config, así que con la carpeta del compose de la guía de instalación la copia son dos órdenes:
docker compose stop jellyfin
tar -czf ~/jellyfin-10.11.11-$(date +%F).tar.gz config
Mi /config ocupaba 68 MB (32 MB de metadatos, 28 MB de una copia interna y 5,5 MB de base de datos) y el tar.gz resultante pesó 27,8 MB en 0,85 s. La caché no hace falta: es desechable. Si usas volúmenes con nombre en lugar de carpetas, la copia se hace desde un contenedor auxiliar, como se explica en el artículo sobre volúmenes y bind mounts en Docker:
docker run --rm -v jellyfin-config:/config:ro \
-v "$PWD":/backup alpine:3 \
tar -czf /backup/jellyfin-config.tar.gz -C / config
La copia integrada de la 10.11, la del botón del panel, no me sirvió como vuelta atrás. Restaurar su zip con --restore-archive en la 10.11.11 falló sobre la base de datos ya migrada (table BaseItems has no column named ExtraIds) y también sobre un /config vacío (no such table: AccessSchedules). Solo funcionó sobre un /config que la 10.11.11 había inicializado antes con un arranque y una parada. Guárdala como plan C, no como plan A.
Tampoco cuentes con la copia automática de la migración: el registro anuncia que respalda jellyfin.db antes de tocarla y, si todo va bien, la borra al terminar. Si copias el servidor con restic para copias cifradas, la 12.0 deja un fichero CACHEDIR.TAG en /cache, y la opción --exclude-caches de restic salta esa carpeta sola.
Actualizar Jellyfin 12 paso a paso
El procedimiento completo, con la copia ya hecha, son siete pasos. Los ejecuté tal cual sobre la carpeta de prueba:
- Cambia la etiqueta de la imagen en
compose.yamlde10.11.11a12.0. - Descarga la imagen nueva con
docker compose pull jellyfin. - Lanza solo la migración con
docker compose run --rm jellyfin --mode MigrateSystem. - Revisa que el registro diga
Migrations have been appliedy no contenga[FTL]. - Arranca el servidor con
docker compose up -d. - Lanza un escaneo completo de todas las bibliotecas.
- Recarga el navegador sin caché y reinstala los complementos.
El paso 3 es opcional, porque el servidor migra solo al arrancar, pero separa la migración del arranque y te deja leer el resultado con calma. En mi carpeta de prueba tardó 3,5 s y dejó estas líneas, recortadas para que quepan:
sed -i 's#jellyfin/jellyfin:10.11.11#jellyfin/jellyfin:12.0#' compose.yaml
docker compose pull jellyfin
docker compose run --rm jellyfin --mode MigrateSystem 2>&1 \
| grep -E 'migrations for stage|Migrations have been|FTL'
JellyfinMigrationService: There are 18 migrations for stage
CoreInitialisation.
JellyfinMigrationService: There are 13 migrations for stage
AppInitialisation.
Main: Migrations have been applied, optimizing the database...
Como MigrateSystem sale con código 0 incluso cuando falla, el grep es la comprobación de verdad. Con el servidor ya arrancado, el escaneo se lanza desde Panel de control con Escanear todas las bibliotecas, o desde la línea de comandos con una clave de API:
docker compose up -d
curl -s -X POST http://localhost:8096/Library/Refresh \
-H 'Authorization: MediaBrowser Token="tu_clave_de_api"'
La respuesta correcta es un 204 sin cuerpo. Después, recarga la interfaz web con Ctrl+Shift+R: según el propio anuncio, los recursos en caché del navegador son la primera causa de fallos visuales tras actualizar.
¿Cuánto tarda la migración y el primer escaneo?
En una biblioteca de 1144 elementos, la migración es cuestión de segundos y el primer escaneo es el paso más largo. Repetí la migración con --mode MigrateSystem sobre tres copias de la misma copia de seguridad, con una carga media de 24 a 25 en 18 núcleos. Tardó 3,82 s, 3,66 s y 3,58 s de reloj, contenedor incluido. En el registro se ve el trabajo que hace: MigrateLinkedChildren procesó 1016 elementos y retiró 1 versión alternativa con tipo incorrecto, y otra rutina revisó los nombres de los 1144.
El primer escaneo después de migrar, medido en esas mismas tres copias con carga de 30 a 33, tardó 12 s según el registro de Jellyfin en las tres. El segundo escaneo, justo después, tardó 2 s en las tres. Es lo que describe el anuncio: el primer escaneo comprueba cada elemento de la biblioteca contra los ficheros del disco.
Mi biblioteca es pequeña y sintética, así que para bibliotecas grandes solo tengo testimonios, no mediciones. En el hilo de Hacker News, un usuario con unos 40 TB que saltó desde la 10.10.7 habla de "a few minutes" de migración. Otro cuenta unos 10 minutos de migración y unos 30 de escaneo, y un tercero, desde la 10.11.11, entre 2 y 3 minutos.
El espacio apenas cambia. jellyfin.db pasó de 5,46 MB a 6,48 MB con la migración, un 19 % más. La carpeta /config completa pasó de 68,4 MB a 70,0 MB tras migrar y escanear.
Qué cambia en tu servidor después de actualizar

Los datos de los usuarios sobrevivieron intactos, pero la biblioteca no se ve igual hasta el primer escaneo. Justo después de migrar, ana conservaba sus 3 episodios vistos y su favorita, y la lista de carlos seguía con sus 6 episodios. En cambio, Sintel había perdido su versión de 360p: la migración retira las versiones alternativas agrupadas de forma automática, y el escaneo las recupera. Tras escanear, Sintel volvió a tener sus dos versiones.
La película en .ogg corrió otra suerte. La 12.0 trata esa extensión como audio, así que tras el escaneo Elephants Dream dejó de ser una película. El contador bajó de 5 a 4, que es lo que muestra la captura. Si guardas vídeo en .ogg, conviértelo a otro contenedor antes o después de actualizar.
Queda un aviso inofensivo en el registro: Error loading configuration file: /config/config/encoding.xml con '' is not a valid value for EncoderPreset. Comparé el fichero antes y después, y solo cambiaron dos líneas: EncoderPreset pasó de vacío a auto y apareció el ajuste nuevo SubtitleExtractionTimeoutMinutes. El resto de la configuración de transcodificación quedó igual. La interfaz web, por su parte, abre con el diseño Modern en escritorio y móvil; el anterior sigue disponible con el nombre Legacy.
¿Cómo vuelvo a la 10.11 si algo sale mal?
Volver atrás exige restaurar la copia, porque la 10.11.11 no funciona sobre datos migrados aunque lo parezca. Arranqué la 10.11.11 sobre una copia ya migrada a la 12.0: el registro soltó [FTL] An error occurred starting the application con no such column: b.ExtraIds, pero el proceso siguió vivo y llegó a Startup complete. Docker marcó el contenedor como sano, /health devolvió 200 y /System/Info/Public también. Todas las peticiones autenticadas, incluido el inicio de sesión, devolvieron 500.
Ese es el caso peligroso: un monitor que solo mire el estado del contenedor dirá que todo va bien. La restauración que sí funcionó, probada con estas mismas órdenes, fue esta:
docker compose down
mv config config.12-fallida
tar -xzf ~/jellyfin-10.11.11-2026-09-14.tar.gz
sed -i 's#jellyfin/jellyfin:12.0#jellyfin/jellyfin:10.11.11#' compose.yaml
docker compose up -d
La 10.11.11 arrancó con sus 5 películas, los 3 usuarios, los vistos, la favorita y la lista de 6 episodios, y el inicio de sesión volvió a dar 200. Conserva config.12-fallida hasta saber qué falló: el registro de la migración está en config.12-fallida/log.
Watchtower y la etiqueta latest
Si Watchtower vigila un Jellyfin con latest, habrá aplicado la 12.0 en su primera pasada después del 8 de septiembre, salvo que lo tengas en modo solo aviso. Eso significa migración sin copia previa, con los complementos de terceros aún instalados y sin el escaneo obligatorio. Revisa el registro del primer arranque y la lista de complementos, y confirma que tienes una copia anterior a esa fecha.
Para lo que venga, el artículo sobre Watchtower para actualizar contenedores Docker ya recomienda fijar la versión mayor. Con el esquema nuevo de Jellyfin eso por fin significa algo: 12 no saltará a una 13.0. Aun así, para un servicio con migraciones de base de datos prefiero fijar 12.0 o excluirlo con la etiqueta com.centurylinklabs.watchtower.enable: "false" y actualizar a mano con copia.
Preguntas frecuentes
¿Puedo pasar de la 10.10.3 directamente a Jellyfin 12?
No. Las notas de la 12.0 solo admiten el salto directo desde la 10.10.7 o cualquier 10.11.x. Actualiza primero a la 10.10.7, comprueba que arranca, haz la copia y después cambia a la 12.0.
¿Se pierden los vistos, los favoritos y las listas al actualizar?
En mi prueba, no. Los 3 episodios vistos, la película favorita y la lista de 6 episodios seguían ahí antes y después del escaneo. Lo que desaparece hasta escanear son las versiones alternativas agrupadas de forma automática.
¿Conviene esperar a una 12.0.1?
A 14 de septiembre de 2026 no hay ninguna publicada, y el anuncio recomienda actualizar porque la 12.0 incluye correcciones de seguridad. Si tienes la copia, no usas complementos sin versión para la 12.0 y tus scripts ya usan la cabecera Authorization, no hay motivo técnico para esperar.
Conclusión
Actualizar Jellyfin 12 con Docker cuesta unos minutos de trabajo y unos segundos de migración, siempre que sigas el orden. Comprueba la versión y los usuarios, quita los complementos de terceros, copia /config con el servidor parado, migra y escanea.
Lo que me llevo de la prueba es una diferencia entre dos fallos. Una migración rota avisa en el registro; una vuelta atrás sin restaurar deja un contenedor sano que no sirve nada, y esa es la peligrosa. Guarda el tar.gz hasta que lleves una semana tranquila en la 12.0. La versión en inglés de esta guía está en How to upgrade Jellyfin 12 with Docker.
Fuentes
- jellyfin/jellyfin, notas de la versión 12.0
- Blog de Jellyfin, anuncio de Jellyfin 12.0
- jellyfin/jellyfin, notas de la versión 10.11.10
- Jellyfin, instalación en contenedor y etiquetas
- Docker Hub, imagen oficial jellyfin/jellyfin
- jellyfin/jellyfin, PR 15559 sobre la autenticación antigua
- Jellyfin, copia de seguridad y restauración
- Jellyfin, catálogo estable de complementos
- restic, documentación de la copia y –exclude-caches
- Hacker News, hilo del lanzamiento de Jellyfin 12.0
Código fuente
Accede a todo el código fuente de este artículo en GitHub.
Ver en GitHub