Volver al blog
data-engineering

Docker para Data Engineers: Lo que realmente necesitás saber

Docker sin el fluff. Comandos esenciales, best practices y cómo dockerizar tus pipelines.

17 de mayo de 20269 min
dockercontainersdevops

Docker resolvió el problema clásico de los data engineers: "funciona en mi máquina". Si tu pipeline corre en local pero falla en producción por diferencias de entorno, Docker es la solución. Guía práctica enfocada en casos de uso de datos.

El problema concreto

# En tu MacBook con Python 3.11
pip install pandas==2.1.0 scikit-learn==1.4.0
python train_model.py  # ✅ funciona

# En el servidor de producción con Python 3.9
# scikit-learn 1.4.0 requiere Python 3.10+
# 💥 ImportError: cannot import name 'PolynomialCountSketch'

Docker encapsula todo: Python version, dependencias, variables de entorno, y el código.

Dockerfile para un pipeline de datos

# Usar imagen oficial de Python slim (más liviana)
FROM python:3.11-slim

# Metadatos
LABEL maintainer="data-team@company.com"
LABEL version="1.0"

# Variables de entorno
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

# Directorio de trabajo
WORKDIR /app

# Instalar dependencias del sistema (si son necesarias)
RUN apt-get update && apt-get install -y \
    libpq-dev \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Copiar requirements PRIMERO (para aprovechar cache de Docker)
COPY requirements.txt .

# Instalar dependencias Python
RUN pip install --no-cache-dir -r requirements.txt

# Copiar el código del pipeline
COPY src/ ./src/
COPY config/ ./config/

# Usuario no-root por seguridad
RUN adduser --disabled-password --gecos "" appuser
USER appuser

# Comando por defecto
CMD ["python", "src/main.py"]

docker-compose para desarrollo local

# docker-compose.yml
version: "3.8"

services:
  pipeline:
    build: .
    environment:
      - GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-sa.json
      - POSTGRES_URL=postgresql://user:pass@postgres:5432/analytics
      - ENV=development
    volumes:
      - ./src:/app/src          # Hot reload del código
      - ~/.config/gcloud:/secrets  # Credenciales GCP
      - ./data:/app/data        # Datos locales
    depends_on:
      postgres:
        condition: service_healthy
    command: python src/main.py --date 2026-01-15

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: analytics
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d analytics"]
      interval: 5s
      timeout: 5s
      retries: 5

  jupyter:
    image: jupyter/scipy-notebook:latest
    ports:
      - "8888:8888"
    volumes:
      - ./notebooks:/home/jovyan/work
    environment:
      - JUPYTER_TOKEN=secret

volumes:
  postgres_data:
# Comandos útiles
docker-compose up pipeline    # Correr el pipeline
docker-compose up -d postgres # Postgres en background
docker-compose logs -f        # Ver logs en tiempo real
docker-compose exec pipeline bash  # Shell dentro del container

Multi-stage build para imágenes más pequeñas

# Stage 1: Build (con herramientas de compilación)
FROM python:3.11-slim as builder

WORKDIR /app
RUN pip install poetry

COPY pyproject.toml poetry.lock .
RUN poetry export -f requirements.txt --output requirements.txt

# Stage 2: Runtime (imagen final liviana)
FROM python:3.11-slim as runtime

WORKDIR /app

COPY --from=builder /app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY src/ ./src/

USER nobody
CMD ["python", "src/main.py"]

Resultado: imagen de ~200 MB vs ~800 MB sin multi-stage.

Patterns para pipelines de datos

Variables de entorno y secrets

import os
from functools import lru_cache
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    # Obligatorias — fallan si no están
    postgres_url: str
    gcs_bucket: str
    
    # Opcionales con defaults
    batch_size: int = 10000
    max_retries: int = 3
    env: str = "production"
    
    class Config:
        env_file = ".env"        # Para desarrollo local
        env_file_encoding = "utf-8"

@lru_cache
def get_settings() -> Settings:
    return Settings()

settings = get_settings()

Logging que funciona con Docker

import logging
import sys

# Docker captura stdout/stderr automáticamente
logging.basicConfig(
    stream=sys.stdout,          # stdout, no archivo
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)

Deploy en Cloud Run

# Build y push a Google Container Registry
docker build -t gcr.io/my-project/data-pipeline:v1.0 .
docker push gcr.io/my-project/data-pipeline:v1.0

# Deploy en Cloud Run (serverless)
gcloud run jobs create data-pipeline \
  --image gcr.io/my-project/data-pipeline:v1.0 \
  --region us-central1 \
  --memory 4Gi \
  --cpu 2 \
  --max-retries 3 \
  --set-secrets POSTGRES_URL=postgres-url:latest \
  --set-env-vars GCS_BUCKET=my-bucket

# Ejecutar manualmente
gcloud run jobs execute data-pipeline

Docker + Cloud Run es la combinación que uso para pipelines batch. Sin gestionar servidores, con reproducibilidad garantizada.

Escrito por Mariano Gobea Alcoba