De Junior a Pro: Controlando tus entornos de ejecución
Creado por Irina IchimDocker 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 ecosistema Java es robusto pero pesado, y el de React es ligero pero volátil (las versiones de Node son un caos).
| 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 |
Antes de empezar a construir, necesitas entender estos conceptos fundamentales:
. indica "usa el Dockerfile del directorio actual"-p mapea puertos: puerto_host:puerto_contenedor-it = modo interactivo con terminal-f = seguir logs en tiempo real--build reconstruye las imágenesdocker exec -it backend-java ping database. Si el ping falla, es la red. Si el ping funciona, es tu application.properties
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
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
.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
La magia de Docker es que es (casi) igual en todos lados.
| 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 |
--platform linux/amd64 si vas a desplegar en la nube tradicionalVamos a crear un Dockerfile profesional para una aplicación Spring Boot. Usaremos una build multietapa para optimizar el tamaño de la imagen final.
# ========================================
# 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"]
pom.xml ANTES que el código fuente. Así, si solo cambias código Java, Docker reutiliza las dependencias ya descargadas. ¡Ahorra mucho tiempo!
root. Creamos un usuario específico spring con permisos limitados.
# 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>
Para React, también usaremos una build multietapa: compilamos con Node y servimos con Nginx.
# ========================================
# 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;"]
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;
}
}
npm start) NO está optimizado para producción# 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;
# Construcción con variable de entorno
docker build \
--build-arg REACT_APP_API_URL=http://backend:8080 \
-t mi-app-react .
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.
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 en la raíz del proyecto
DB_PASSWORD=super_secreto_123
.env a GitHub.gitignore.env.example con las variables pero SIN valores realesCuando desarrollamos en local sin Docker, todo es localhost. Pero dentro de Docker, cada contenedor es como una casa independiente en una misma calle.
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 |
localhost porque el navegador está en TU máquinaCORS (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.
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);
}
}
Si usas allowCredentials(true), NO puedes usar allowedOrigins("*"). Debes especificar los orígenes exactos.
// 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
Lo que "parece que funciona" pero te dará problemas a largo plazo:
FROM node:latest o FROM openjdk:latest
-slim o -alpinenode:18-alpine
Dockerfile o docker-compose.yml
.env y NUNCA lo subas a Git
# ❌ MAL - Hardcoded
POSTGRES_PASSWORD: mi_password_super_secreta
# ✅ BIEN - Variable de entorno
POSTGRES_PASSWORD: ${DB_PASSWORD}
.dockerignore
node_modules, .git, etc. El build tarda siglos
.dockerignore es OBLIGATORIO
# Ejemplo de .dockerignore
node_modules
target
.git
.env
.vscode
*.log
coverage
build
dist
FROM postgres:latestFROM node:latest
postgres:15-alpinenode:18-alpine
docker system prune
docker system prune -a
La IA no solo sirve para escribir código, también puede ayudarte a optimizar, debuggear y aprender Docker más rápido.
"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."
"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]"
"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]"
"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."
"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 no solo sirve para Java/React, sirve para "empaquetar inteligencia"
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.
"Hoy despliegas Java, mañana un modelo de reconocimiento de imágenes. El comando docker build es el mismo".
# 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."
Lista de verificación mental antes de desplegar. Esto reduce la ansiedad de "no sé si me falta algo".
| 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 |
Si el backend no conecta, haz un:
docker exec -it backend-java ping database
Ahora tienes las herramientas para empaquetar, desplegar
y escalar aplicaciones profesionales
Irina Ichim
Full-Stack Developer & Tech Lead |