Uno de los retos más persistentes en el ecosistema Python es crear aplicaciones Python portables, que aíslen correctamente el manejo de dependencias y garanticen una distribución más confiable. Aunque los contenedores han mejorado considerablemente la portabilidad, todavía enfrentan desafíos relacionados con el tamaño de imagen y los tiempos de construcción y despliegue. En este artículo comparto mi experiencia utilizando PEX (Python EXecutable), una herramienta que permite empaquetar aplicaciones Python junto con todas sus dependencias en un único archivo ejecutable. Esta solución no solo mejora la portabilidad y reproducibilidad, sino que en algunos casos también puede ayudar a reducir el tiempo de arranque, un factor clave en entornos escalables y de alta demanda.
¿Qué es PEX?
PEX (Python EXecutable) es una herramienta de código abierto, desarrollada originalmente por Twitter, cuyo propósito es empaquetar una aplicación Python, todas sus dependencias (incluso las transitivas), y su punto de entrada en un solo archivo .pex.
Este ejecutable y puede trasladarse a cualquier sistema que tenga un intérprete Python compatible. Incluso es posible empaquetar el propio intérprete dentro del ejecutable, gracias a las distribuciones Python Standalone Builds de CPython, lo que elimina por completo la necesidad de tener Python preinstalado en el host de destino.
¿Por qué usar PEX?
PEX es una solución madura que resuelve varios problemas comunes en proyectos Python, tanto en desarrollo como en producción. A continuación, detallo algunos de sus beneficios clave:
1. Portabilidad real
El archivo .pex es completamente autocontenible, es decir, puedes moverlo de un entorno a otro sin preocuparte por replicar entornos virtuales, versiones de dependencias o configuraciones específicas.
En entornos más exigentes, puedes usar la opción –scie eager para incluir el intérprete Python directamente en el binario, lo que resulta ideal para despliegues en entornos edge, servidores bare-metal o funciones serverless.
2. Reproducibilidad garantizada
Al ser un artefacto autocontenido, PEX fija versiones exactas de todas las dependencias, lo que garantiza ejecuciones consistentes sin importar el entorno donde se ejecute el binario. Esta característica ayuda a mitigar errores comunes provocados por actualizaciones inesperadas en PyPI, así como inconsistencias entre entornos de desarrollo, pruebas y producción mal replicados.
3. Contenedores más livianos y seguros
Una de las ventajas más significativas de PEX es su integración con contenedores:
- Permite utilizar imágenes base minimalistas (python:slim, scratch, etc.).
- Elimina la necesidad de instalar pip, compilar dependencias o copiar el código fuente en la imagen final.
- Produce imágenes más pequeñas, más seguras y más rápidas de construir y desplegar.
Caso práctico: optimización de imágenes en MCP OpenAPI Proxy
Recientemente, integré PEX en el proyecto MCP OpenAPI Proxy, y logré reducir el tamaño de la imagen Docker de 152 MB a 134 MB. Esta mejora reduce el consumo de ancho de banda al distribuir contenedores y mejora la eficiencia en entornos con recursos limitados, como en despliegues en el Edge.
FROM python:3.12-slim-bookworm AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
COPY . /app/
# Sincronización de dependencias y creación del ejecutable PEX
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-editable && \
uvx pex -o mcp-proxy -c mcp-proxy --sh-boot --include-tools .
¿Y el desempeño?
Aunque PEX mejora portabilidad y tamaño, no necesariamente siempre puede llegar a reducir el tiempo de arranque. En las puebas de rendimiento que realicé usando hyperfine para comparar dos imágenes: una con PEX y otra con una estrategia anterior.
$ hyperfine 'docker run --rm pex --help' 'docker run --rm previous --help'
Resultados:
Benchmark 1: docker run --rm pex --help
Time (mean ± σ): 2.643 s ± 0.040
Range (min … max): 2.585 s … 2.718 s [10 runs]
Benchmark 2: docker run --rm previous --help
Time (mean ± σ): 2.207 s ± 0.018 s
Range (min … max): 2.180 s … 2.238 s [10 runs]
Summary:
'docker run --rm previous --help' ran
1.20 ± 0.02 times faster than 'docker run --rm pex --help'
Conclusión: La imagen previa fue aproximadamente 1.2 veces más rápida en tiempo de arranque.
Esta diferencia puede no ser crítica en muchos escenarios, pero sí es relevante en aplicaciones serverless o de escalado rápido, donde cada milisegundo cuenta. Como siempre, es importante evaluar el equilibrio entre portabilidad, rendimiento y simplicidad de operación.
Conclusión
PEX es una herramienta poderosa, flexible y madura para desarrollar aplicaciones Python portables, reproducibles y eficientes.
Combinado con herramientas modernas como uv y estrategias multibuild en contenedores, permite construir imágenes más ligeras y robustas, con menos complejidad y más previsibilidad.
Si trabajas con Python en producción, o simplemente buscas formas más limpias y portables de entregar tus aplicaciones, vale la pena experimentar con PEX. Sus ventajas lo convierten en una herramienta imprescindible en el arsenal de cualquier desarrollador Python profesional.