Guía de Docker: De 0 a 100
¿Qué es Docker?
Docker es una plataforma de contenerización que permite empaquetar aplicaciones con todas sus dependencias en contenedores ligeros y portables. Fue lanzado por Docker Inc. (entonces dotCloud) en 2013, obra de Solomon Hykes, y revolucionó la forma en que desplegamos software.
¿Qué lo hace diferente de una máquina virtual?
| Aspecto | Contenedor Docker | Máquina Virtual |
|---|---|---|
| Kernel | Comparte el kernel del host | Kernel propio (huésped) |
| Arranque | Milisegundos | Minutos |
| Aislamiento | Namespaces + cgroups (nivel proceso) | Hypervisor (nivel hardware) |
| Tamaño | MB (solo app + librerías) | GB (SO completo) |
| Overhead | ~0% CPU/RAM | 5–15% CPU/RAM |
| Migración | Trivial (imagen portable) | Compleja (snapshots grandes) |
Los contenedores usan namespaces (pid, net, mnt, uts, ipc, user) para aislar procesos y cgroups para limitar recursos (CPU, RAM, disco). Esto es posible porque todos los contenedores sobre una misma máquina comparten el kernel del host, a diferencia de las VMs que virtualizan hardware completo.
¿Dónde se usa Docker?
- Desarrollo: entornos idénticos en local para todo el equipo
- CI/CD: builds y tests reproducibles en pipelines (GitHub Actions, GitLab CI, Jenkins)
- Producción: despliegues escalables con Swarm o Kubernetes, microservicios, serverless
- Edge/IoT: contenedores ligeros en dispositivos con recursos limitados
- Data Science: notebooks, modelos ML, pipelines reproducibles
¿Quién lo usa? Google (todo corre en contenedores), Netflix (orquestación masiva), Spotify (microservicios en K8s), Uber (cientos de servicios), Airbnb (infraestructura con Docker + K8s).
🔗 Docker — What is a Container?, Docker Overview docs.docker.com, Containers vs VMs (IBM)
Prerrequisitos
Antes de empezar, necesitas:
- Conocimientos básicos de terminal: navegar directorios, ejecutar comandos, usar un editor de texto
- El lenguaje/herramienta instalado en tu sistema (sigue la sección de Instalación si aún no lo tienes)
Si no cumples algún requisito, no te preocupes: cada sección te guiará paso a paso.
¿Cómo empezar? Instalación en todos los entornos
Windows
- Activa WSL2:
wsl --installen PowerShell como administrador - Descarga Docker Desktop
- Durante la instalación, marca “Use WSL 2 instead of Hyper-V”
- Verifica:
docker --version
macOS
- Docker Desktop: descarga de docker.com
- Colima (open-source):
brew install colima && colima start - OrbStack (rápido, Apple Silicon):
brew install orbstack - Verifica:
docker --version && docker compose version
Linux (Debian/Ubuntu)
sudo apt remove docker docker-engine docker.io containerd runc
sudo apt update && sudo apt install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
docker run hello-world
Fedora/RHEL:
sudo dnf -y install dnf-plugin
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker && sudo usermod -aG docker $USER
Docker Engine vs Docker Desktop
| Aspecto | Docker Engine | Docker Desktop |
|---|---|---|
| Componentes | Engine + CLI + Containerd + BuildKit | Engine + CLI + UI + Compose + BuildKit + Extensions |
| Plataforma | Linux | Windows, macOS (Linux vía VM) |
| Licencia | Apache 2.0 | Free para pequeños, pago para grandes |
| Consumo RAM | ~50–100 MB | ~1–2 GB |
| Instalación | Repositorio oficial | Installer con GUI |
| Ideal para | Servidores, CI/CD, producción | Desarrollo local en Win/Mac |
Rootless mode (Linux)
sudo apt install -y uidmap dbus-user-system
dockerd-rootless-setuptool.sh install
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$UID/docker.sock
systemctl --user enable docker --now
Alias útiles
alias dkps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dkls='docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"'
alias dkrm='docker rm -f $(docker ps -aq) 2>/dev/null; echo "Eliminados"'
alias dklog='docker logs -f --tail 100'
alias dkexec='docker exec -it'
alias dkprune='docker system prune -af --volumes'
alias dcup='docker compose up -d --build'
alias dcdown='docker compose down'
alias dclogs='docker compose logs -f'
alias dkbuild='docker build -t'
alias dkrun='docker run -it --rm'
🔗 Docker Engine installation docs, Docker Desktop docs, Rootless mode docs
Escala de aprendizaje 0–100
Nivel 0–15: Fundamentos absolutos
Qué aprender:
- Qué es un contenedor: proceso aislado con su propio sistema de archivos
docker run,docker ps,docker stop,docker rmdocker pull,docker images,docker rmi- Diferencia entre imagen (plantilla) y contenedor (instancia)
docker run hello-worldy entender qué pasadocker run -it ubuntu bash— explorar un contenedor interactivo- Conceptos clave: los contenedores comparten el kernel del host. NO son máquinas virtuales.
Proyecto: Servidor web estático — contenedor nginx que sirva un index.html. docker run -d -p 8080:80 -v $(pwd):/usr/share/nginx/html nginx:alpine.
Nivel 15–30: Dockerfile y builds
Qué aprender:
- Dockerfile básico:
FROM,WORKDIR,COPY,RUN,CMD - Orden de instrucciones y layer caching
.dockerignoreEXPOSE,ENV,ARG- Diferencia entre
RUN,CMDyENTRYPOINT
Proyecto: App Node.js dockerizada — Dockerfile con layer caching. Copia package.json primero, instala, luego copia el resto.
Nivel 30–45: Docker Compose y multi-servicio
Qué aprender:
compose.yml: services, build, image, ports, volumes, environmentdocker compose up,down,logs,exec- Dependencias con
depends_ony condiciones - Variables de entorno y archivos
.env - Profiles: entornos dev/staging/prod
Proyecto: App web + BD + caché — compose con web (Node.js/Python), PostgreSQL y Redis.
Nivel 45–60: Multi-stage, volúmenes y redes
Qué aprender:
- Multi-stage builds
- Volúmenes: bind mount, volume, tmpfs
- Redes avanzadas: bridge, host, overlay
- HEALTHCHECK, logging drivers
- Sidecar y Ambassador patterns
Proyecto: App Go con multi-stage — Dockerfile multi-stage (~10 MB final). Bind mount para desarrollo, volume para logs. Red separada para BD.
Nivel 60–75: Seguridad, registros y CI/CD
Qué aprender:
- No ejecutar como root:
USER appuser --cap-drop=ALL --cap-add=NET_BIND_SERVICE- Secrets:
--secreten build, monturas en runtime docker scout/ Trivy para vulnerabilidades- GitHub Actions: build + push con caché
- Tags semánticos:
v1.2.3, SHA
Proyecto: CI/CD pipeline — GitHub Action que construye, escanea, etiqueta y publica en GHCR.
Nivel 75–90: Swarm y orquestación
Qué aprender:
docker swarm init,docker service create,docker stack deploy- Redes overlay multi-host
- Rolling updates
- Secrets y configs en Swarm
Proyecto: Cluster Swarm local — 3 nodos, stack con app (3 réplicas), BD, Redis. Rolling update y escalado.
Nivel 90–100: Kubernetes, service mesh y producción
Qué aprender:
- Minikube, Deployments, Services, ConfigMaps, Secrets, PVCs
- Ingress controller, HPA
- Service Mesh: Istio/Linkerd para mTLS
- Observabilidad: Prometheus + Grafana
- GitOps: ArgoCD
Proyecto: Microservicios en K8s — tres servicios en Minikube. Deployments con HPA, Ingress, Prometheus, Istio.
🔗 Docker Swarm docs, Kubernetes docs
Primeros pasos y configuración del entorno
El primer contenedor
docker run hello-world
Esto descarga la imagen hello-world de Docker Hub, crea un contenedor, ejecuta el binario hello que imprime el mensaje de bienvenida, y el contenedor se detiene.
🧪 Verificación
docker run hello-world && echo "Docker funciona correctamente" || echo "Error en Docker"
Dockerfile básico
Un Dockerfile es un script de instrucciones para construir una imagen. Cada instrucción crea una capa (layer) que se cachea.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
docker build -t mi-app .
docker run -p 3000:3000 mi-app
.dockerignore
node_modules
.git
.env
*.md
.gitignore
Dockerfile
.dockerignore
dist
docker compose
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
environment:
- NODE_ENV=development
depends_on:
- db
db:
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: devpassword
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:
docker compose up -d
docker compose logs -f
docker compose exec web sh
docker compose down -v
🧪 Mini-script: verificar instalación completa
#!/bin/bash
echo "=== Verificación Docker ==="
docker --version && echo "✅ CLI OK" || echo "❌ CLI falló"
docker compose version && echo "✅ Compose OK" || echo "❌ Compose falló"
docker run --rm hello-world > /dev/null 2>&1 && echo "✅ Contenedor OK" || echo "❌ Contenedor falló"
echo "=== Fin ==="
🔗 Dockerfile reference, Compose specification
Sistema de archivos: volúmenes, monturas y almacenamiento
¿Qué es?
Docker tiene su propio sistema de archivos en capas (overlay2) que permite compartir datos entre el host y los contenedores, entre contenedores, y gestionar el almacenamiento eficientemente.
Tipos de monturas
# Named volume (Docker gestiona la ruta)
docker volume create mis-datos
docker run -v mis-datos:/data ubuntu
# Bind mount (ruta explícita del host)
docker run -v /ruta/host:/ruta/contenedor ubuntu
docker run --mount type=bind,source=/ruta/host,target=/ruta/contenedor ubuntu
# tmpfs (solo en RAM)
docker run --tmpfs /app/temp:noexec,nosuid,size=64M ubuntu
| Tipo | Persistencia | Rendimiento | Caso de uso |
|---|---|---|---|
| Named volume | Sí (gestionado por Docker) | Excelente | BD, datos de producción |
| Bind mount | Sí (en el host) | Excelente | Desarrollo, hot reload |
| tmpfs | No (se pierde al parar) | Muy rápido (RAM) | Caché, sesiones, tmp |
docker cp
docker cp ./archivo.conf contenedor:/etc/app/config.conf
docker cp contenedor:/var/log/app.log ./app.log
docker cp ./data/ contenedor:/app/data/
COPY vs ADD
COPY archivo.txt /destino/
COPY --chown=appuser:appgroup . /app
ADD archivo.tar.gz /destino/ # Extrae automáticamente
Regla: usa siempre COPY. ADD con URLs es mala práctica. ADD con tarballs es aceptable pero prefiere COPY + RUN tar -xf.
Contexto de build y .dockerignore
El contexto es todo lo que envías al daemon. El .dockerignore excluye archivos:
node_modules
.git
.env
*.md
dist
.cache
*.log
docker build -t mi-app .
docker build -f ./app/Dockerfile ./app
docker export/import (contenedores a tarballs)
docker export mi-contenedor > mi-contenedor.tar
cat mi-contenedor.tar | docker import - mi-imagen:importada
docker save/load (imágenes a tarballs)
docker save mi-app:latest | gzip > mi-app.tar.gz
docker load < mi-app.tar.gz
docker save mi-app:latest | ssh servidor docker load
Overlay2: el sistema de archivos en capas
Docker usa el driver overlay2 (Linux nativo) para unir capas en un sistema de archivos único:
Capa superior (contenedor, r/w)
---
Capa N (última instrucción Dockerfile, r/o)
---
...
---
Capa 0 (FROM python:3.12-slim, r/o)
- Copy-on-Write (CoW): si un contenedor modifica un archivo de una capa inferior, overlay2 lo copia a la capa superior antes de modificarlo
- Compartición: si dos contenedores usan la misma imagen base, comparten las mismas capas en disco
docker history mi-app:latest
docker inspect mi-app:latest --format '{{.RootFS.Layers}}'
docker system df
Build cache
Docker cachea cada capa por el hash de su instrucción y archivos. La caché se invalida cuando:
- Cambia el contenido de un archivo
COPYoADD - Cambia el comando de un
RUN - Cambia la imagen base (
FROM) - Una capa anterior se invalida (cascada)
docker build --no-cache -t mi-app .
docker build --cache-from ghcr.io/mi-org/app:latest -t mi-app .
docker build --progress=plain -t mi-app . 2>&1 | grep CACHED
🧪 Mini-script: explorar capas de una imagen
#!/bin/bash
IMAGE=${1:-nginx:alpine}
echo "=== Capas de $IMAGE ==="
docker pull -q "$IMAGE" > /dev/null
docker history "$IMAGE" --format "table {{.CreatedBy}}\t{{.Size}}" | head -20
echo "=== Tamaño total ==="
docker images "$IMAGE" --format "{{.Size}}"
✅ Buenas prácticas
- Ordena instrucciones de menos a más cambiantes para maximizar caché
- Combina
RUNcomandos con&¶ reducir capas - Usa
.dockerignorepara reducir el contexto de build - Prefiere volúmenes named sobre bind mounts en producción
- No almacenes datos de BD en bind mounts — usa volúmenes
🔗 Para saber más
Algoritmos y optimización de builds
¿Qué es?
Docker no es un lenguaje de programación y no tiene algoritmos en el sentido tradicional. Sin embargo, sus mecanismos internos tienen comportamientos analizables en términos de complejidad.
Resolución de capas (overlay2) — O(n)
Cada capa es una diferencia respecto a la anterior. Cuando el contenedor lee un archivo, overlay2 debe recorrer las capas de arriba abajo hasta encontrarlo:
Lectura de /etc/passwd -> busca en capa r/w -> no está -> capa N -> ... -> capa 0 -> lo encuentra
En el peor caso, O(n) donde n = número de capas. Con caché de metadatos (dcache), O(1). Se recomienda máximo ~20-50 capas.
Cache invalidation — O(n) en capas modificadas
FROM python:3.12-slim # Capa 0 — cacheada
WORKDIR /app # Capa 1 — cacheada
COPY requirements.txt . # Capa 2 — ¡CAMBIA!
RUN pip install -r ... # Capa 3 — se reconstruye
COPY . . # Capa 4 — se reconstruye
Si la capa 2 se invalida, las capas 3 y 4 también. Ordena de menos a más cambiante.
Dependencias entre servicios — O(V + E)
depends_on en compose crea un grafo de dependencias resuelto con orden topológico:
services:
web:
depends_on: [api, redis]
api:
depends_on:
db:
condition: service_healthy
worker:
depends_on: [redis, db]
redis:
db:
No se permiten dependencias cíclicas — error de validación.
Scheduling en Swarm/Kubernetes
- Swarm: spreading — distribuye réplicas entre nodos minimizando desbalance
- K8s: predicados (filtros) + prioridades (puntuación) — O(P x N)
Round-robin DNS
docker compose up -d --scale api=5
Docker DNS resuelve el nombre del servicio con round-robin: O(1) por resolución.
Tabla resumen
| Concepto | Complejidad | Factor determinante |
|---|---|---|
| Resolución de capas (overlay2) | O(n) peor caso, O(1) con caché | n = número de capas |
| Cache invalidation | O(n) capas desde el cambio | Orden del Dockerfile |
| Dependencias (compose) | O(V + E) topológico | V = servicios, E = depends |
| Scheduling Swarm | Spreading (balance de recursos) | Nodos y réplicas |
| Scheduling K8s | O(P x N) predicados + prioridades | Pods y nodos |
| Round-robin DNS | O(1) por resolución | Número de réplicas |
| Build context transfer | O(tamaño contexto) | Archivos enviados al daemon |
🧪 Mini-script: medir tiempo de build con y sin caché
#!/bin/bash
echo "=== Build SIN caché ==="
time docker build --no-cache -t test-cache . 2>&1 | tail -3
echo "=== Build CON caché ==="
time docker build -t test-cache . 2>&1 | tail -3
✅ Buenas prácticas
- Máximo 20-50 capas por imagen
- Ordena Dockerfile de menos a más cambiante
- Usa
--cache-fromen CI para reusar imágenes previas - El contexto de build debe ser lo más pequeño posible
🔗 Para saber más
Conceptos clave explicados a fondo
Imágenes vs Contenedores
¿Qué es? Una imagen es una plantilla inmutable de solo lectura con el sistema de archivos y configuración de la app. Un contenedor es una instancia ejecutable de una imagen: una capa r/w sobre las capas de solo lectura.
docker build -t mi-app:v1 .
docker run -d --name c1 mi-app:v1
docker run -d --name c2 mi-app:v1
Capas (Layer Caching)
¿Qué es? Cada instrucción en el Dockerfile crea una capa. Docker cachea cada capa por su hash. Si una capa no ha cambiado, Docker reusa la cacheada.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]
💡 Para maximizar el caché: ordena instrucciones de menos a más cambiantes, separa dependencias del código, combina RUN comandos.
ENTRYPOINT vs CMD
CMD ["python", "app.py"]
ENTRYPOINT ["python"]
| Dockerfile | docker run imagen | docker run imagen --help |
|---|---|---|
CMD ["python", "app.py"] | python app.py | python app.py --help |
ENTRYPOINT ["python"] + CMD ["app.py"] | python app.py | python --help |
Patrón recomendado: ENTRYPOINT para el ejecutable fijo, CMD para argumentos por defecto.
Dockerfile multi-stage
Permite usar múltiples imágenes base en un mismo Dockerfile:
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/app .
FROM scratch
COPY --from=builder /bin/app /bin/app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/bin/app"]
docker build -t go-app .
# ~10 MB en lugar de ~800 MB
ARG vs ENV
ARG VERSION=latest
ENV NODE_ENV=production
ARG: solo durante el build, se pasa con--build-argENV: persiste en runtime, config de la app- Seguridad: nunca pongas secrets en
ENV. Usa--secretde BuildKit.
HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
Estados: starting → healthy → unhealthy. Swarm y K8s usan esto para reiniciar contenedores.
Volúmenes: bind mount, volume, tmpfs
docker volume create mis-datos
docker run -v mis-datos:/data ubuntu
docker run --mount type=bind,src=/host/ruta,target=/container/ruta ubuntu
docker run --tmpfs /app/temp ubuntu
| Tipo | Persistencia | Rendimiento | Caso de uso |
|---|---|---|---|
| Volume | Sí | Excelente | BD, datos de producción |
| Bind mount | Sí | Excelente | Desarrollo, hot reload |
| tmpfs | No | Muy rápido (RAM) | Caché, sesiones |
Redes: bridge, host, overlay, macvlan
docker network create --driver bridge mi-red
docker run --network mi-red --name web nginx
docker network create --driver overlay --attachable mi-red-overlay
docker network create --driver macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 macvlan-red
Registro: Docker Hub, GHCR, Harbor
docker login
docker tag mi-app usuario/mi-app:1.0.0
docker push usuario/mi-app:1.0.0
docker login ghcr.io -u USERNAME --password-stdin < token.txt
docker tag mi-app ghcr.io/mi-org/mi-app:1.0.0
docker push ghcr.io/mi-org/mi-app:1.0.0
Mejor práctica: etiqueta siempre con SHA + semántico en CI:
docker tag mi-app ghcr.io/mi-org/mi-app:${GITHUB_SHA::8}
docker tag mi-app ghcr.io/mi-org/mi-app:${GITHUB_REF_NAME#v}
docker push --all-tags ghcr.io/mi-org/mi-app
Seguridad
No ejecutar como root:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Secrets: nunca en imágenes:
docker build --secret id=mysecret,src=./secret.txt -t mi-app .
Capabilities:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE mi-app
Análisis de vulnerabilidades:
docker scout quickview mi-app
trivy image mi-app
Patrones de Docker (reinterpretación de patrones de diseño)
Sidecar Container
Un contenedor auxiliar que acompaña al principal. Ejemplo: proxy sidecar.
services:
app:
build: .
networks:
- backend
sidecar-proxy:
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
networks:
- backend
- frontend
networks:
frontend:
backend:
internal: true
Ambassador Container
Proxy que abstrae la conexión a un servicio externo. La app se conecta a localhost del ambassador.
services:
app:
build: .
environment:
- DATABASE_URL=postgres://user:pass@ambassador:5432/db
depends_on:
- ambassador
ambassador:
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
Init Containers
Contenedores que se ejecutan antes que el principal para preparar el entorno.
services:
init-db:
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/miapp
depends_on:
db:
condition: service_healthy
profiles:
- setup
Blue/Green Deploy
Dos versiones coexisten y se cambia el tráfico atómicamente.
docker compose --profile blue up -d
docker compose --profile green up -d
docker compose exec nginx nginx -s reload
docker compose --profile blue down
🔗 Docker security best practices, Docker patterns, Kubernetes Patterns (Red Hat)
🧪 Mini-script: comprobar seguridad de una imagen
#!/bin/bash
IMAGE=${1:-nginx:alpine}
echo "=== Análisis de $IMAGE ==="
docker scout quickview "$IMAGE" 2>/dev/null || echo "Docker Scout no disponible"
echo "=== Verificar usuario no root ==="
docker run --rm "$IMAGE" whoami 2>/dev/null || echo "(no hay whoami)"
echo "=== Capas ==="
docker history "$IMAGE" --format "{{.Size}}\t{{.CreatedBy}}" | head -10
Testing y calidad
Frameworks y herramientas
| Framework | Propósito | Async | CLI |
|---|---|---|---|
| Docker Compose tests | Tests de integración multi-servicio | Sí | docker compose up --wait |
| Container health checks | Verificar estado del contenedor | No | docker inspect --format='{{.State.Health.Status}}' |
| Testcontainers | Tests con contenedores desde código | Sí | pip install testcontainers |
| bats | Tests de scripts bash/Dockerfile | No | bats test/*.bats |
| container-structure-test | Tests de estructura de imágenes | No | container-structure-test test --image |
Cómo testear cada concepto
Dockerfile y capas → verificar que el build es correcto:
#!/bin/bash
# test_build.sh
docker build -t test-app . || exit 1
echo "✅ Build exitoso"
SIZE=$(docker images test-app --format "{{.Size}}")
echo "Tamaño: $SIZE"
Health checks → verificar que el contenedor responde:
docker compose up -d --wait
docker inspect --format='{{.State.Health.Status}}' "$(docker compose ps -q app)" | grep -q healthy
echo "✅ Health check OK"
Integración con Testcontainers (Python):
# test_docker.py
from testcontainers.postgres import PostgresContainer
import psycopg2
with PostgresContainer("postgres:16-alpine") as postgres:
conn = psycopg2.connect(
host=postgres.get_container_host_ip(),
port=postgres.get_exposed_port(5432),
user=postgres.POSTGRES_USER,
password=postgres.POSTGRES_PASSWORD,
dbname=postgres.POSTGRES_DB,
)
cur = conn.cursor()
cur.execute("SELECT 1")
assert cur.fetchone() == (1,)
print("✅ Testcontainer Postgres funciona")
Test de estructura de imagen (container-structure-test):
# structure-test.yaml
schemaVersion: "2.0.0"
fileContentTests:
- name: "Non-root user"
path: "/etc/passwd"
expectedContents: ["appuser"]
commandTests:
- name: "Node version"
command: "node"
args: ["--version"]
expectedOutput: ["v20"]
container-structure-test test --image mi-app --config structure-test.yaml
Tests parametrizados con bash
#!/bin/bash
# test_compose.sh
SERVICES=("api" "db" "redis")
for svc in "${SERVICES[@]}"; do
docker compose up -d "$svc" --wait
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$(docker compose ps -q "$svc")" 2>/dev/null || echo "no healthcheck")
echo "$svc: $STATUS"
done
docker compose down
Cobertura y CI
# .github/workflows/docker-test.yml
name: Docker Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: docker build -t mi-app .
- name: Test
run: |
docker compose up -d --wait
curl -f http://localhost:3000/health || exit 1
docker compose down
- name: Security scan
run: docker scout quickview mi-app
🧪 Mini-scripts de verificación
Mini-script 1: Verificar que un contenedor arranca y responde
#!/bin/bash
docker run -d --name test-web -p 8080:80 nginx:alpine
sleep 2
if curl -f http://localhost:8080 > /dev/null 2>&1; then
echo "✅ Contenedor nginx responde"
else
echo "❌ Contenedor no responde"
fi
docker rm -f test-web
Mini-script 2: Verificar multi-stage (imagen pequeña)
#!/bin/bash
docker build -t test-multi -f Dockerfile.multi .
SIZE=$(docker images test-multi --format "{{.Size}}")
echo "Tamaño imagen: $SIZE"
if [[ $SIZE == *"MB"* ]]; then
NUM=${SIZE%MB}
if (( $(echo "$NUM < 50" | bc -l) )); then
echo "✅ Imagen optimizada (< 50MB)"
else
echo "⚠️ Imagen grande ($SIZE)"
fi
fi
🔗 Testcontainers docs, container-structure-test, Docker Scout docs
Conceptos avanzados: Swarm, Kubernetes y producción
Docker Swarm
Swarm convierte un grupo de servidores en un clúster lógico con balanceo de carga integrado.
# Inicializar clúster
docker swarm init
docker swarm join --token SWMTKN-1-... 192.168.1.10:2377
# Crear servicio
docker service create \
--name api \
--replicas 5 \
--publish published=80,target=3000 \
--network overlay-net \
--limit-cpu 0.5 \
--limit-memory 256M \
mi-app:latest
# Rolling update
docker service update \
--image mi-app:v2 \
--update-parallelism 2 \
--update-delay 10s \
api
Stack deploy desde compose:
docker stack deploy -c compose.yml miapp
Kubernetes (K8s)
K8s es el estándar de la industria para orquestación. Minikube te da un clúster local de un nodo.
minikube start
kubectl get nodes
Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
ports:
- containerPort: 3000
resources:
limits:
cpu: "0.5"
memory: "256Mi"
Service:
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
targetPort: 3000
type: ClusterIP
Horizontal Pod Autoscaler:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f hpa.yaml
kubectl scale deployment api --replicas=10
Docker Compose en producción
services:
app:
build: .
restart: unless-stopped
deploy:
replicas: 3
resources:
limits:
cpus: "0.5"
memory: "256M"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Service Mesh (Istio)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: api
spec:
hosts:
- api
http:
- route:
- destination:
host: api
subset: v1
weight: 90
- destination:
host: api
subset: v2
weight: 10
Observabilidad: Prometheus + Grafana
services:
prometheus:
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: "img/guia_0_100_docker/guia_0_100_docker_cover-1200.webp"
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
GitOps con ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
argocd app create miapp --repo https://github.com/mi-org/miapp --path k8s --dest-server https://kubernetes.default.svc
Comparativa de escalado
| Herramienta | Ámbito | Escalado | Balanceo | Persistencia |
|---|---|---|---|---|
| Compose —scale | Un host | Manual | DNS RR | Volúmenes locales |
| Swarm | Multi-host | Manual/automático | Ingress LB | Volúmenes + NFS |
| Kubernetes | Multi-host | HPA automático | Service + Ingress | PVC + CSI |
🧪 Mini-script: probar Swarm local
#!/bin/bash
echo "=== Iniciar Swarm ==="
docker swarm init 2>/dev/null || echo "Swarm ya iniciado"
echo "=== Desplegar servicio ==="
docker service create --name demo --replicas 3 --publish 80:80 nginx:alpine
sleep 3
echo "=== Servicios ==="
docker service ls
echo "=== Escalar ==="
docker service scale demo=5
sleep 2
docker service ps demo
echo "=== Limpiar ==="
docker service rm demo
docker swarm leave --force 2>/dev/null
🔗 Docker Swarm docs, Kubernetes docs, Minikube docs, Istio docs, Prometheus docs
Proyecto final integrador
Gestor de tareas API dockerizado
Proyecto que combina: file system I/O, algoritmos (caché), patrones (sidecar/ambassador), testing, y red.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY . .
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_USER=tasks
- DB_PASSWORD=${DB_PASSWORD}
volumes:
- ./src:/app/src:ro
- logs:/app/logs
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
POSTGRES_DB: tasks
POSTGRES_USER: tasks
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tasks"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:
redis-data:
logs:
// server.js
const express = require('express');
const { Pool } = require('pg');
const Redis = require('ioredis');
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'tasks',
password: process.env.DB_PASSWORD || 'password',
database: 'tasks'
});
const redis = new Redis({ host: 'redis' });
const app = express();
app.use(express.json());
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.get('/tasks', async (req, res) => {
const cached = await redis.get('tasks:list');
if (cached) return res.json(JSON.parse(cached));
const result = await pool.query('SELECT * FROM tasks ORDER BY id');
await redis.setex('tasks:list', 30, JSON.stringify(result.rows));
res.json(result.rows);
});
app.post('/tasks', async (req, res) => {
const { title } = req.body;
const result = await pool.query(
'INSERT INTO tasks (title) VALUES ($1) RETURNING *',
[title]
);
await redis.del('tasks:list');
res.status(201).json(result.rows[0]);
});
app.listen(3000, () => console.log('Tasks API on :3000'));
Pruebas:
#!/bin/bash
# test_proyecto.sh
echo "=== Test: Build ==="
docker build -t tasks-api . || exit 1
echo "=== Test: Up ==="
docker compose up -d --wait
echo "=== Test: Health ==="
curl -f http://localhost:3000/health && echo " ✅ Health OK"
echo "=== Test: Crear tarea ==="
curl -s -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d '{"title":"Test"}' | grep -q "Test" && echo " ✅ Create OK"
echo "=== Test: Listar tareas ==="
curl -s http://localhost:3000/tasks | grep -q "Test" && echo " ✅ List OK"
echo "=== Test: Caché Redis ==="
docker compose exec redis redis-cli get "tasks:list" | grep -q "Test" && echo " ✅ Redis cache OK"
echo "=== Limpiar ==="
docker compose down -v
echo "=== Todos los tests pasaron ==="
Documentación:
README.mdcon instrucciones de build, test y despliegue- Scripts:
build.sh,test.sh,deploy.sh - CI/CD: GitHub Action que ejecuta los tests
Canales y recursos en español
YouTube
- Midulive — Docker, Kubernetes, CI/CD y DevOps con ejemplos prácticos
- MoureDev — Docker desde cero, proyectos dockerizados
- OpenWebinars — Curso completo de Docker y Kubernetes
- EDteam — Rutas de Docker desde fundamentos hasta Swarm
- HolaMundo — Introducciones rápidas a Docker Compose y Swarm
- Programación Fácil — Tutoriales de Docker para principiantes
- Platzi — Curso de Docker y Curso de Kubernetes
Comunidades
- r/docker (Reddit) — Noticias, dudas, proyectos
- r/devops (Reddit) — Comunidad DevOps
- Docker Community Slack — Canales oficiales
- Stack Overflow en español — Etiquetas
dockerydocker-compose
Repositorios
- docker/awesome-compose — Ejemplos oficiales
- veggiemonk/awesome-docker — Lista curada
- jessfraz/dockerfiles — Colección de Dockerfiles
Recursos del blog
Blogs y newsletters
- Docker Blog — Blog oficial
- Play with Docker — Laboratorio interactivo
- Docker Captain Blogs
Hacks y tips de productividad
1. docker system prune --volumes — limpia todo lo no usado
docker system prune -af --volumes
2. dive — inspecciona el contenido de cada capa
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive mi-app
3. lazydocker — TUI interactiva
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock lazyteam/lazydocker
4. docker scout — análisis de vulnerabilidades
docker scout quickview mi-app
docker scout cves mi-app
docker scout recommendations mi-app
5. --progress=plain — logs detallados del build
docker build --progress=plain --no-cache -t mi-app .
6. BuildKit — builds paralelos y secretos
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
RUN --mount=type=secret,id=api_key echo "API key: $(cat /run/secrets/api_key)"
7. Contenedores como herramientas CLI
alias yq='docker run --rm -i -v ${PWD}:/workdir mikefarah/yq'
alias jq='docker run --rm -i -v ${PWD}:/data imega/jq'
alias py3='docker run --rm -i -v ${PWD}:/app python:3.12-slim python'
8. .env con docker compose
DB_PASSWORD=supersecreto
TAG=latest
PORT=3000
9. Docker socket proxy para seguridad
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: 1
SERVICES: 1
10. Probar healthchecks localmente
docker run --rm alpine sh -c "apk add curl && curl -f http://host.docker.internal:3000/health"
11. Depurar contenedor que no arranca
docker logs contenedor --tail 100
docker inspect contenedor | jq '.[0].State'
docker commit contenedor debug-image && docker run -it --entrypoint sh debug-image
12. Exportar logs a JSON
docker logs contenedor --timestamps 2>&1 | jq -R '{timestamp: ., line: input_line_number}'
🔗 Dive GitHub, LazyDocker GitHub, Docker Scout docs
⚠️ Errores comunes
| Error | Causa | Solución |
|---|---|---|
Got permission denied while trying to connect to the Docker daemon socket | Tu usuario no está en el grupo docker o el daemon no está arrancado | Ejecuta sudo usermod -aG docker $USER y vuelve a entrar en la sesión; comprueba con systemctl status docker |
port is already allocated | Otro proceso ya usa el puerto que intentas mapear con -p | Usa sudo lsof -i :<puerto> para identificar el proceso; matalo o cambia el puerto de host (-p 8080:80) |
The container name "<nombre>" is already in use | Ya existe un contenedor con ese nombre (activo o detenido) | Renómbralo con --name otro-nombre o elimina el anterior con docker rm <nombre> |
Cannot connect to the Docker daemon at unix:///var/run/docker.sock | El servicio Docker no está arrancado | sudo systemctl start docker y verifica con docker ps |
docker: command not found | Docker no está instalado o no está en el PATH | Instala Docker Engine siguiendo la sección de instalación de esta guía; en WSL instala el paquete docker.io |
failed to solve: process ... did not complete successfully | Error durante el build con BuildKit (fallo en un RUN, COPY o resolución de imagen base) | Revisa el log completo con docker build --no-cache .; comprueba que las rutas en COPY existen y que la imagen base es válida |
no matching manifest for linux/arm64 in the manifest list entries | Intentas tirar una imagen solo disponible para amd64 en un Mac M1/M2 o ARM | Añade --platform linux/amd64 o busca una imagen multi-arch; alternativamente usa docker buildx para cross-build |
🔗 Guías relacionadas
Estas guías te ayudan a complementar lo que aprendiste aquí:
- Guía de Astro: de 0 a 100 — despliega tu sitio Astro con Docker y aprende a containerizar frontend moderno
- Docker: instalación, primeros pasos y containerizar — repaso guiado si eres nuevo en contenedores
- WSL: instalación, configuración y Docker Desktop — configura Docker en Windows con WSL2 paso a paso
Referencias y documentación oficial
- Docs oficiales: docs.docker.com
- Docker Hub: hub.docker.com
- Dockerfile reference: docs.docker.com/engine/reference/builder
- Compose specification: docs.docker.com/compose/compose-file
- Docker Engine: docs.docker.com/engine
- Docker Security: docs.docker.com/engine/security
- Docker Swarm: docs.docker.com/engine/swarm
- BuildKit: docs.docker.com/build/buildkit
- Docker Scout: docs.docker.com/scout
- Docker Blog: docker.com/blog
- Docker en GitHub: github.com/docker
- Play with Docker: labs.play-with-docker.com
- Docker Curriculum: docker-curriculum.com
- Kubernetes docs: kubernetes.io/docs
- Kubernetes Patterns: redhat.com/kubernetes-patterns
- OWASP Docker Security: cheatsheetseries.owasp.org
- Containers vs VMs (IBM): ibm.com/cloud/blog/containers-vs-vms
- OverlayFS kernel docs: kernel.org/doc/Documentation/filesystems/overlayfs.txt
- Testcontainers: testcontainers.com
- container-structure-test: github.com/GoogleContainerTools/container-structure-test
- Docker Awesome Compose: github.com/docker/awesome-compose
- r/docker: reddit.com/r/docker
🏆 Retos Relacionados
Pon a prueba lo aprendido con estos desafíos:
