Docker para Desarrolladoras de IA

De Junior a Pro: Controlando tus entornos de ejecución

Creado por Irina Ichim
Full-Stack Developer & Tech Lead

1. ¿Por qué Docker? 🤔

Docker no es una herramienta de 'infraestructura', es una herramienta de desarrollo. Si sabes Docker, tu valor en el mercado se multiplica porque eres capaz de entregar software que funciona, no solo código que compila.

El Problema Real

El ecosistema Java es robusto pero pesado, y el de React es ligero pero volátil (las versiones de Node son un caos).

💡 Para Java
Se acabó el "instalar el JDK 17, configurar el Maven, las variables de entorno...". Ahora el JDK vive dentro del contenedor.
⚛️ Para React
Olvidaos de los conflictos de versiones de npm. El contenedor tiene la versión exacta de Node que vuestra app necesita.
🤖 En IA
Si vuestro backend de Java llama a un servicio de IA, Docker asegura que esa conexión (API) funcione igual en local que en la nube.

La Tabla de la Verdad: Antes vs. Después

Situación Antes (Sin Docker) Ahora (Con Docker)
Nueva compañera se une Tarda 1 día configurando Java, Maven, Node... Hace docker-compose up y empieza en 5 min
Versiones de Node "Uf, tengo la 16 y el proyecto pide la 20" El contenedor ya trae la 20. Cero conflictos
Base de Datos "Se me olvidó cómo se instalaba Postgres en Windows" Una línea en el Compose y ya tienes DB
Despliegue "En mi ordenador funcionaba, no sé por qué falla" Si funciona en el contenedor, funciona en la nube

2. Vocabulario Docker 📖

Antes de empezar a construir, necesitas entender estos conceptos fundamentales:

📦
Imagen (Image)
La Receta. Es una plantilla inmutable que contiene todo lo necesario para ejecutar tu aplicación: código, runtime, librerías, configuración.
Ejemplo: "node:18-alpine" es una imagen oficial de Node.js
🏃‍♀️
Contenedor (Container)
El Plato Servido. Es una instancia en ejecución de una imagen. Puedes tener múltiples contenedores de la misma imagen corriendo simultáneamente.
Ejemplo: Tu backend de Java corriendo en el puerto 8080
💾
Volumen (Volume)
El Almacén Persistente. Espacio de almacenamiento que sobrevive cuando el contenedor se detiene. Crucial para bases de datos.
Ejemplo: Los datos de PostgreSQL que no quieres perder
🌐
Network (Red)
El Sistema de Comunicación. Permite que los contenedores se hablen entre sí usando nombres en lugar de IPs.
Ejemplo: El frontend puede llamar a "backend:8080"
📝
Dockerfile
El Libro de Instrucciones. Archivo de texto que define cómo construir una imagen. Cada línea es un paso.
Ejemplo: FROM, COPY, RUN, CMD son instrucciones
🎼
Docker Compose
El Director de Orquesta. Archivo YAML que define y ejecuta aplicaciones multi-contenedor con un solo comando.
Ejemplo: Levanta DB + Backend + Frontend de una vez
🔄 Flujo: De Imagen a Contenedor
📄
Dockerfile
Instrucciones
🏗️
docker build
Construcción
📦
Imagen
Plantilla
▶️
docker run
Ejecución
🏃‍♀️
Contenedor
App corriendo

3. Comandos Esenciales 🛠️

Comandos Básicos

docker build -t mi-modelo .
Construye la imagen (cocina la receta). El . indica "usa el Dockerfile del directorio actual"
docker run -p 8080:8080 mi-modelo
Lanza el contenedor (sirve el plato). -p mapea puertos: puerto_host:puerto_contenedor
docker ps
¿Qué está pasando ahora mismo? Lista los contenedores en ejecución
docker ps -a
Lista TODOS los contenedores (incluyendo los detenidos)
docker exec -it <id> bash
"Entrar" dentro del contenedor para cotillear. -it = modo interactivo con terminal
docker logs -f <nombre>
Para ver por qué el Spring Boot está lanzando un error. -f = seguir logs en tiempo real
docker compose up --build
Levanta el Backend (Java) y el Frontend (React) a la vez. --build reconstruye las imágenes
docker compose down -v
El "borrón y cuenta nueva". Detiene contenedores y borra volúmenes

