Ir al contenido

Conceptos 03 - Buenas prácticas, optimización y troubleshooting

Recomendaciones para construir imágenes eficientes y seguras, técnicas de optimización comprobadas, y soluciones a los problemas más frecuentes. Abra cada sección para ver ejemplos concretos.

  • 10 buenas prácticas
  • 5 técnicas de optimización
  • 8 problemas frecuentes
  • 3 elementos interactivos
  • Autoevaluación final
Parte 1

Buenas prácticas

Concepto 1 de 10

Orden de instrucciones y capas

Prácticas 1 y 2: ordenar las instrucciones por frecuencia de cambio y combinar comandos RUN.

1 · Ordenar instrucciones por frecuencia de cambioLas instrucciones que cambian menos van al inicio. Si una capa cambia, todas las subsecuentes se reconstruyen.

Evitar

FROM python:3.11-slim
COPY . /app
RUN pip install -r requirements.txt

Preferir

FROM python:3.11-slim
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
COPY . /app
2 · Combinar comandos RUNCada RUN crea una capa. Múltiples capas innecesarias aumentan el tamaño y reducen la eficiencia del cache.

Evitar

RUN apt-get update
RUN apt-get install -y curl git
RUN apt-get clean

Preferir

RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

Compare los dos Dockerfile de la práctica 1 ante un cambio en el código y ante un cambio en las dependencias.

