Docker es una herramienta que empaqueta tu aplicación junto con todo lo que necesita para correr, sin importar en qué servidor esté.
El problema que Docker resuelve
Imagina que eres desarrollador en una empresa como Liverpool. Terminas tu código, lo pruebas en tu laptop y funciona perfecto. Lo subes al servidor de producción y, de repente, la aplicación falla. El equipo de operaciones te dice: "en mi máquina no pasa nada". Tú dices lo mismo. Nadie sabe qué está mal.
Este problema tiene nombre: el síndrome de "en mi máquina sí funciona". Ocurre porque tu laptop tiene una versión de Python, el servidor tiene otra. Tu laptop tiene ciertas librerías instaladas, el servidor tiene versiones diferentes. Las rutas de archivos, los puertos, las variables de entorno... todo puede ser distinto.
Docker elimina ese problema de raíz. En lugar de enviar solo tu código, envías una caja sellada que incluye el código, el sistema operativo base, las librerías, la versión exacta de Python y la configuración. Esa caja se llama contenedor.
El Sistema de Cajas Selladas
Piensa en un contenedor de barco. El contenido llega igual a Shanghai, Rotterdam o Veracruz porque la caja protege todo adentro. Docker funciona igual.
Este sistema tiene tres piezas principales:
- Dockerfile: la receta para construir tu caja. Es un archivo de texto con instrucciones.
- Imagen: la caja construida y lista. Es estática, no cambia.
- Contenedor: la imagen corriendo. Es tu aplicación ejecutándose dentro de la caja.
La relación es simple: el Dockerfile genera una imagen, y la imagen genera uno o varios contenedores. Puedes arrancar cien contenedores del mismo contenedor y todos se comportarán igual.
Tu primer Dockerfile
Supón que tienes una API en Python que calcula bonos de nómina para empleados de FEMSA. El archivo principal se llama app.py y usa la librería Flask.
Así luce un Dockerfile básico:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Línea por línea:
FROM python:3.11-slim— parte de una imagen base oficial con Python 3.11 ya instalado.WORKDIR /app— define la carpeta de trabajo dentro del contenedor.COPY requirements.txt .— copia la lista de librerías antes de instalarlas.RUN pip install -r requirements.txt— instala las dependencias.COPY . .— copia el resto de tu código.EXPOSE 5000— documenta que la aplicación usa el puerto 5000.CMD ["python", "app.py"]— el comando que arranca tu aplicación.
Este orden importa. Docker guarda cada línea como una capa. Si solo cambias tu código (COPY . .), Docker reutiliza las capas anteriores desde el caché. Eso hace que las siguientes construcciones sean mucho más rápidas.
Construir y correr el contenedor
Con el Dockerfile listo, abre tu terminal en la misma carpeta y ejecuta:
docker build -t femsa-bonos:1.0 .
Esto construye la imagen y le pone la etiqueta femsa-bonos:1.0. El punto al final le dice a Docker que busque el Dockerfile en la carpeta actual.
Para arrancar un contenedor con esa imagen:
docker run -p 8080:5000 femsa-bonos:1.0
El flag -p 8080:5000 conecta el puerto 8080 de tu laptop con el puerto 5000 del contenedor. Ahora puedes abrir tu navegador en http://localhost:8080 y ver tu aplicación corriendo dentro de Docker.
Un ejemplo completo con datos reales
Supon que tu app.py calcula el bono mensual de un empleado. Así luce una versión simplificada:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/bono")
def calcular_bono():
salario = 18500
porcentaje = 0.15
bono = salario * porcentaje
return jsonify({
"salario": f"${salario:,.0f}",
"bono": f"${bono:,.0f}",
"total": f"${salario + bono:,.0f}"
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Cuando accedes a http://localhost:8080/bono, la respuesta es:
{
"salario": "$18,500",
"bono": "$2,775",
"total": "$21,275"
}
Esa misma imagen corre igual en la laptop de cualquier desarrollador, en el servidor de staging y en producción. Sin sorpresas.
Mover la imagen entre servidores
Construir la imagen en tu laptop está bien para desarrollar. En producción necesitas que los servidores descarguen la imagen desde un lugar central. Ese lugar se llama registro de imágenes o registry.
Docker Hub es el registry público más común. Empresas como Bimbo o Mercado Libre usan registries privados dentro de sus nubes (AWS ECR, Google Artifact Registry o Azure Container Registry).
Para subir tu imagen a Docker Hub:
docker tag femsa-bonos:1.0 tuusuario/femsa-bonos:1.0
docker push tuusuario/femsa-bonos:1.0
Para descargarla en otro servidor:
docker pull tuusuario/femsa-bonos:1.0
docker run -p 8080:5000 tuusuario/femsa-bonos:1.0
Así de simple. El servidor no necesita tener Python instalado, ni Flask, ni ninguna librería. Solo necesita Docker.
Docker en el pipeline de CI/CD
Recuerda el pipeline de entrega continua que vimos en la lección anterior. Docker encaja perfectamente en ese flujo.
El pipeline de GitHub Actions puede construir y subir tu imagen automáticamente cada vez que haces push:
- name: Construir imagen Docker
run: docker build -t tuusuario/femsa-bonos:${{ github.sha }} .
- name: Subir al registry
run: docker push tuusuario/femsa-bonos:${{ github.sha }}
El github.sha es el identificador único del commit. Así cada versión de tu código tiene su propia imagen etiquetada. Si algo falla en producción, puedes regresar a la imagen del commit anterior con un solo comando. Ese es tu rollback automático.
Errores comunes al usar Docker
Error 1: copiar todo el proyecto sin filtrar
Si no tienes un archivo .dockerignore, Docker copiará tu carpeta node_modules, tu entorno virtual de Python, archivos .git y todo lo demás. La imagen se vuelve enorme sin razón. Crea un .dockerignore con:
__pycache__
.env
.git
venv/
*.pyc
Error 2: poner secretos dentro de la imagen
Nunca hagas COPY .env . en tu Dockerfile. El archivo .env con contraseñas queda grabado en la imagen para siempre. Cualquiera que descargue la imagen puede ver esos datos. Usa variables de entorno en tiempo de ejecución:
docker run -e DB_PASSWORD=mi_clave femsa-bonos:1.0
O mejor aún, usa un gestor de secretos como AWS Secrets Manager o HashiCorp Vault.
Error 3: correr todo como usuario root
Por defecto, los procesos dentro de un contenedor corren como root. Eso es un riesgo de seguridad. Agrega estas líneas antes del CMD:
RUN adduser --disabled-password appuser
USER appuser
Error 4: ignorar el tamaño de la imagen
Una imagen de 2 GB tarda más en descargarse y ocupa más espacio en el servidor. Usa imágenes base ligeras como python:3.11-slim en lugar de python:3.11. La diferencia puede ser de 800 MB a 150 MB.
La diferencia entre una imagen y un contenedor
Mucha gente confunde estos dos conceptos. Una imagen es como el molde de una pieza de fábrica: estático, reutilizable, no cambia. Un contenedor es la pieza ya fabricada y en uso: tiene estado, puede pararse y reiniciarse.
Puedes listar tus imágenes con docker images y tus contenedores activos con docker ps. Para detener un contenedor: docker stop <id>. Para eliminarlo: docker rm <id>.
Por qué Docker es clave en DevOps
En empresas como Mercado Libre, donde hay decenas de equipos desplegando código varias veces al día, Docker es la base de todo. Cada microservicio vive en su propio contenedor. Los equipos de desarrollo y operaciones hablan el mismo idioma: la imagen.
Docker no solo resuelve el problema técnico de los ambientes diferentes. También reduce la fricción entre equipos. Cuando tu pipeline entrega una imagen al equipo de infraestructura, ellos no necesitan saber cómo configurar Python. Solo corren la imagen.
La regla de oro de Docker: si funciona en tu imagen, funciona en cualquier servidor.