Gestión de Limpieza (Para no quedarse sin disco)

⚠️ Importante: Docker ocupa espacio
Con el tiempo, Docker acumula imágenes, contenedores detenidos y volúmenes huérfanos. Tu disco puede llenarse rápidamente.
docker system prune -a
El botón nuclear. Borra todo lo que no se esté usando (imágenes, redes y caché). Úsalo cuando Docker diga que no tiene espacio
docker volume ls
Lista todos los volúmenes existentes
docker volume prune
Limpia volúmenes que no están siendo usados por ningún contenedor
docker images
Muestra todas las imágenes descargadas y su tamaño
docker rmi <imagen>
Elimina una imagen específica

Inspección (Para cuando algo falla)

docker inspect <nombre>
Te da un JSON gigante con TODA la info (IPs, rutas, variables de entorno)
docker stats
Para ver cuánta RAM y CPU está consumiendo tu Java (¡Spring Boot a veces tiene hambre de RAM!)
docker top <nombre>
Para ver los procesos que corren dentro del contenedor sin entrar en él

Comandos de "Pánico" (Para cuando algo no va bien)

💡 Tip de Debugging
Si el backend no conecta, haz un docker exec -it backend-java ping database. Si el ping falla, es la red. Si el ping funciona, es tu application.properties
🔧 Herramientas Visuales
Herramientas como LazyDocker o la extensión de Docker para VS Code ayudan mucho al principio. Ver los contenedores en una lista visual es más intuitivo que memorizar comandos.

3.5 Preparando el Proyecto 📁

El Archivo .dockerignore (Obligatorio)

⚠️ Consejo
¡Nunca metáis la carpeta node_modules ni el /target de Java dentro de la imagen! Eso hace que la imagen pese gigas y sea lentísima.

Igual que el .gitignore, el .dockerignore es vital. Le dice a Docker qué archivos NO copiar durante el build.

# .dockerignore para proyecto Java + React # Node.js node_modules/ npm-debug.log yarn-error.log .npm # Java/Maven target/ *.jar *.war *.class .mvn # Git .git/ .gitignore .gitattributes # Variables de entorno sensibles .env .env.local .env.production .env.*.local # IDEs .vscode/ .idea/ *.iml *.swp *.swo # Logs y caché *.log logs/ coverage/ build/ dist/ # Docker docker-compose.yml Dockerfile* # Documentación (opcional) README.md docs/ *.md

Estructura Típica del Proyecto

mi-proyecto/ ├── backend/ # Java Spring Boot │ ├── src/ │ ├── pom.xml │ ├── Dockerfile │ └── .dockerignore │ ├── frontend/ # React │ ├── src/ │ ├── public/ │ ├── package.json │ ├── Dockerfile │ ├── nginx.conf │ └── .dockerignore │ ├── docker-compose.yml # Orquestación ├── .env # Variables (NO subir a Git) ├── .env.example # Plantilla (SÍ subir a Git) └── README.md
💡 Tip Pro
Crea un archivo .env.example con las mismas variables que .env pero sin valores reales:
# .env.example DB_PASSWORD=your_password_here API_KEY=your_api_key_here
Así tus compañeras saben qué variables necesitan configurar.

4. Entornos: Windows, Mac y Linux 💻

La magia de Docker es que es (casi) igual en todos lados.

Diferencias por Sistema Operativo

Característica Windows (WSL2) macOS (Apple Silicon) Linux (Ubuntu)
Instalación Docker Desktop Docker Desktop Docker Engine (terminal)
Rendimiento Muy bueno gracias a WSL2 Bueno, pero emula arquitectura x86 a veces El mejor (Nativo)
Consumo RAM Docker Desktop puede comerse 4GB solo por arrancar Gestionado por el sistema, pero alto Solo consume lo que consumen los procesos
Rutas de archivos C:\Users... (Cuidado con permisos) /Users/... /home/...
Interfaz Visual (Docker Desktop) Visual (Docker Desktop) Principalmente Terminal

