La mayoría de los equipos de ciencia de datos recurren primero a una plataforma de notebooks gestionada. SageMaker, Vertex AI y Azure Machine Learning son genuinamente cómodas, hasta el momento en que necesitas mover una carga de trabajo, auditar lo que realmente se ejecutó, o explicarle a tu equipo legal en qué jurisdicción se almacenan los datos de entrenamiento. El coste de salida es alto, la reproducibilidad es frágil, y la pregunta sobre la soberanía no tiene una respuesta clara. Hay un punto de partida mejor: un entorno contenedorizado en un VPS que tú controlas, construido para ejecutarse de forma idéntica desde una instancia de 20 €/mes hasta un nodo GPU de bare metal, sin reescribir una sola línea.
Esta guía cubre la configuración completa: contenedorización del entorno Python, despliegue de un servidor Jupyter notebook en producción y los patrones operativos que hacen que una configuración de ciencia de datos en VPS sea genuinamente portable. El objetivo es un entorno donde la máquina es reemplazable, pero el experimento no lo es.
Por qué la contenedorización es la base
El problema del entorno de ML reproducible es, en esencia, un problema de dependencias. Un notebook que funciona en tu portátil falla en el servidor de entrenamiento porque las versiones de CUDA difieren, porque una dependencia transitiva de pip se actualizó, o porque el sistema operativo base divergió. Los contenedores eliminan esa clase de fallos al convertir el entorno en un artefacto que puedes versionar, probar y distribuir.
El punto de partida práctico es un Dockerfile que fija cada capa: imagen base, runtime de CUDA si necesitas soporte GPU, versión de Python y versiones de paquetes bloqueadas mediante pip-compile o poetry lock. No uses latest en ningún punto de la definición de la imagen. Etiqueta las imágenes con el SHA del commit de Git del repositorio que las generó y súbelas a un registro privado que tú mismo operes. Esa etiqueta es tu rastro de auditoría.
Una imagen CPU mínima para trabajo exploratorio puede tener este aspecto:
FROM python:3.11.9-slim
WORKDIR /workspace
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["jupyter", "lab", "--ip=0.0.0.0", "--no-browser", "--allow-root"]
La variante GPU sustituye la imagen base por nvidia/cuda:12.4.1-runtime-ubuntu22.04 y añade los wheels compatibles con CUDA. El código de la aplicación y los notebooks son idénticos. Pasar del prototipo a producción implica actualizar una sola línea en tu archivo Compose o en el manifiesto de Kubernetes.
Despliegue del servidor Jupyter notebook: la forma correcta
Ejecutar Jupyter directamente en una IP pública con un token no es un despliegue, es un incidente esperando ocurrir. El patrón correcto tiene tres componentes: un proxy inverso que gestiona la terminación TLS, una capa de autenticación delante del servidor de notebooks y el servidor en sí vinculado únicamente a localhost.
Nginx o Caddy funcionan bien como proxy inverso. La gestión automática de certificados de Caddy a través de Let’s Encrypt reduce considerablemente la carga operativa para equipos pequeños. Colócalo delante de JupyterHub si tienes varios usuarios, o delante de una única instancia de Jupyter Lab para trabajo individual o en pareja. Vincula el servidor de notebooks a 127.0.0.1:8888 y enrútalo a través del proxy inverso en el puerto 443. Nunca expongas el puerto del notebook directamente.
Para la autenticación, JupyterHub con autenticación PAM o LDAP es la opción de nivel productivo. Para configuraciones más pequeñas, un túnel SSH es a la vez más sencillo y más seguro: reenvía el puerto del notebook de forma local, accede a él en localhost y mantén el servidor completamente fuera de la internet pública.
El entorno que se ejecuta en producción debe ser la misma imagen de contenedor que se usó en desarrollo. Si no lo es, no estás haciendo ciencia reproducible, estás haciendo coincidencia reproducible.
Estructurar el proyecto para la portabilidad
La reproducibilidad se rompe en la capa de datos con tanta frecuencia como en la capa de código. Trata las referencias a datos igual que las versiones de dependencias: explícitas, versionadas y almacenadas fuera del contenedor. El almacenamiento de objetos con un bucket versionado es la abstracción adecuada. Tu notebook lee desde una ruta como s3://project-data/v3/train.parquet; la versión está en la ruta, no implícita en el momento en que ejecutaste la celda.
La estructura de proyecto que sobrevive limpiamente la transición del prototipo a producción separa las responsabilidades de forma clara:
- notebooks/ para exploración e informes, confirmados en Git con las salidas eliminadas
- src/ para módulos Python reutilizables extraídos de los notebooks una vez que se estabilizan
- pipelines/ para trabajos programados o activados por eventos, definidos como scripts Python simples o DAGs ligeros
- docker/ para Dockerfiles y definiciones Compose, uno por nivel de entorno
- data/ como punto de montaje local únicamente, con los datos reales en almacenamiento de objetos y un
.gitignoreque cubre todo lo que hay debajo
MLflow o DVC gestiona el seguimiento de experimentos sin necesidad de un servicio gestionado. Ambos se ejecutan en tu propio VPS, almacenan artefactos en tu bucket de almacenamiento de objetos y exponen una interfaz a la que accedes mediante el mismo patrón de proxy inverso descrito anteriormente. El registro de experimentos permanece en tu infraestructura, bajo tus claves.
Escalar de un único VPS a bare metal
La misma imagen de contenedor que se ejecuta en un VPS de 4 núcleos para exploración de datos funciona en un nodo GPU de bare metal para entrenamiento. El único cambio es el override de Compose o el selector de nodo de Kubernetes que solicita recursos GPU. Aquí es donde la inversión en contenedorización da sus frutos: no reescribes el entorno al escalar el cómputo.
La buena práctica de infraestructura para ciencia de datos al escalar consiste en mantener el servidor de notebooks en un VPS persistente de tamaño moderado y ejecutar los trabajos de entrenamiento como contenedores separados en instancias más grandes aprovisionadas bajo demanda. El notebook es un servicio de larga duración; el trabajo de entrenamiento es efímero. Mezclarlos en la misma instancia acopla dos elementos con requisitos de ciclo de vida muy distintos.
Para los equipos que migran desde SageMaker o Vertex AI, el camino es directo. Exporta tus notebooks. Reconstruye el entorno desde un Dockerfile que fije las versiones de Python y las bibliotecas que estabas usando. Apunta tus referencias de datos a tu propio almacenamiento de objetos. La plataforma gestionada proporcionaba cómputo, un runtime y una interfaz; los tres son reemplazables con herramientas abiertas que se ejecutan en infraestructura que tú controlas.
Bases operativas que importan a cualquier escala
Una configuración de ciencia de datos en VPS sigue siendo un servidor, y los servidores requieren disciplina operativa. Tres aspectos en los que los equipos invierten sistemáticamente menos de lo necesario:
- Copias de seguridad automatizadas del estado del servidor de notebooks. Los notebooks, los datos de seguimiento de MLflow y el registro de imágenes de contenedor deben tener un procedimiento documentado de copia de seguridad y restauración. Prueba la restauración, no solo la copia de seguridad.
- Límites de recursos en los contenedores. Un trabajo de entrenamiento sin restricciones saturará la memoria y dejará el servidor de notebooks sin respuesta. Define límites de CPU y memoria en tus definiciones de Compose o Kubernetes, incluso en una configuración de nodo único.
- Gestión de secretos. Las credenciales de bases de datos, las claves de almacenamiento de objetos y los tokens de API no pertenecen a archivos de entorno confirmados en Git. Usa Docker secrets, un gestor de secretos o, como mínimo, inyección de variables de entorno en tiempo de ejecución desde un archivo explícitamente excluido del control de versiones.
Ejecutamos este stack en infraestructura de Nivel III con certificación ISO 27001 y un SLA contractual de disponibilidad del 99,9 %, lo que significa que las garantías operativas están escritas en el contrato, no declaradas en una página de marketing. Para los equipos que trabajan con datos de entrenamiento sensibles, esa distinción importa: la soberanía es un compromiso a nivel contractual, no una opción de configuración.
El bloqueo que evitas
Las plataformas de notebooks gestionadas combinan comodidad con dependencia. La comodidad es real: el aprovisionamiento es rápido, las integraciones están preconfiguradas y el primer experimento se ejecuta en minutos. La dependencia también es real: tu entorno está definido por el runtime de la plataforma, tus pipelines de datos se integran con el almacenamiento y el IAM de la plataforma, y tu equipo aprende las abstracciones de la plataforma en lugar de las transferibles.
Un entorno contenedorizado en un VPS que tú controlas invierte ese equilibrio. La configuración inicial lleva más tiempo. Cada entorno posterior es un Dockerfile y un archivo Compose. El equipo aprende Docker, empaquetado de Python y herramientas estándar de Linux: habilidades que se transfieren a cualquier infraestructura. Los artefactos de los experimentos viven en almacenamiento que tú posees. Cuando necesitas más cómputo, aprovisionas una instancia más grande o un nodo bare metal sin renegociar una relación con una plataforma.
Eso es lo que un entorno de ML reproducible significa en la práctica: no solo que el experimento pueda volver a ejecutarse, sino que la infraestructura sobre la que se ejecuta es tuya para mover, modificar y comprender sin pedir permiso.