Dockerfile
Cambio en el proyecto

    Concepto 2 de 10

    Imagen base y usuario

    Prácticas 3 y 4: usar imágenes específicas y ligeras, y no ejecutar como root.

    3 · Usar imágenes específicas y ligerasLa imagen base impacta directamente en el tamaño final y la superficie de ataque.
    FROM python:latest # Evitar: impredecible
    FROM python:3.11 # Aceptable: ~920 MB
    FROM python:3.11-slim # Recomendado: ~125 MB
    FROM python:3.11-alpine # Óptimo: ~49 MB
    4 · No ejecutar como rootLos contenedores se ejecutan como root por defecto. Siempre cree y cambie a un usuario sin privilegios.
    RUN useradd -m -u 1000 -s /bin/bash appuser
    WORKDIR /app
    COPY --chown=appuser:appuser app.py .
    USER appuser
    CMD ["python", "app.py"]
    # Debian/Ubuntu
    RUN useradd -m -u 1000 appuser
    # Alpine
    RUN adduser -D -u 1000 appuser

    Concepto 3 de 10

    Cache de paquetes y contexto

    Prácticas 5 y 6: limpiar cache y archivos temporales, y usar .dockerignore.

    5 · Limpiar cache y archivos temporalesLos gestores de paquetes crean archivos de cache que ocupan espacio innecesario en la imagen final.
    Node.js
    # Python
    RUN pip install --no-cache-dir -r requirements.txt
    RUN npm ci --only=production && npm cache clean --force
    # APT (Debian/Ubuntu)
    RUN apt-get update && apt-get install -y pkg && \
    apt-get clean && rm -rf /var/lib/apt/lists/*
    # APK (Alpine)
    RUN apk add --no-cache pkg
    6 · Usar .dockerignoreExcluye archivos del contexto de construcción. Reduce tiempo de build, tamaño de imagen y evita incluir archivos sensibles.
    .dockerignore
    .git
    node_modules
    dist
    *.log
    .env
    .env.*
    __pycache__
    *.pyc
    .vscode
    .idea
    Dockerfile*
    docker-compose*.yml

    Concepto 4 de 10

    Procesos y configuración

    Prácticas 7 y 8: un proceso por contenedor y variables de entorno para la configuración.

    7 · Un proceso por contenedorFacilita la escalabilidad, el monitoreo y la gestión del ciclo de vida de cada servicio.

    Evitar

    # nginx Y mysql en un solo contenedor
    RUN apt-get install -y nginx mysql-server
    CMD ["/start-services.sh"]

    Preferir

    Dockerfile.nginx
    # Contenedores separados, orquestados con Compose
    FROM nginx:alpine
    CMD ["nginx", "-g", "daemon off;"]
    8 · Variables de entorno para configuraciónPermiten diferentes valores en desarrollo, staging y producción sin modificar la imagen.
    ENV APP_ENV=production \
    APP_PORT=8080 \
    LOG_LEVEL=info
    Ventana de terminal
    # Sobrescribir al ejecutar
    $ docker run -e LOG_LEVEL=debug mi-app
    $ docker run --env-file .env.production mi-app

    Concepto 5 de 10

    Metadatos y salud

    Prácticas 9 y 10: documentar con LABEL y definir healthchecks.

    9 · Documentar con LABELMetadatos que facilitan la gestión, auditoría y automatización de imágenes.
    LABEL org.opencontainers.image.title="Mi App" \
    org.opencontainers.image.version="1.2.3" \
    org.opencontainers.image.authors="jesse@example.com"
    Ventana de terminal
    # Consultar labels
    $ docker inspect --format='{{json .Config.Labels}}' mi-app | jq
    # Filtrar por label
    $ docker images --filter "label=com.example.team=backend"
    10 · Healthchecks siemprePermiten a Docker verificar si el contenedor funciona correctamente, habilitando reinicio automático.
    # HTTP con curl
    HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1
    # PostgreSQL
    HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
    CMD pg_isready -U postgres || exit 1
    Ventana de terminal
    $ docker ps --format "table {{.Names}}\t{{.Status}}"
    $ docker inspect --format='{{json .State.Health}}' mi-app | jq
    Parte 2

    Optimización

    Concepto 6 de 10

    Reducir el tamaño de la imagen

    Multi-stage builds, Alpine Linux y capas mínimas que se limpian en la misma instrucción.

    Multi-stage buildsMúltiples imágenes base en un Dockerfile, copiando solo artefactos necesarios a la imagen final. Reducción típica del 90-97%.
    # Etapa de construcción
    FROM golang:1.21-alpine AS builder
    WORKDIR /build
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 go build -o main .
    # Etapa de producción
    FROM alpine:3.18
    RUN apk --no-cache add ca-certificates
    COPY --from=builder /build/main /app/
    RUN adduser -D appuser
    USER appuser
    CMD ["./main"]
    Usar Alpine LinuxImagen base minimalista. node:18 pesa 950 MB, node:18-alpine pesa 170 MB.
    FROM node:18 # 950 MB
    FROM node:18-alpine # 170 MB
    Minimizar capas y limpiar en la misma instrucciónCombine comandos RUN y elimine archivos temporales en la misma capa donde se crean.

    Evitar

    RUN apt-get update
    RUN apt-get install -y curl
    RUN rm -rf /var/lib/apt/lists/*

    Preferir

    RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*

    Compare qué contiene la imagen final con una sola etapa y con multi-stage, usando el ejemplo de Go.

    FROM golang:1.21-alpineImagen final

    • Base Alpine7 MB
    • Toolchain de Go240 MB
    • Módulos descargados (go mod download)200 MB
    • Código fuente y archivos intermedios45 MB
    • Binario main8 MB

    FROM alpine:3.18Imagen final

    • alpine:3.187 MB
    • ca-certificates500 KB
    • Binario main (COPY --from=builder)8 MB

    Imagen final: unos 500 MB. Incluye el compilador, los módulos y el código fuente, aunque en ejecución solo se necesita el binario.

    Y observe por qué borrar archivos en un RUN separado no reduce la imagen.

      Concepto 7 de 10

      Analizar y acelerar la construcción

      Herramientas para inspeccionar capas y BuildKit para construir más rápido.

      Herramientas de análisisInspeccione las capas de sus imágenes para identificar oportunidades de optimización.
      Ventana de terminal
      # Ver historial de capas
      $ docker history mi-app:1.0
      # Análisis visual con Dive
      $ docker run --rm -it \
      -v /var/run/docker.sock:/var/run/docker.sock \
      wagoodman/dive:latest mi-app:1.0
      Habilitar BuildKitMotor de construcción con paralelización, cache mounts y salida mejorada.
      # Habilitar temporalmente
      $ DOCKER_BUILDKIT=1 docker build -t mi-app .
      # Habilitar permanentemente
      $ export DOCKER_BUILDKIT=1 # agregar a ~/.bashrc
      Parte 3

      Troubleshooting

      Concepto 8 de 10

      Errores al ejecutar contenedores

      Puerto en uso, permisos en bind mounts y contenedores que no inician o se detienen de inmediato.

      Puerto ya en usoOtro proceso en el host ocupa el puerto que Docker intenta publicar.
      Error starting userland proxy: listen tcp4 0.0.0.0:5432: bind: address already in use
      Ventana de terminal
      # Identificar qué proceso usa el puerto
      $ sudo lsof -i :5432
      $ sudo netstat -tulpn | grep 5432
      # Detener el servicio conflictivo
      $ sudo systemctl stop postgresql
      # O usar un puerto diferente en el host
      $ docker run -d -p 5433:5432 postgres:17
      Permisos en volúmenes (bind mounts)El usuario dentro del contenedor no tiene permisos sobre el directorio montado desde el host.
      Permission denied
      Ventana de terminal
      # Cambiar permisos del directorio del host
      $ sudo chown -R 999:999 /home/user/postgres-data
      # O usar named volume en lugar de bind mount
      $ docker run -d -v postgres-data:/var/lib/postgresql/data postgres:17
      Contenedor no iniciaEl contenedor aparece creado pero no está en ejecución.
      Ventana de terminal
      $ docker ps -a
      $ docker logs nombre_contenedor
      $ docker inspect nombre_contenedor
      $ docker events
      Contenedor se detiene inmediatamenteEl proceso principal (PID 1) termina y el contenedor se cierra.
      Ventana de terminal
      $ docker logs --tail 50 nombre_contenedor
      $ docker run -it nombre_imagen /bin/bash

      Evitar

      CMD echo "Hello World"

      Preferir

      CMD ["python", "-u", "app.py"]
      CMD ["nginx", "-g", "daemon off;"]

      Concepto 9 de 10

      Base de datos y volúmenes

      Fallos de conexión con la base de datos y volúmenes llenos o corruptos.

      No se puede conectar a la base de datosLa aplicación o el cliente no logra establecer conexión con el servicio de base de datos.
      Ventana de terminal
      # 1. Verificar que el contenedor está corriendo
      $ docker ps | grep postgres
      # 2. Verificar logs
      $ docker logs postgres-container
      # 3. Verificar conectividad interna
      $ docker exec postgres-container pg_isready -U postgres
      # 4. Probar conexión desde el host
      $ psql -h localhost -p 5432 -U postgres
      # 5. Verificar puertos expuestos
      $ docker port postgres-container
      Volumen lleno o corruptoLos datos del volumen se han dañado o el espacio se ha agotado.
      Ventana de terminal
      # Backup del volumen
      $ docker run --rm \
      -v postgres-data:/data \
      -v $(pwd):/backup \
      ubuntu tar cvf /backup/backup.tar /data
      # Eliminar y recrear
      $ docker volume rm postgres-data
      $ docker volume create postgres-data
      # Restaurar
      $ docker run --rm \
      -v postgres-data:/data \
      -v $(pwd):/backup \
      ubuntu tar xvf /backup/backup.tar -C /

      Concepto 10 de 10

      Errores al construir imágenes

      Archivos que COPY no encuentra y comandos que no existen en la distribución base.

      Error COPY failed: statDocker no encuentra el archivo referenciado en la instrucción COPY.
      COPY failed: stat /var/lib/docker/tmp/.../file.txt: no such file or directory
      # Verificar que el archivo existe
      $ ls -la file.txt
      # Verificar que NO está en .dockerignore
      $ grep file.txt .dockerignore

      Evitar

      COPY /src/file.txt /app/

      Preferir

      COPY ./src/file.txt /app/
      Error Alpine vs DebianComandos de Debian/Ubuntu no funcionan en Alpine y viceversa.
      /bin/sh: apt-get: not found

      Evitar

      RUN apt-get update && apt-get install -y curl

      Preferir

      RUN apk add --no-cache curl

      Autoevaluación

      Comprueba lo que aprendiste

      Cinco preguntas rápidas para verificar las ideas clave de la lectura. Cada respuesta incluye una explicación.

      1. 1¿Por qué conviene copiar requirements.txt e instalar dependencias antes de copiar el resto del código?
      2. 2Un Dockerfile instala paquetes en un RUN y borra /var/lib/apt/lists en un RUN posterior. ¿Qué pasa con el tamaño?
      3. 3¿Qué logra un multi-stage build?
      4. 4Un contenedor con CMD echo "Hello World" aparece como Exited apenas inicia. ¿Por qué?
      5. 5Según la práctica 8, ¿dónde nunca deben incluirse secretos como contraseñas?

      0/5 respuestas correctas