Tips Específicos por Sistema

🪟 Windows
🍎 Mac (M1/M2/M3)
🐧 Linux

5. Hands-on Java: Dockerfile Multietapa 🔨

Vamos a crear un Dockerfile profesional para una aplicación Spring Boot. Usaremos una build multietapa para optimizar el tamaño de la imagen final.

🏗️ Multi-Stage Build: Cómo funciona
⚙️
Etapa 1: BUILD
Maven compila el código
Imagen pesada (~600MB)
📦
Genera .jar
app-0.0.1.jar
Solo el ejecutable
🚀
Etapa 2: RUN
Solo JRE + .jar
Imagen ligera (~150MB)

Dockerfile Completo

# ======================================== # ETAPA 1: COMPILACIÓN (Build Stage) # ======================================== # Usamos una imagen con Maven y JDK para compilar FROM maven:3.8.6-eclipse-temurin-17 AS build # Establecemos el directorio de trabajo WORKDIR /app # Copiamos los archivos de configuración de Maven primero # (Esto optimiza el cache de Docker) COPY pom.xml . RUN mvn dependency:go-offline # Ahora copiamos el código fuente COPY src ./src # Compilamos la aplicación (genera el .jar) RUN mvn clean package -DskipTests # ======================================== # ETAPA 2: EJECUCIÓN (Runtime Stage) # ======================================== # Usamos una imagen ligera solo con Java Runtime (JRE) FROM eclipse-temurin:17-jre-alpine # Creamos un usuario no-root por seguridad RUN addgroup -S spring && adduser -S spring -G spring USER spring:spring # Copiamos SOLO el .jar de la etapa anterior COPY --from=build /app/target/*.jar app.jar # Exponemos el puerto (informativo, no abre el puerto) EXPOSE 8080 # Comando para ejecutar la aplicación ENTRYPOINT ["java", "-jar", "/app.jar"]
💡 ¿Por qué dos etapas?

Optimizaciones Profesionales

⚠️ Caché de Dependencias
Copiamos pom.xml ANTES que el código fuente. Así, si solo cambias código Java, Docker reutiliza las dependencias ya descargadas. ¡Ahorra mucho tiempo!
🔒 Seguridad
Nunca ejecutes aplicaciones como root. Creamos un usuario específico spring con permisos limitados.

Construir y Ejecutar

# Construir la imagen
docker build -t mi-app-java .

# Ejecutar el contenedor
docker run -p 8080:8080 mi-app-java

# Ver los logs
docker logs -f <nombre_contenedor>

    

6. Hands-on React: Dockerfile con Nginx ⚛️

Para React, también usaremos una build multietapa: compilamos con Node y servimos con Nginx.

⚛️ React Build Process
📦
Etapa 1: BUILD
npm install + npm build
Node + dependencias
📁
/build
HTML + CSS + JS
Archivos estáticos
🌐
Etapa 2: SERVE
Nginx sirve archivos
Imagen ultra-ligera

Dockerfile Completo

# ======================================== # ETAPA 1: COMPILACIÓN # ======================================== FROM node:18-alpine AS build # Directorio de trabajo WORKDIR /app # Copiamos package.json y package-lock.json # (Optimización de caché) COPY package*.json ./ # Instalamos dependencias RUN npm ci --only=production # Copiamos el código fuente COPY . . # Variables de entorno para el build ARG REACT_APP_API_URL ENV REACT_APP_API_URL=$REACT_APP_API_URL # Compilamos la aplicación (genera /build) RUN npm run build # ======================================== # ETAPA 2: SERVIDOR WEB # ======================================== FROM nginx:alpine # Copiamos los archivos compilados desde la etapa anterior COPY --from=build /app/build /usr/share/nginx/html # Copiamos configuración personalizada de nginx (opcional) COPY nginx.conf /etc/nginx/conf.d/default.conf # Exponemos el puerto 80 EXPOSE 80 # Nginx se inicia automáticamente con esta imagen CMD ["nginx", "-g", "daemon off;"]

