Docker para Desarrollo Web: Guía Práctica Completa

14 de diciembre de 2025
Osman Jimenez
Docker DevOps Desarrollo Web

Containerización con Docker

Docker revolucionó el desarrollo web al permitir entornos consistentes y reproducibles. Aprende a usar Docker efectivamente en tus proyectos.

Dockerfile para Node.js

# Multi-stage build para optimizar tamaño
FROM node:20-alpine AS builder

WORKDIR /app

# Copiar package files primero (cache layer)
COPY package*.json ./
RUN npm ci --only=production

# Copiar código fuente
COPY . .
RUN npm run build

# Imagen final
FROM node:20-alpine

WORKDIR /app

# Copiar solo lo necesario
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

# Usuario no-root por seguridad
USER node

EXPOSE 3000

CMD ["node", "dist/main.js"]

Dockerfile para Angular

# Build stage
FROM node:20-alpine AS build

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build -- --configuration=production

# Production stage
FROM nginx:alpine

COPY --from=build /app/dist/my-app /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Docker Compose para Stack Completo

version: '3.8'

services:
  # Frontend
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "4200:80"
    depends_on:
      - backend
    networks:
      - app-network
  
  # Backend API
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    networks:
      - app-network
    volumes:
      - ./backend:/app
      - /app/node_modules
  
  # PostgreSQL
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network
  
  # Redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - app-network

volumes:
  postgres-data:

networks:
  app-network:
    driver: bridge

Comandos Esenciales

# Build imagen
docker build -t my-app:latest .

# Run container
docker run -d -p 3000:3000 --name my-app my-app:latest

# Ver logs
docker logs -f my-app

# Ejecutar comando en container
docker exec -it my-app sh

# Docker Compose
docker-compose up -d
docker-compose down
docker-compose logs -f backend

# Limpiar
docker system prune -a
docker volume prune

Hot Reload en Desarrollo

# docker-compose.dev.yml
version: '3.8'

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    command: npm run dev

Dockerfile.dev con Hot Reload

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]

.dockerignore

node_modules
npm-debug.log
dist
.git
.gitignore
.env
.env.local
.DS_Store
coverage
*.md
.vscode
.idea

Optimizaciones

1. Layer Caching

# ❌ Malo: Invalida cache en cada cambio
COPY . .
RUN npm install

# ✅ Bueno: Cache de dependencias
COPY package*.json ./
RUN npm ci
COPY . .

2. Multi-stage Builds

# Reduce tamaño final de imagen
FROM node:20 AS builder
# ... build steps ...

FROM node:20-alpine
COPY --from=builder /app/dist ./dist

3. Usar Alpine

# node:20 = ~900MB
# node:20-alpine = ~170MB
FROM node:20-alpine

Health Checks

FROM node:20-alpine

# ...

HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD node healthcheck.js

Secrets y Variables de Entorno

# .env
DATABASE_URL=postgresql://user:pass@localhost:5432/db
JWT_SECRET=my-secret

# docker-compose.yml
services:
  backend:
    env_file:
      - .env
    # O específicas
    environment:
      - NODE_ENV=production
      - PORT=3000

Networking

# Crear red personalizada
docker network create my-network

# Conectar containers
docker run --network my-network --name backend backend:latest
docker run --network my-network --name frontend frontend:latest

# Frontend puede acceder a backend via:
fetch('http://backend:3000/api')

Debugging

# Inspeccionar container
docker inspect my-app

# Ver procesos
docker top my-app

# Estadísticas en tiempo real
docker stats

# Acceder a shell
docker exec -it my-app sh

# Ver cambios en filesystem
docker diff my-app

CI/CD con Docker

# .github/workflows/docker.yml
name: Docker Build and Push

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .
      
      - name: Push to registry
        run: |
          echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
          docker push myapp:${{ github.sha }}

Mejores Prácticas

  1. Una aplicación por container: Sigue el principio de responsabilidad única
  2. Usa .dockerignore: Reduce tamaño y tiempo de build
  3. Multi-stage builds: Para imágenes más pequeñas
  4. No uses latest en producción: Usa tags específicos
  5. Health checks: Para monitoreo y auto-healing
  6. Logs a stdout/stderr: Para mejor observabilidad
  7. Usuario no-root: Por seguridad

Conclusión

Docker es esencial en el desarrollo web moderno. Proporciona consistencia entre entornos, facilita el deployment y mejora la colaboración en equipo. Dominar Docker te hace un desarrollador más versátil y valioso.