🐳 Docker de verdad
Imágenes vs contenedores, Dockerfile completo con multi-stage builds, docker-compose real con app, postgres y redis, buenas prácticas y comandos esenciales.
Docker de verdad
Docker empaqueta tu aplicación y todo su entorno en una unidad portable: la misma imagen funciona en tu laptop, en CI y en producción. Esta guía va del concepto de imagen/contenedor hasta compose reales multi-servicio y buenas prácticas que evitan imágenes de 2 GB llenas de basura.
Imágenes vs contenedores
| Imagen | Contenedor | |
|---|---|---|
| Qué es | Plantilla inmutable (capas de archivos + config) | Una instancia en ejecución de una imagen |
| Analogía | Un programa / un ISO / una clase | Un proceso / una VM / una instancia |
| Se crea | docker build |
docker run |
| Se ve | docker images |
docker ps |
| Ciclo de vida | persistente | efímero (puede nacer y morir) |
De una imagen salen tantos contenedores como quieras, cada uno con su propio filesystem escribible, PID, red y espacio de nombres aislados.
Anatomía de un Dockerfile
# Base del sistema (elige bases pequeñas y con soporte)
FROM node:22-alpine
# Metadatos
LABEL maintainer="dev@midominio.com"
# Directorio de trabajo (se usa para los comandos siguientes)
WORKDIR /app
# Copiar primero solo package.json para cachear capas
COPY package.json package-lock.json ./
# Instalar dependencias
RUN npm ci --production
# Copiar el resto del código
COPY . .
# Variables de entorno por defecto (sobrescribibles en runtime)
ENV NODE_ENV=production \
PORT=3000
# Puerto que expone el contenedor (documental)
EXPOSE 3000
# Usuario no-root (¡seguridad!)
USER node
# Comando al arrancar
CMD ["node", "src/server.js"]
Directivas clave:
| Directiva | Qué hace |
|---|---|
FROM |
Base de la imagen (obligatoria, primera) |
RUN |
Ejecuta un comando en build (crea una capa) |
COPY |
Copia archivos del host al contenedor |
WORKDIR |
Directorio de trabajo de los siguientes comandos |
ENV |
Variables de entorno |
EXPOSE |
Puerto expuesto (documental) |
CMD |
Comando por defecto (se puede sobrescribir) |
ENTRYPOINT |
Comando fijo (difícil de sobrescribir) |
USER |
Usuario con el que corre el proceso |
💡 CMD vs ENTRYPOINT:
ENTRYPOINTdefine el proceso principal que no debe cambiar;CMDson los argumentos/el comando por defecto. Juntos:ENTRYPOINT ["node"] CMD ["server.js"]— puedes pasardocker run img otros.jsy sustituye solo elCMD.
Multi-stage build: compilar Go en un stage, copiar el binario
El patrón clave para imágenes pequeñas: compilas con todo el toolchain en un stage y solo copias el resultado a una imagen final mínima.
# ---- Stage 1: compilar ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .
# ---- Stage 2: imagen final ----
FROM alpine:3.20
RUN apk add --no-cache ca-certificates && \
adduser -D -H appuser
WORKDIR /app
COPY --from=builder /app/server .
USER appuser
EXPOSE 8080
ENTRYPOINT ["/app/server"]
Resultado: una imagen de unos pocos MB en vez de gigas. No llevas el compilador ni el código fuente a producción.
⚠️
CGO_ENABLED=0produce un binario Go estático (sin librerías C), que corre enalpinesin problemas. Si tu app usa CGO (por ejemplonetcon resolvers nativos), necesitasglibc, nomusl.
docker-compose: app + postgres + redis
docker-compose.yml (o compose.yaml) define y arranca varios servicios juntos, con redes y volúmenes:
version: "3.9"
services:
app:
build: .
restart: unless-stopped
environment:
DB_HOST: postgres
REDIS_HOST: redis
PORT: "8080"
ports:
- "8080:8080" # host:contenedor
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # desde .env
POSTGRES_DB: miapp
volumes:
- pgdata:/var/lib/postgresql/data # volumen nombrado (persistente)
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 3s
retries: 5
networks:
- backend
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redisdata:/data
networks:
- backend
networks:
backend:
volumes:
pgdata:
redisdata:
💡 Volúmenes guardan datos fuera del contenedor, que es efímero. Sin volumen,
docker compose down && upborraría tu base de datos. Los volúmenes nombrados sobreviven al contenedor y acompose down.
docker compose up -d # construye y arranca en segundo plano
docker compose down # detiene y elimina contenedores (no volúmenes)
docker compose down -v # ...y también los volúmenes (¡borra datos!)
docker compose logs -f app # logs de un servicio
docker compose exec app sh # shell dentro del contenedor
Buenas prácticas
Imágenes pequeñas y capas
# 1. Usa bases minimalistas (alpine, distroless)
FROM node:22-alpine
# 2. Ordena COPY: lo que cambia menos, primero (mejor cache)
COPY package.json ./
RUN npm ci
COPY . .
# 3. Ejecuta múltiples comandos en un solo RUN (una capa)
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
💡 Cada
RUN/COPYcrea una capa; la caché de capas acelera builds, pero también engorda la imagen. Combina lo que va junto y borra archivos temporales dentro del mismoRUN.
.dockerignore
Como .gitignore, excluye lo que no debe ir a la imagen (y a la caché de build):
node_modules
.git
.env
dist
*.log
Dockerfile*
.dockerignore
⚠️ Nunca copies
.enva una imagen: quedaría dentro de las capas y cualquiera con acceso a la imagen podría extraerlo. Pasa secretos en runtime (-e, archivossecrets, o un secret manager).
Comandos esenciales
| Necesito… | Comando |
|---|---|
| Construir imagen | docker build -t miapp . |
| Ejecutar contenedor | docker run -d -p 8080:8080 --name app miapp |
| Listar contenedores | docker ps / docker ps -a |
| Entrar a un contenedor | docker exec -it app sh |
| Ver logs | docker logs -f app |
| Parar/borrar | docker stop app / docker rm app |
| Ver imágenes | docker images |
| Limpiar cosas sin usar | docker system prune -a |
| Redes | docker network ls |
| Volúmenes | docker volume ls |
Cheatsheet Dockerfile
| Directiva | Uso típico |
|---|---|
FROM base AS stage |
Base + nombre de stage |
COPY --from=builder /ruta /dest |
Copia entre stages (multi-stage) |
RUN |
Instalar, compilar, limpiar |
CMD / ENTRYPOINT |
Comando de arranque |
USER no-root |
Seguridad |
Para profundizar
- Docker docs: Get Started: el punto de partida oficial.
- Dockerfile reference: todas las directivas en detalle.
- Compose file reference: schema completo de compose.
- Docker Best Practices (best practices guides): buenas prácticas oficiales.
- Google: best practices for building containers: recomendaciones de producción.
- Ruta completa: Infraestructura y DevOps.