Configuración de Nginx (nginx.conf)

server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; # Para React Router: redirige todo a index.html try_files $uri $uri/ /index.html; } # Configuración de CORS si es necesario location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
💡 ¿Por qué Nginx y no el servidor de desarrollo?

Variables de Entorno en React

⚠️ Importante sobre ENV en React
Las variables de entorno en React se "queman" durante el build. NO puedes cambiarlas después sin recompilar.
# Archivo .env.development REACT_APP_API_URL=http://localhost:8080 # Archivo .env.production REACT_APP_API_URL=http://backend:8080 # En el código React const API_URL = process.env.REACT_APP_API_URL;

Construir con Variables

# Construcción con variable de entorno
docker build \
  --build-arg REACT_APP_API_URL=http://backend:8080 \
  -t mi-app-react .

    

7. Orquestación: Docker Compose 🎼

Docker Compose permite definir y ejecutar aplicaciones multi-contenedor. Es el pegamento que une todo.

Con un solo comando (docker-compose up) levantas toda tu arquitectura: base de datos, backend y frontend.

Arquitectura Completa

Aplicación Completa con Docker Compose
🗄️
database
PostgreSQL 15
Puerto: 5432
Almacena datos
Volumen persistente
backend
Spring Boot (Java)
Puerto: 8080
API REST
Conecta con database
⚛️
frontend
React + Nginx
Puerto: 3000
Interfaz de usuario
Llama a backend:8080
Red Interna Docker: Los contenedores se comunican usando sus nombres como hostnames

docker-compose.yml Completo

version: '3.8' services: # ======================================== # BASE DE DATOS # ======================================== database: image: postgres:15-alpine container_name: postgres-db environment: POSTGRES_DB: myapp POSTGRES_USER: myuser POSTGRES_PASSWORD: ${DB_PASSWORD} # Desde .env ports: - "5432:5432" # Solo para desarrollo local volumes: - postgres_data:/var/lib/postgresql/data networks: - app-network healthcheck: test: ["CMD-SHELL", "pg_isready -U myuser"] interval: 10s timeout: 5s retries: 5 # ======================================== # BACKEND (Java Spring Boot) # ======================================== backend: build: context: ./backend dockerfile: Dockerfile container_name: java-backend depends_on: database: condition: service_healthy # Espera a que DB esté lista environment: SPRING_DATASOURCE_URL: jdbc:postgresql://database:5432/myapp SPRING_DATASOURCE_USERNAME: myuser SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD} SPRING_JPA_HIBERNATE_DDL_AUTO: update ports: - "8080:8080" networks: - app-network # ======================================== # FRONTEND (React) # ======================================== frontend: build: context: ./frontend dockerfile: Dockerfile args: REACT_APP_API_URL: http://localhost:8080 # Para desarrollo container_name: react-frontend depends_on: - backend ports: - "3000:80" # Nginx escucha en 80, mapeamos a 3000 networks: - app-network # ======================================== # REDES Y VOLÚMENES # ======================================== networks: app-network: driver: bridge volumes: postgres_data: # Persistencia de datos

Archivo .env (Nunca subir a Git)

# Archivo .env en la raíz del proyecto DB_PASSWORD=super_secreto_123
🚨 Seguridad Crítica

Comandos de Docker Compose

docker-compose up
Levanta todos los servicios en modo attached (ves los logs)
docker-compose up -d
Levanta en segundo plano (detached mode)
docker-compose up --build
Reconstruye las imágenes antes de levantar
docker-compose down
Detiene y elimina contenedores y redes
docker-compose down -v
También elimina los volúmenes (⚠️ borras la DB)
docker-compose logs -f <servicio>
Ver logs de un servicio específico en tiempo real
docker-compose ps
Lista el estado de los servicios
docker-compose exec <servicio> bash
Entrar en un contenedor específico

8. Networking y CORS 🌐

Cuando desarrollamos en local sin Docker, todo es localhost. Pero dentro de Docker, cada contenedor es como una casa independiente en una misma calle.

La Red Interna (Docker Network)

