Guia practica de Docker - Construccion de entornos de desarrollo modernos mediante contenedorizacion

2025.12.02

Conceptos basicos de Docker

Docker es una plataforma que empaqueta aplicaciones como contenedores, garantizando un comportamiento consistente en cualquier entorno.

Virtualizacion tradicional vs Virtualizacion de contenedores

flowchart TB
    subgraph VM["Virtualizacion tradicional (VM)"]
        direction TB
        AppA1["App A"] --> GuestA["Guest OS"]
        AppB1["App B"] --> GuestB["Guest OS"]
        AppC1["App C"] --> GuestC["Guest OS"]
        GuestA & GuestB & GuestC --> Hypervisor["Hypervisor"]
        Hypervisor --> HostOS1["Host OS"]
    end

    subgraph Container["Virtualizacion de contenedores (Docker)"]
        direction TB
        AppA2["App A"] --> LibsA["Libs A"]
        AppB2["App B"] --> LibsB["Libs B"]
        AppC2["App C"] --> LibsC["Libs C"]
        LibsA & LibsB & LibsC --> DockerEngine["Docker Engine"]
        DockerEngine --> HostOS2["Host OS (compartido)"]
    end

Comparacion:

  • VM: Cada aplicacion necesita un sistema operativo guest completo (pesado)
  • Contenedor: Solo bibliotecas, compartiendo el sistema operativo host (ligero)

Fundamentos de Dockerfile

Aplicacion Node.js

# Dockerfile
FROM node:20-alpine

WORKDIR /app

# Instalacion de dependencias (aprovechando cache)
COPY package*.json ./
RUN npm ci --only=production

# Copiar codigo de aplicacion
COPY . .

# Ejecutar con usuario no-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

EXPOSE 3000

CMD ["node", "server.js"]

Build multi-etapa

# Dockerfile.multi-stage
# =========================================
# Stage 1: Instalacion de dependencias
# =========================================
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# =========================================
# Stage 2: Build
# =========================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Variables de entorno (durante build)
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

RUN npm run build

# =========================================
# Stage 3: Imagen de produccion
# =========================================
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# Seguridad: Usuario no-root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copiar solo archivos necesarios
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

USER nextjs

EXPOSE 3000

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

Aplicacion Next.js

# Dockerfile.nextjs
FROM node:20-alpine AS base

# Stage 1: Dependencias
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm i --frozen-lockfile

# Stage 2: Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Deshabilitar telemetria de Next.js
ENV NEXT_TELEMETRY_DISABLED=1

RUN corepack enable pnpm && pnpm run build

# Stage 3: Produccion
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Usar salida standalone
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]

Docker Compose

Entorno de desarrollo

# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

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

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  adminer:
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db

volumes:
  postgres_data:
  redis_data:

Entorno de produccion

# docker-compose.prod.yml
services:
  app:
    image: myapp:${VERSION:-latest}
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - app

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    deploy:
      resources:
        limits:
          memory: 1G

volumes:
  postgres_data:
    driver: local

Optimizacion de Dockerfile

Puntos de optimizacion de Dockerfile

1. Aprovechamiento del cache de capas

MetodoCodigo
Mal ejemploCOPY . .RUN npm install
Buen ejemploCOPY package*.json ./RUN npm ciCOPY . .

2. Minimizacion del tamano de imagen

Imagen baseTamano
node:201.1GB
node:20-slim250MB
node:20-alpine140MB
Build multi-etapa final50-100MB

3. Seguridad

  • Ejecutar con usuario no-root
  • No incluir paquetes innecesarios
  • No incluir secretos en la imagen
  • Uso de .dockerignore

.dockerignore

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env*
.env.local
.env.*.local
Dockerfile*
docker-compose*
.dockerignore
README.md
.next
.cache
coverage
.nyc_output
*.log
.DS_Store

Health Check

# En la aplicacion
FROM node:20-alpine

WORKDIR /app
COPY . .
RUN npm ci --only=production

# Endpoint de health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "server.js"]
// server.ts - Endpoint de health check
import express from 'express';

const app = express();

app.get('/health', async (req, res) => {
  try {
    // Verificar conexion a base de datos
    await db.query('SELECT 1');

    // Verificar Redis
    await redis.ping();

    res.status(200).json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      checks: {
        database: 'ok',
        redis: 'ok',
      },
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message,
    });
  }
});

app.get('/ready', (req, res) => {
  // Verificar si la aplicacion esta lista
  if (appIsReady) {
    res.status(200).send('Ready');
  } else {
    res.status(503).send('Not Ready');
  }
});

Gestion de logs

// logger.ts - Logging para Docker
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  // Formato JSON recomendado para entornos Docker
  transport:
    process.env.NODE_ENV === 'development'
      ? {
          target: 'pino-pretty',
          options: { colorize: true },
        }
      : undefined,
});

// Logs estructurados
logger.info({ userId: 123, action: 'login' }, 'User logged in');
logger.error({ err: error, requestId: 'abc-123' }, 'Request failed');

export default logger;
# docker-compose.yml - Configuracion de logs
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Integracion CI/CD

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

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Comandos de uso frecuente

# Build de imagen
docker build -t myapp:latest .

# Ejecutar contenedor
docker run -d -p 3000:3000 --name myapp myapp:latest

# Ver logs
docker logs -f myapp

# Entrar al contenedor
docker exec -it myapp sh

# Verificar tamano de imagen
docker images myapp

# Eliminar recursos no utilizados
docker system prune -a

# Operaciones con Docker Compose
docker compose up -d
docker compose down
docker compose logs -f app
docker compose exec app sh

Enlaces de referencia

← Volver a la lista