Creación de Imágenes en Docker
Resumen de una línea
Cómo construir imágenes Docker personalizadas con Dockerfile, docker build, caching, y mejores prácticas para aplicaciones en producción.
Información
- Fuente: Curso Docker 2024 - Módulo 7
- URL Plataforma: https://plataforma.josedomingo.org/pledin/cursos/docker2024/
- URL GitHub: https://github.com/josedom24/curso_docker_ow
- Líneas de contenido: 1138
- Nota: Módulo más grande del curso
El Problema: Por Qué Dockerfile
Forma Manual (❌ No reproducible)
# Crear contenedor
docker run -it ubuntu bash
# Instalar software dentro
root@abc123:/# apt-get update
root@abc123:/# apt-get install -y apache2
root@abc123:/# echo "<h1>Hello</h1>" > /var/www/html/index.html
# Guardar como imagen
docker commit abc123 mi-apache:v1Problemas:
- ❌ No reproducible
- ❌ Difícil de documentar
- ❌ Imposible automatizar
- ❌ Variabilidad entre compilaciones
Forma Declarativa (✅ Reproducible)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y apache2
COPY index.html /var/www/html/
EXPOSE 80
CMD ["apache2ctl", "-D", "FOREGROUND"]Ventajas:
- ✅ Reproducible
- ✅ Autodocumentado
- ✅ Versionable (git)
- ✅ Automatizable
Dockerfile: Sintaxis Completa
Estructura Básica
# syntax=docker/dockerfile:1 # Parser directive
FROM ubuntu:22.04 # Imagen base (obligatorio)
LABEL maintainer="user@example.com" # Metadatos
ENV APP_HOME=/app # Variables de entorno
ENV DEBUG=false
RUN apt-get update && \ # Comandos en build
apt-get install -y python3
WORKDIR /app # Directorio de trabajo
COPY app.py . # Copiar archivos del host
ADD https://example.com/file . # Descargar URL
EXPOSE 8000 # Documentar puerto (no expone realmente)
USER appuser # Usuario que ejecuta
ENTRYPOINT ["python3"] # Comando principal (fijo)
CMD ["app.py"] # Argumentos por defecto (sobrescribible)Instrucciones Principales
FROM (Obligatorio)
FROM ubuntu:22.04
FROM python:3.11-slim
FROM node:18-alpine
FROM scratch # Imagen vacía (para binarios)RUN (Ejecutar Comandos en Build)
# Forma shell (con /bin/sh -c)
RUN apt-get update
# Forma exec (recomendado)
RUN ["apt-get", "install", "-y", "curl"]
# Múltiples líneas (mejor legibilidad)
RUN apt-get update && \
apt-get install -y curl && \
apt-get cleanCOPY vs ADD
COPY file.txt /app/ # Copiar archivos locales
COPY src/ /app/src/ # Copiar directorios
ADD https://example.com/file . # Descargar URL
ADD archive.tar.gz /app/ # Descomprimir automáticamente
# ⚠️ COPY es más explícito, preferible en la mayoría de casosWORKDIR (Directorio de Trabajo)
WORKDIR /app
RUN mkdir -p /app/config
COPY . . # Copia en /app
# Equivalente a: cd /app en un contenedorENV (Variables de Entorno)
ENV NODE_ENV=production
ENV DATABASE_URL="postgres://localhost/db"
# Accesibles en tiempo de ejecución
# docker run -e OVERRIDE=valueEXPOSE (Documentar Puertos)
EXPOSE 80
EXPOSE 443
EXPOSE 3000/tcp
EXPOSE 5000/udp
# Solo documentación, no expone realmente
# docker run -p 8080:80 para exponerENTRYPOINT vs CMD
# ENTRYPOINT: Comando principal (fijo, no se sobrescribe)
ENTRYPOINT ["python"]
# CMD: Argumentos por defecto (se sobrescribe)
CMD ["app.py"]
# Resultado: python app.pyDiferencias:
# Con ENTRYPOINT + CMD
docker run mi-image # python app.py
docker run mi-image server.py # python server.py
# Con solo CMD
docker run mi-image # arg.py
docker run mi-image echo "hello" # echo helloUSER (Usuario que Ejecuta)
RUN useradd -m appuser
USER appuser
# Contenedor ejecuta como appuser (no root)docker build: Construir Imágenes
Sintaxis Básica
docker build -t nombre:tag [contexto]Ejemplos
# Construir en directorio actual
docker build -t mi-app:1.0 .
# Con múltiples tags
docker build -t usuario/repo:1.0 -t usuario/repo:latest .
# Especificar Dockerfile
docker build -t app:latest -f Dockerfile.prod .
# Con argumentos de build
docker build --build-arg ENV=production -t app .
# Sin caché
docker build --no-cache -t app .
# Con labels
docker build --label version=1.0 --label env=prod -t app .Contexto de Build
build/
├── Dockerfile # Instrucciones
├── app.py
├── requirements.txt
├── .dockerignore # Archivos a excluir
└── config/
└── settings.yaml
docker build -t app . # `.` = contexto (directorio actual)Archivos excluidos con .dockerignore:
.git
.gitignore
node_modules
__pycache__
*.pyc
.env
Ver Capas
docker history mi-app:1.0
# Output:
IMAGE CREATED CREATED BY SIZE
abc123 2 minutes ago /bin/sh -c echo "done" 0B
def456 2 minutes ago COPY app.py . 1.2MB
ghi789 2 minutes ago RUN apt-get install -y curl 10MB
jkl012 2 minutes ago /bin/sh -c #(nop) WORKDIR /app 0BCaching en Docker Build
Cómo Funciona
FROM ubuntu:22.04 # Capa 1 (caché hit)
RUN apt-get update && \
apt-get install -y curl # Capa 2 (caché hit)
COPY app.py . # Capa 3 (caché hit)
RUN python app.py # Capa 4 (caché hit, sin cambios)Primera compilación: Todas las capas se generan
# Modifica app.py y recompila
RUN python app.py # Capa 4 (CACHÉ INVÁLIDA)Segunda compilación: Solo recompila desde la primera capa modificada
Optimizar Caching
❌ Ineficiente:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl wget git
COPY . /app # Copia TODO
RUN python app.pyProblema: Cambiar cualquier archivo → Recompila desde la copia
✅ Eficiente:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl wget git
COPY requirements.txt . # Solo dependencias
RUN pip install -r requirements.txt # Caché si no cambia
COPY . /app # Copia código (cambia frecuente)
RUN python app.pyBeneficio: Cambiar código no invalida caché de dependencias
Estrategias
- FROM primero: Base no cambia
- RUN apt-get primero: Dependencias del SO
- COPY dependencias: requirements.txt, package.json
- RUN install: Instalar dependencias
- COPY código: Código fuente (cambia frecuente)
- Comandos: CMD/ENTRYPOINT al final
Ciclo de Vida de Aplicaciones Docker
1. Desarrollo (escribir código + Dockerfile)
↓
2. Build (docker build → imagen)
↓
3. Test (docker run en dev/test)
↓
4. Distribución (docker push a Docker Hub)
↓
5. Producción (docker run en prod)
↓
6. Actualización (volver al paso 1 si cambios)
Casos de Uso Prácticos
Caso 1: Aplicación Web Estática
FROM nginx:alpine
COPY html/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]docker build -t web-static:1.0 .
docker run -d -p 8080:80 web-static:1.0Caso 2: Aplicación Python
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
ENV FLASK_APP=app.py
CMD ["flask", "run", "--host=0.0.0.0"]docker build -t flask-app:1.0 .
docker run -d -p 5000:5000 flask-app:1.0Caso 3: Aplicación Node.js
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]Caso 4: Multi-stage (Pequeño)
# Stage 1: Build
FROM node:18 as builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Stage 2: Runtime
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package.json .
RUN npm ci --only=production
EXPOSE 3000
CMD ["npm", "start"]Beneficio: Imagen final sin herramientas de build (más pequeña)
Mejores Prácticas
1. Usar Imágenes Base Pequeñas
# ❌ 300MB
FROM ubuntu:22.04
# ✅ 80MB
FROM debian:bookworm-slim
# ✅✅ 5MB
FROM alpine:3.182. Minimizar Capas
# ❌ 3 capas
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
# ✅ 1 capa
RUN apt-get update && \
apt-get install -y curl git && \
apt-get clean3. Limpiar Después de RUN
# ❌ Deja residuos
RUN apt-get install -y build-essential
# ✅ Limpia caché
RUN apt-get update && \
apt-get install -y build-essential && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*4. No Ejecutar como Root
RUN useradd -m appuser
USER appuser5. Usar .dockerignore
.git
node_modules
__pycache__
.env
.pytest_cache
6. Healthchecks
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/ exit 1Conceptos Clave
- Dockerfile: Receta declarativa (no procedural)
- Capas: Cada instrucción crea una capa
- Caché: Reutiliza capas si no cambian
- Contexto: Directorio que se envía a Docker daemon
- ENTRYPOINT: Comando fijo (no sobrescribible)
- CMD: Argumentos (sobrescribibles)
- Multi-stage: Separar build de runtime
- Reproducibilidad: Mismo Dockerfile = misma imagen
Flujo Típico
# 1. Escribir Dockerfile
# 2. Construir
docker build -t myapp:1.0 .
# 3. Verificar
docker images myapp
# 4. Ejecutar localmente
docker run -d -p 8080:80 myapp:1.0
# 5. Distribuir
docker push myregistry.com/myapp:1.0
# 6. Usar en producción
docker run -d myregistry.com/myapp:1.0Relaciones
Conecta con
- Creación de Imágenes en Docker — Concepto y sintaxis
- Docker — Plataforma base
- Docker Compose — Define imágenes a construir
Parte de
- Construcción y automatización de imágenes
- Infrastructure as Code declarativo