📡 Regla de Oro
Los contenedores se hablan usando el nombre del servicio definido en docker-compose.yml.
Desde Hacia URL Correcta URL Incorrecta
Backend Database jdbc:postgresql://database:5432/mydb localhost:5432
Frontend (navegador) Backend http://localhost:8080/api http://backend:8080
Frontend (dentro del contenedor) Backend http://backend:8080/api localhost:8080
⚠️ Atención: Dos contextos diferentes

El Fantasma del CORS

CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad del navegador que impide que tu React (puerto 3000) hable con tu Java (puerto 8080) a menos que Java le dé permiso explícito.

🚫 El Problema del CORS
❌ Sin configuración CORS: Access to fetch at 'http://localhost:8080/api/users' from origin
'http://localhost:3000' has been blocked by CORS policy
✅ Con configuración CORS: Response from backend: { "users": [...] }

Solución CORS en Spring Boot

package com.example.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // Todas las rutas .allowedOrigins( "http://localhost:3000", // Desarrollo local "http://frontend:80" // Dentro de Docker ) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }
⚠️ Importante sobre CORS

Si usas allowCredentials(true), NO puedes usar allowedOrigins("*"). Debes especificar los orígenes exactos.

💡 Alternativa: Proxy Inverso con Nginx
Otra solución es configurar Nginx para que sirva tanto el frontend como el backend bajo el mismo dominio, eliminando el problema CORS por completo.

Variables de Entorno: No cambies el código

// En React: src/config.js const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080'; export const fetchUsers = async () => { const response = await fetch(`${API_URL}/api/users`); return response.json(); };
# En desarrollo local (.env.development) REACT_APP_API_URL=http://localhost:8080 # En Docker (.env.production o en docker-compose) REACT_APP_API_URL=http://backend:8080

9. Antipatrones: Errores Comunes 🚫

Lo que "parece que funciona" pero te dará problemas a largo plazo:

1. 🍔 El Antipatrón "Imagen Obesa"
Error:
Usar FROM node:latest o FROM openjdk:latest
Por qué está mal:
Estas imágenes pesan +800MB y tienen herramientas innecesarias
Solución:
Usa -slim o -alpine
Ej: node:18-alpine
2. 🔑 El "Hardcoding" de Secretos
Error:
Escribir contraseñas directamente en Dockerfile o docker-compose.yml
Por qué está mal:
Si subes ese código a GitHub, cualquiera tendrá tus llaves
Solución:
Usa archivo .env y NUNCA lo subas a Git
# ❌ MAL - Hardcoded POSTGRES_PASSWORD: mi_password_super_secreta # ✅ BIEN - Variable de entorno POSTGRES_PASSWORD: ${DB_PASSWORD}
3. 🏗️ El Síndrome de "Todo en Uno"
Error:
Meter Java, React y PostgreSQL en el MISMO contenedor
Por qué está mal:
Si actualizas React, tienes que reiniciar también la base de datos
Solución:
Un proceso por contenedor - Docker Compose para orquestar
4. 📂 Ignorar el .dockerignore
Error:
No crear un archivo .dockerignore
Por qué está mal:
Docker copia node_modules, .git, etc. El build tarda siglos
Solución:
Crear .dockerignore es OBLIGATORIO
# Ejemplo de .dockerignore node_modules target .git .env .vscode *.log coverage build dist
5. 🔄 Usar :latest en Producción
Error:
FROM postgres:latest
FROM node:latest
Por qué está mal:
"latest" cambia con el tiempo. Tu app puede romperse sin previo aviso cuando salga una nueva versión
Solución:
Usa versiones específicas:
postgres:15-alpine
node:18-alpine
6. 🗑️ No limpiar imágenes antiguas
Error:
Nunca ejecutar docker system prune
Por qué está mal:
Docker acumula gigas de imágenes huérfanas, contenedores detenidos y volúmenes no usados. Tu disco se llena
Solución:
Limpia regularmente con:
docker system prune -a

10. IA como Copilot 🤖

La IA no solo sirve para escribir código, también puede ayudarte a optimizar, debuggear y aprender Docker más rápido.

