[Actualizado a 14 de octubre de 2026]
Contenedores🔗
Este manual es muy, muy resumido con el único objeto de tener una herramienta de partida para correr contenedores y crear imágenes del C.E. de Desarrollo de Aplicaciones en Lenguaje Python. Para cualquier otro objetivo revisar las fuentes listadas y la documentación oficial.
1. Instalación🔗
Con Docker Desktop
¿Funciona?🔗
Abra una terminal y ejecuta docker --version.
2. Comando docker🔗
El comando docker tiene la siguiente estructura:
docker <comando> <argumentos> {{imagen}}[:tag] <COMANDO>
Vamos a analizarlo por partes:
docker: indica al sistema que vamos a pasar un comando al serviciodockery no al shell donde estemos corriendo (PS, CMD, Bash, etc).<comando>: que acción queremos realizar. Lo analizamos más adelante.<argumentos>: modificadores de aislamiento del contenedor. Son opcionales, pero recuerda que el contenedor se ejecutará completamente aislado, excepto aquellos elementos que explícitamente le indiquemos aquí.{{imagen}}: es la base con la que deseamos trabajar. Habitualmente la obtendremos de docker hub u otro repositorio público o privado.:tag: nos permite elegir la versión concreta de la imagen (ver opciones enDocker Hub.<COMANDO>: Las imágenes tienen pre-cargado el comando a ejecutar al arrarncar... pero podemos seleccionar cualquier otro que se encuentre disponible dentro de la imagen.
2.1. <comando>🔗
En realidad sólo necesitamos recordar dos comandos: docker run y docker network.
2.1.1. docker run🔗
Es un comando mágico para trabajar con contenedores que realiza 3 funciones:
- Busca la imagen en el "almacén local" (caché local). Si no existe la busca en el almacen original, por defecto docker hub, y la descarga.
- La instancia (crea un contenedor ejecutable a partir de ella).
- Lo corre (el contenedor).
El resto de comandos podemos manejarlos gráficamente con Docker Desktop, pero los mencionamos para que el alumnado investigue si lo desea: ps, logs, start, stop, rm, exec, pull, push, cp...
2.1.2. docker network🔗
Es un comando para gestión de redes que nos permite:
docker network createpara crear una red 1.docker network lspara listar redes disponibles.
Info
Las redes en docker nos permite tener contenedores conectados entre sí y aislados del resto del mundo (o no).
2.2. <argumentos>🔗
Esta parte es la que más debemos meditar ya que NO podremos modificarla luego y tiene implicaciones de usabilidad y seguridad.
2.2.1. Volúmenes🔗
Permite que el host y el contenedor compartan datos (archivos y directorios).
docker run -v {{DIR_DEL_HOST}}:{{DIR_DE_LA_IMAGEN}} ...
El directorio del host puede especificarse de forma absoluta (p.e. "C:/Users/Luis/mis_datos") o relativa al directorio actual:
- En Bash:
./mis_datos,$(pwd)o${PWD} - En Powershell:
${PWD} - y en CMD:
%cd%
Warning
El {{DIR_DE_LA_IMAGEN}} lo marca el creador de la imagen y NO podemos modificarlo.
Podemos consultar los volúmenes en Docker Desktop-Containers-{{Contenedor}}-Bind mounts.
Podemos "montar" varios volúmenes y los mismos pueden conectar archivos o directorios.
2.2.2. Puertos🔗
Permite "abrir" puertos al exterior o escuchar en puertos del host.
docker run -p 8080:80 nginx
Conectará el puerto 8080 del host con el 80 del contenedor, pudiendo especificar el puerto que queramos del host que esté disponible.
Warning
El puerto del contenedor es fijado por el creador de la imagen y NO podemos cambiarlo.
2.2.3. Redes🔗
Conexión a redes con --net {{NOMBRE_DE_RED}}. El contenedor será creado con una conexión a esta red y podrá comunicarse con todos los contenedores que estén en ella.
Algunas redes pre-creadas son:
host: el contenedor tendrá pleno acceso a la red y al host. La más flexible e insegura.none: el contenedor está aislado. La más rígida y segura.bridge: podremos conectar si especificamos los puertos. Es la red por defecto.
Cualquier otra red creada permitirá la conexión entre contenedores y su conexión desde el exterior será según drivers utilizados.
2.2.4. Otros🔗
- Elegir entre "interactivo"
-io desatendido-d(segundo plano). - Usar un emulador de terminal...
-t - Nombrar contenedor
--name {{NOMBRE}} - Autoborrar el contenedor al finalizar
--rm - Elegir el directorio de trabajo
-w {{DIRECTORIO_DEL_CONTENEDOR}}
2.3. Imagen🔗
2.4. <COMANDO>🔗
Podemos modificar el comportamiento del contenedor lanzando comandos diferentes al por defecto:
| PowerShell | |
|---|---|
Note
Para separar en varias líneas la introducción de un comando de PowerShell tenemos que añadir un backtrick "`" al final de la misma.
En Bash lo sustituímos por una "contrabarra" \:
2.5. Ejemplos🔗
Probar nuestra aplicación en otra versión de python🔗
-
Comprobamos:
docker run -it --rm --name python-3.13 -v ${PWD}/:/home -w /home python:3.13 python --version -
Ejecutamos:
docker run -it --rm --name python-3.13 -v ${PWD}/:/home -w /home python:3.13 python app.py
Aislamiento de red🔗
Aislamiento de red con --net none o --network none:
Gestión de variables de entorno -e:🔗
Demostrar cómo inyectar configuración sin modificar código Python.
- Creamos la aplicación:
- La ejecutamos
Si nuestra aplicación tuviera más de un archivo y estuviera en el directorio work_dir...
docker run --rm \
-e ENTORNO="Produccion" \
-e PUERTO=5000 \
-v ${PWD}/work_dir:/app \
-w /app \
python:3.13 \
python app.py
Comunicación de contenedores🔗
Comunicación entre dos contenedores mediante una red bridge personalizada:
2.6. Ejercicios🔗
Ejercicio 1: Primeros pasos e inspección (Fácil)🔗
Despliega un contenedor interactivo con la imagen python:3.13-slim. Accede a la REPL (consola interactiva de Python), calcula la potencia de $2^{100}$ y sal del contenedor asegurándote de que el contenedor se elimine automáticamente al cerrar.
Ejercicio 2: Ejecución de scripts locales en entorno aislado (Fácil)🔗
Crea en el host un archivo hola.py con el contenido print("Hola desde el contenedor"). Ejecútalo usando un contenedor de Python sin copiar el archivo dentro de la imagen, utilizando un volumen relativo.
Ejercicio 3: Prueba de compatibilidad entre versiones de Python (Fácil - Medio)🔗
Dado un script check_version.py con el contenido import sys; print(sys.version), ejecútalo usando contenedores con Python 3.9, 3.11 y 3.13 comprobando la versión reportada en cada caso.
Ejercicio 4: Servidor HTTP rápido con redirección de puertos (Medio)🔗
Usa el módulo nativo de Python http.server para servir los archivos de tu directorio local en el puerto 8080 del Host, sabiendo que el servidor de Python escucha internamente en el puerto 8000.
Ejercicio 5: Pasar variables de configuración al contenedor (Fácil)🔗
Dado el script Python config_check.py que imprime las variables de entorno DATABASE_URL y DEBUG. Lánzalo dentro de un contenedor asignándole los valores postgres://user:pass@db:5432/app a DATABASE_URL y True A `DEBUG.
Ejercicio 6: Generación de artefactos persistentes (Medio)🔗
Ejecuta un script dentro de un contenedor que genere un archivo de texto resultados.txt con los números del 1 al 10. Verifica que, al finalizar el contenedor, el archivo permanece disponible en tu equipo host.
Ejercicio 7: Redes personalizadas entre servicios (Medio - Avanzado)🔗
- Crea una red personalizada de Docker llamada
red-app. - Lanza un servidor HTTP de Python en esa red con el nombre
servidor-web. - Lanza un segundo contenedor interactivo en la misma red y utiliza
curlourlliben Python para obtener la página principal delservidor-webusando su nombre de contenedor como hostname.
Ejercicio 8: Contenedor en aislamiento de seguridad total (Avanzado)🔗
Simula la ejecución de un script de análisis de datos sensible analisis.py que no debe tener acceso a internet ni a la red local. Utiliza la bandera de red adecuada para garantizar aislamiento total y verifica que no puede conectarse a un servidor externo.
Ejercicio 9: Inyección y prueba con base de datos en memoria (Avanzado)🔗
Lanza un contenedor de Redis (redis:alpine) expuesto en el puerto 6379. Luego, ejecuta un script efímero en Python que instale la librería redis, se conecte al servidor, guarde una clave materia: "python" y la lea.
Para la conexión y guardado emplea el siguiente PROMT:
Creame una app python que se conecte a un servidor redis con el FQDN `redis` en el puerto almacenado en la variable de entorno `PUERTO` y que almacene el par: `materia: "python"`. Debe mostrar si finalizó con éxito.
Después debe leerla y mostrarla por pantalla.
Además:
1. Lead with the next action.
2. Number multi-step tasks.
3. End with one concrete next step.
4. Suppress tangents.
5. Restate state every turn.
6. Specific time estimates (minutes, not "a bit").
7. Make wins visible.
8. Matter-of-fact errors.
9. Cap lists to 5 items.
10. No preamble. No recap. No closers.
Ejercicio 10: Automatización multi-paso mediante CLI (Avanzado / Experto)🔗
Escribe un comando en Bash/PowerShell que descargue un dataset CSV público desde un script de Python en un contenedor con acceso a internet, lo guarde en un volumen compartido, y luego procese dicho CSV con un segundo contenedor con la red deshabilitada (--network none).
3. "Recetas" docker compose🔗
A medida que nuestras aplicaciones crecen, escribir comandos docker run extremadamente largos con múltiples volúmenes, variables de entorno, puertos y redes se vuelve propenso a errores y difícil de mantener.
Docker Compose es una herramienta que permite definir y ejecutar aplicaciones multicontenedor mediante un único archivo declarativo escrito en formato YAML (habitualmente llamado docker-compose.yml o compose.yaml).
3.1. Sintaxis Básica de un archivo compose.yaml🔗
Un archivo de Compose traduce directamente las banderas de docker run a clave-valor:
Banderas docker run |
Equivalente en compose.yaml |
|---|---|
--name mi-app |
container_name: mi-app |
-p 8000:8000 |
ports: - "8000:8000" |
-v ${PWD}/:/app |
volumes: - ./:/app |
-e ENTORNO=prod |
environment: - ENTORNO=prod |
--net mi-red |
networks: - mi-red |
--restart always |
restart: always |
3.2. De docker run a docker compose con composerize🔗
La herramienta web Composerize permite transformar comandos CLI de Docker directamente a archivos YAML.
Ejemplo de conversión:🔗
Comando Docker CLI:
docker run -d \
--name api-python \
-p 5000:5000 \
-v ${PWD}/:/app \
-e FLASK_ENV=development \
--net red-datos \
python:3.13-slim python /app/main.py
Resultado generado:
version: '3.8'
services:
api-python:
container_name: api-python
ports:
- 5000:5000
volumes:
- ${PWD}/:/app
environment:
- FLASK_ENV=development
networks:
- red-datos
image: python:3.13-slim
command: python /app/main.py
networks:
red-datos:
driver: bridge
3.3. Comandos Esenciales de Docker Compose🔗
Para gestionar el ciclo de vida de la "receta", ejecuta desde el directorio donde se encuentra el archivo compose.yaml:
-
Levantar los servicios en segundo plano:
-
Ver los logs de los contenedores en tiempo real:
-
Detener y eliminar contenedores, redes y volúmenes creados:
-
Ejecutar un comando dentro de un servicio en ejecución:
3.4. Recetas Frecuentes para Desarrollo en Python🔗
Receta 1: Entorno de Desarrollo Web Python (FastAPI / Flask) con Recarga en Vivo🔗
services:
web:
image: python:3.13-slim
container_name: python-web-dev
ports:
- "8000:8000"
volumes:
- ${PWD}/:/app
working_dir: /app
command: sh -c "pip install -r requirements.txt && uvicorn main:app --host 0.0.0.0 --reload"
Receta 2: Pila Completa (Python API + Base de Datos PostgreSQL)🔗
services:
app:
image: python:3.13-slim
container_name: python-app
ports:
- "8000:8000"
volumes:
- ${PWD}/:/app
working_dir: /app
environment:
- DB_HOST=db
- DB_NAME=app_db
- DB_USER=postgres
- DB_PASS=secret
depends_on:
- db
command: python main.py
db:
image: postgres:16-alpine
container_name: postgres-db
environment:
POSTGRES_DB: app_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
4. Creando imágenes🔗
4.1 Ejercicios🔗
Basado en las plantillas habituales de Dockerfile para desarrollo en Python (Slim, Alpine, Multi-stage y Poetry/UV), aquí tienes la batería de ejercicios de desarrollo de imágenes:
Ejercicio 4.1: Mi primer Dockerfile para un Script CLI🔗
Crea un proyecto con un archivo app.py que salude e imprima la hora actual. Crea un Dockerfile usando python:3.13-slim, construye la imagen con el tag mi-script:v1 y ejecútala.
Ejercicio 4.2: Optimización de Caché de Capas (COPY estructurado)🔗
Dado un proyecto Python con requirements.txt y código fuente, escribe un Dockerfile estructurado de forma que la capa de instalación de dependencias (pip install) no vuelva a ejecutarse cuando modifiques únicamente el código fuente de la aplicación.
Ejercicio 4.3: Usuario Sin Privilegios (non-root user) por Seguridad🔗
Modifica un Dockerfile de Python para que la aplicación no se ejecute como el usuario root. Crea un grupo y un usuario del sistema llamado appuser, asígnale los permisos del directorio /app y cambia a dicho usuario antes de definir la instrucción CMD.
Dockerfile:
Ejercicio 4.4: Contenerización de una API Web con FastAPI y .dockerignore🔗
- Crea un archivo
.dockerignorepara excluir entornos virtuales (.venv), archivos de Git (.git), y cachés (__pycache__). - Escribe un
Dockerfilepara empaquetar una API en FastAPI que escuche en el puerto8000usandouvicorn.
Ejercicio 4.5: Imagen de Producción Reducida con Multi-Stage Build (Avanzado)🔗
Diseña una imagen multi-etapa (multi-stage build) para una aplicación Python con dependencias pesadas compiladas.
- La etapa
builderdebe incluir herramientas de compilación (build-essential). - La etapa
runnerdebe partir depython:3.13-slim, copiar solo las librerías instaladas y el código, manteniendo un tamaño de imagen final óptimo.
5. Jupyter-server🔗
En bash podemos crear el archivo de variables de entorno con un token único:
| bash | |
|---|---|
O en powershell:
$token = [Convert]::ToBase64String((1..16 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })) -replace '[^a-zA-Z0-9]', ''
"JUPYTER_TOKEN=$token" | Out-File -FilePath .env -Encoding utf8
Luego:
services:
jupyter:
container_name: jupyter-server
image: quay.io/jupyter/base-notebook
ports:
- "8888:8888"
volumes:
- .:/datos
working_dir: /datos
command: start-notebook.py --NotebookApp.token=${JUPYTER_TOKEN}
Lanzamos y ejecutamos docker exec -it jupyter-server jupyter server list para obtener el enlace directo al servidor.
Ahora podemos:
- Entrar con el enlace a la interfaz web del servidor o,
- Invocar desde
VS Codecon el pluginJupyterde Microsoft
Instalando requerimientos en el servidor🔗
Desgraciadamente si corremos cualquiera de los notebooks veremos enseguida que tenemos algunos errores ya que no tenemos las librerías empleadas en los notebooks.
Para instalarlas:
FROM quay.io/jupyter/base-notebook:latest
# Copiar el archivo de requerimientos e instalar dependencias
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
Y ahora empleamos el compose:
services:
jupyter:
build:
context: .
dockerfile: Dockerfile
container_name: jupyter-server
ports:
- "8888:8888"
volumes:
- .:/datos
working_dir: /datos
command: start-notebook.py --NotebookApp.token=${JUPYTER_TOKEN}
El archivo de requisitos:
Creamos la imagen con docker compose build y luego lo levantamos con docker compose up -d
Warning
Recuerda regenerar la imagen después de cada modificación del archivo de requisitos requirements.txt
Ejercicios de ampliación🔗
- Compilando para navegador con
WASM. - Análisis de datos intensivo con python en el navegador.
Fuentes🔗
-
Aunque tenemos varios modos de red nos bastará con el modo
bridge(el por defecto). ↩