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.
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-slimCOPY . /appRUN pip install -r requirements.txtPreferir
FROM python:3.11-slimCOPY requirements.txt /app/RUN pip install -r /app/requirements.txtCOPY . /app2 · 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 updateRUN apt-get install -y curl gitRUN apt-get cleanPreferir
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.
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: impredecibleFROM python:3.11 # Aceptable: ~920 MBFROM python:3.11-slim # Recomendado: ~125 MBFROM python:3.11-alpine # Óptimo: ~49 MB4 · 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 appuserWORKDIR /appCOPY --chown=appuser:appuser app.py .USER appuserCMD ["python", "app.py"]# Debian/UbuntuRUN useradd -m -u 1000 appuser# AlpineRUN adduser -D -u 1000 appuserConcepto 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.
# PythonRUN 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 pkg6 · Usar .dockerignoreExcluye archivos del contexto de construcción. Reduce tiempo de build, tamaño de imagen y evita incluir archivos sensibles.
.gitnode_modulesdist*.log.env.env.*__pycache__*.pyc.vscode.ideaDockerfile*docker-compose*.ymlConcepto 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 contenedorRUN apt-get install -y nginx mysql-serverCMD ["/start-services.sh"]Preferir
# Contenedores separados, orquestados con ComposeFROM nginx:alpineCMD ["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# Sobrescribir al ejecutar$ docker run -e LOG_LEVEL=debug mi-app$ docker run --env-file .env.production mi-appConcepto 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"# 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 curlHEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1
# PostgreSQLHEALTHCHECK --interval=10s --timeout=5s --retries=5 \ CMD pg_isready -U postgres || exit 1$ docker ps --format "table {{.Names}}\t{{.Status}}"$ docker inspect --format='{{json .State.Health}}' mi-app | jqOptimizació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ónFROM golang:1.21-alpine AS builderWORKDIR /buildCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 go build -o main .
# Etapa de producciónFROM alpine:3.18RUN apk --no-cache add ca-certificatesCOPY --from=builder /build/main /app/RUN adduser -D appuserUSER appuserCMD ["./main"]Usar Alpine LinuxImagen base minimalista. node:18 pesa 950 MB, node:18-alpine pesa 170 MB.
FROM node:18 # 950 MBFROM node:18-alpine # 170 MBMinimizar 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 updateRUN apt-get install -y curlRUN 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.
# 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.0Habilitar 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 ~/.bashrcTroubleshooting
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# 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:17Permisos en volúmenes (bind mounts)El usuario dentro del contenedor no tiene permisos sobre el directorio montado desde el host.
Permission denied# 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:17Contenedor no iniciaEl contenedor aparece creado pero no está en ejecución.
$ docker ps -a$ docker logs nombre_contenedor$ docker inspect nombre_contenedor$ docker eventsContenedor se detiene inmediatamenteEl proceso principal (PID 1) termina y el contenedor se cierra.
$ docker logs --tail 50 nombre_contenedor$ docker run -it nombre_imagen /bin/bashEvitar
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.
# 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-containerVolumen lleno o corruptoLos datos del volumen se han dañado o el espacio se ha agotado.
# 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 .dockerignoreEvitar
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 foundEvitar
RUN apt-get update && apt-get install -y curlPreferir
RUN apk add --no-cache curlAutoevaluación
Comprueba lo que aprendiste
Cinco preguntas rápidas para verificar las ideas clave de la lectura. Cada respuesta incluye una explicación.
0/5 respuestas correctas