Prompts Útiles para Docker

💡 Generación de Dockerfiles
"Soy desarrolladora Junior, tengo un microservicio en Spring Boot 3 con Java 17. Necesito un Dockerfile multietapa que use Maven para compilar y una imagen ligera para correr. Además, necesito un docker-compose que incluya una base de datos PostgreSQL."
🔍 Optimización de Imágenes
"Actúa como una experta en DevOps. Revisa este Dockerfile para una aplicación de Java y React. Busca antipatrones, problemas de seguridad y dime cómo puedo reducir el tamaño de la imagen final. [Pega aquí tu Dockerfile]"
🐛 Debugging de Errores
"Mi contenedor de React da error 'ECONNREFUSED' al intentar conectar con el contenedor de Java en Docker Compose. Aquí tienes mi archivo docker-compose.yml, ¿qué estoy configurando mal? [Pega tu docker-compose.yml]"
🌐 Configuración de Networking
"Soy mentora y quiero explicar cómo configurar un Proxy Inverso con Nginx en Docker para que Java y React parezcan estar en el mismo dominio y evitar problemas de CORS. Dame el archivo nginx.conf con comentarios explicativos."
📚 Explicaciones Didácticas
"Explica qué hace cada línea de este Dockerfile como si estuvieras enseñando a desarrolladoras que nunca han usado Docker: [Pega tu Dockerfile]"

Docker en Proyectos de IA

Docker no solo sirve para Java/React, sirve para "empaquetar inteligencia"
🤖 Modelos como Servicios

En el futuro podrás meter un modelo de Python (como una API de inferencia de FastAPI) en un contenedor y tu backend de Java lo llamará exactamente igual que llama a la base de datos hoy.

🔄 Portabilidad de la IA

"Hoy despliegas Java, mañana un modelo de reconocimiento de imágenes. El comando docker build es el mismo".

Prompt para Integración IA + Docker

# Prompt de ejemplo "Dame un Dockerfile optimizado para una aplicación FastAPI que usa: - Python 3.11 - TensorFlow para inferencia - Acceso a GPU con CUDA - Variables de entorno para la API key de OpenAI Incluye también un docker-compose que conecte esta API con un backend de Spring Boot que la consume."

11. Checklist de Producción ✅

Lista de verificación mental antes de desplegar. Esto reduce la ansiedad de "no sé si me falta algo".

¿He limpiado el código?
No hay console.log ni System.out.println innecesarios
¿Existe un .dockerignore?
Compruebo que no subo node_modules ni target
¿Variables de entorno configuradas?
Nada de contraseñas en el código. Uso .env
¿Imágenes con versiones específicas?
No uso :latest en producción
¿Healthcheck configurado?
Docker espera a que la DB esté lista antes de lanzar Java
¿Volúmenes para persistencia?
He definido volúmenes para que la base de datos no se borre
¿CORS configurado?
El frontend puede comunicarse con el backend
¿Logs accesibles?
Sé cómo ver logs de cada servicio con docker logs
¿Usuario no-root?
Por seguridad, los contenedores no corren como root
¿Documentación mínima?
README.md con instrucciones de docker-compose up

Herramientas Recomendadas

Herramienta Para qué sirve Link
Docker Desktop Interfaz visual para gestionar contenedores (Windows/Mac) docker.com
LazyDocker TUI (Terminal UI) para gestionar Docker desde terminal github.com/jesseduffield/lazydocker
Docker Extension (VS Code) Gestionar contenedores directamente desde VS Code Buscar "Docker" en extensiones
dive Analizar imágenes capa por capa para optimizar tamaño github.com/wagoodman/dive
hadolint Linter para Dockerfiles (encuentra errores comunes) github.com/hadolint/hadolint

Gestión de Logs y Debugging Real

🕵️‍♀️ Tip de Debugging

Si el backend no conecta, haz un:

docker exec -it backend-java ping database
🐳

¡Ya eres una Docker Hero!

Ahora tienes las herramientas para empaquetar, desplegar
y escalar aplicaciones profesionales

Irina Ichim

Full-Stack Developer & Tech Lead |