Tutorial rápido de Docker para ejecutar un script en Python
Te encuentras ante un primer punto de partida para aprender Docker desde cero donde prima más la práctica que la teoría. El objetivo de este artículo NO es enseñarte todos los aspectos, ya hay cursos muchos más completos y profundos. Cubriremos los aspectos básicos y esenciales para que puedas empezar a usar Docker en tus proyectos de desarrollo de software. Iremos paso a paso hasta lograr ejecutar un script en Python dentro de un contenedor Docker.
Voy a dar por sentado que has instalado Docker en tu máquina. Si no es así, puedes seguir las instrucciones oficiales en docs.docker.com/get-docker.
Al ejecutar:
docker --version
Deberías ver algo similar a:
Docker version 28.4.0, build d8eb465
La versión y el build pueden variar, pero debemos comprobar que somos capaces de ejecutar comandos Docker desde la terminal.
¡Comencemos!
¿Qué es Docker y por qué usarlo?
Docker es la plataforma de contenedorización más popular. Te permite empaquetar aplicaciones para que sean fácil de lanzar. De esta manera podrás ejecutar tu código en cualquier sistema operativo que soporte Docker, sin preocuparte por las dependencias, versiones o configuraciones del entorno. Una vez esta configurado, cualquier persona de tu equipo podrá lanzar la aplicación de forma idéntica.
No debes confundir con una máquina virtual. Aunque a priori parecen similares, Docker son procesos aislados que utilizan el kernel del sistema operativo anfitrión, mientras que las máquinas virtuales emulan un sistema operativo completo. Esta diferencia es clave ya que explica su peso tan ligero y rendimiento similar a una aplicación nativa.
Por lo tanto, logramos:
- Portabilidad: Corre en cualquier sistema con Docker instalado
- Seguridad: Cada proceso tiene su propio entorno
- Eficiencia: Similar, o igual, a ejecutar nativamente
- Consistencia: El entorno es idéntico en desarrollo, pruebas y producción
Ahora que entendemos que es Docker y sus beneficios, vamos a ver cómo funciona.
Arquitectura de Docker
Los componentes principales son:
- Docker Engine: El motor que ejecuta los contenedores. Se encarga de crear, ejecutar y gestionar contenedores.
- Docker Client: Interfaz de línea de comandos (CLI) para interactuar con Docker Engine.
- Docker Daemon: Proceso en segundo plano que gestiona imágenes y contenedores.
Cuando trabajas con Docker, principalmente interactúas con el Docker Client, que envía comandos al Docker Daemon para ejecutar acciones.
A la hora de gestionar tus aplicaciones con Docker Client, gestionarás continuamente 2 elementos fundamentales:
- Imagen: Plantilla inmutable que contiene el código y dependencias. Piensa que es como una "clase".
- Contenedor: Instancia en ejecución de una imagen. Sería el "objeto" creado a partir de la clase.
Ya sabes lo más básico sobre Docker. ¡Vamos a la práctica!
Comandos Básicos de Docker CLI
Es el momento de aprender los comandos básicos.
Ejecutar tu primer contenedor
El "hola mundo" de Docker es:
docker run hello-world
Descargará una imagen pequeña y ejecutará un contenedor que imprime un mensaje de bienvenida.
Si ha ido todo bien, podemos ir a algo más avanzado.
# Descargará la imagen de Debian y abrirá una terminal bash dentro del contenedor
docker run -it debian bash
# Comprueba la versión
cat /etc/debian_version
# Salir del contenedor
exit
Ya somos capaces de ejecutar contenedores. Estamos más cerca de poder lanzar nuestro script en Python.
Gestión de contenedores
Veamos algunos comandos útiles para gestionar contenedores. Vuelve a levantar el contenedor de Debian y en un nuevo terminal ejecuta:
# Listar contenedores activos
docker ps
# Listar todos los contenedores, incluidos los parados
docker ps -a
# Parar un contenedor
docker stop <container_id>
# Eliminar un contenedor
docker rm <container_id>
# Eliminar todos los contenedores parados
docker container prune
Modos de ejecución
Cuando hemos levantado el contenedor de Debian, tal vez te habrás preguntado ¿qué significa la flag de -it?. Es el modo de ejecución, o como interactuamos con el contenedor:
- Interactivo (
-it): Para trabajar dentro del contenedor. Permite abrir una terminal y ejecutar comandos manualmente. - Detached (
-d): Ejecuta en segundo plano. Ideal para servicios o aplicaciones que deben estar siempre activos pero sin interacción directa.
# Modo interactivo
docker run --rm -it eggplanter/sh-tetris:v2.1.0
# Modo detached
docker run -d -p 3000:80 excalidraw/excalidraw
Abre en tu navegador http://localhost:3000 para dibujar con Excalidraw.
¿Y como paro el contenedor en modo detached? Muy fácil, recordemos los comandos de gestión:
docker ps
docker stop <container_id>
Logs y debugging
Si necesitas ver qué está pasando dentro de un contenedor, los logs son tu mejor amigo.
# Ver logs de un contenedor
docker logs <container_id>
# Seguir logs en tiempo real
docker logs -f <container_id>
Ejecutar comandos dentro de contenedores
Supongamos que tengo ejecutandose el contenedor de Debian en modo detached y quiero usar su comando curl para hacer una petición HTTP.
Tienes 2 opciones:
# Abrir una shell interactiva
docker exec -it <container_id> bash
# Y dentro
curl rate.sx/btc
# Ejecutar en un solo comando
docker exec <container_id> curl rate.sx/btc
Trabajando con Imágenes
Estamos usando con diferentes imágenes (debian, hello-world, excalidraw...), pero ¿donde están esas imágenes? ¿Cómo funcionan?
Puedes buscarlas y descargarlas desde Docker Hub, el repositorio público más grande de imágenes Docker.
También puedes usar la CLI.
docker search python
Cada imagen tiene un nombre y una etiqueta (tag) que indica la versión o variante. Si no especificas una etiqueta, Docker usará latest por defecto.
Vamos a entrar en la prompt de Python usando la imagen oficial:
docker run -it python:alpine python
La versión alpine es una variante ligera basada en Alpine Linux, ideal para reducir el tamaño de la imagen. Sin embargo, que ocupe menos espacio no significa que tenga todas las librerías o que su rendimiento sea mejor. Cada imagen está optimizada para diferentes casos de uso.
Compartir datos entre el host y el contenedor: Bind mounts
Supongamos que tenemos un script en Python en nuestro equipo y queremos ejecutarlo dentro de un contenedor.
script.py contiene el siguiente código:
print("¡Hola desde el contenedor Docker!")
Para ejecutar este script dentro de un contenedor Python, podemos usar muchas estrategias en Docker. La más sencilla es usar un bind mount para compartir el archivo entre el host y el contenedor.
docker run --rm -v $(pwd)/script.py:/app/script.py python:alpine python /app/script.py
La clave esta en la flag -v. Los 2 puntos separan la ruta del host (izquierda) y la ruta dentro del contenedor (derecha). En este caso, estamos montando $(pwd)/script.py en /app/script.py dentro del contenedor. $(pwd) es una variable de entorno que representa el directorio actual, o donde hemos ejecutado el comando. --rm indica que el contenedor se eliminará automáticamente después de que termine de ejecutarse.
¡Y voilà! Deberías ver el mensaje:
¡Hola desde el contenedor Docker!
¡Felicidades! Has ejecutado tu primer script en Python dentro de un contenedor Docker.
Hemos lanzado un ejemplo muy sencillo. ¿Qué pasaría si nuestro script tuviera dependencias externas? ¿O quisieramos configurarlo para que se ejecute automáticamente sin un intrincado comando docker run? Necesitamos crear nuestras propias imágenes Docker.
Creando tus Propias Imágenes
Los archivos Dockerfile son la forma estándar de definir cómo construir una imagen Docker personalizada.
Un ejemplo para lo que hemos hecho hasta ahora sería:
FROM python:alpine
WORKDIR /app
COPY script.py .
CMD ["python", "script.py"]
Construiríamos la imagen con:
docker build -t mi-script-python:v1 .
¡No olvides el punto al final!
Y la ejecutaríamos con:
docker run --rm mi-script-python:v1
Es lo mismo que antes, pero ahora tenemos una imagen reutilizable. Podríamos compartirla con otros compañeros o desplegarla en producción.
Puedes usar las siguientes instrucciones básicas en un Dockerfile:
- FROM: Define la imagen base
- RUN: Ejecuta comandos durante el build (instalar paquetes)
- COPY: Copia archivos del host a la imagen
- ADD: Similar a COPY pero con capacidades adicionales (descomprimir archivos)
- CMD: Comando por defecto al iniciar el contenedor
- ENTRYPOINT: Comando principal que no se sobrescribe fácilmente
Ahora vamos a complicarlo un poco más.
Creamos un archivo llamado requirements.txt con las dependencias:
names
Es una biblioteca que genera nombres aleatorios.
Modificamos nuestro script.py para usarla:
import names
print(names.get_full_name())
Modificamos el Dockerfile para instalar las dependencias:
FROM python:alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY script.py .
CMD ["python", "script.py"]
Construimos y ejecutamos de nuevo:
docker build -t mi-script-python:v2 .
Y lanzamos:
docker run --rm mi-script-python:v2
Deberías ver un nombre aleatorio generado por la librería names.
Has logrado crear un contenedor para ejecutar tu script en Python con dependencias. ¡Buen trabajo!
¿Qué archivos subiríamos al repositorio o pasaríamos a un compañero?
Dockerfile: El archivo con las instrucciones para construir la imagen.script.py: El script Python que queremos ejecutar.requirements.txt: Las dependencias necesarias para el script.README.md: Opcionalmente, un archivo de documentación con instrucciones para construir y ejecutar la imagen.
Pero ningún binario o imagen pesada de Docker. Cada persona puede construir la imagen localmente.
Extra: Variables de entorno
No siempre es buena idea hardcodear configuraciones dentro del código o el Dockerfile. Por ejemplo contraseñas, tokens o URLs que irán cambiando según el entorno (desarrollo, pruebas, producción). Por ello mismo Docker soporta nativamente las variables de entorno.
Lo más común es usar un archivo .env:
DATABASE_PASSWORD=mysecretpassword
API_KEY=abcdef
Y lo usamos al ejecutar el contenedor:
docker run --rm --env-file .env mi-script-python:v3
Solo funcionarán dentro del contenedor. En Python puedes acceder a ellas usando el módulo os:
import os
db_password = os.getenv("DATABASE_PASSWORD")
api_key = os.getenv("API_KEY")
print(f"DB Password: {db_password}, API Key: {api_key}")
¡Feliz contenerización!
- ¿Qué es Docker y por qué usarlo?
- Arquitectura de Docker
- Comandos Básicos de Docker CLI
- Ejecutar tu primer contenedor
- Gestión de contenedores
- Modos de ejecución
- Logs y debugging
- Ejecutar comandos dentro de contenedores
- Trabajando con Imágenes
- Compartir datos entre el host y el contenedor: Bind mounts
- Creando tus Propias Imágenes
- Extra: Variables de entorno
Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.