C.G.

Docker para Desarrolladores — Lo Esencial

Docker no elimina los bugs, pero elimina una clase entera de diferencias entre entornos. Esta guía cubre lo que uso a diario: imágenes y contenedores, Dockerfile, Compose, datos, redes, seguridad y operación, con los errores que más cuestan.

Qué es Docker y qué problema resuelve

Docker empaqueta una aplicación con sus dependencias en una imagen que se ejecuta igual en local, en CI y en el servidor. Resuelve el clásico "en mi máquina funciona" sin obligarte a mantener una máquina virtual por servicio.

AspectoContenedorMáquina virtual
ArranqueSegundosMinutos
AislamientoProcesoSistema completo
TamañoMegabytesGigabytes
KernelCompartido con el hostPropio
Uso típicoServicios, CI, desarrolloAislamiento fuerte u otro sistema operativo

Imagen, contenedor, volumen y red

Una imagen es el paquete inmutable: sistema base, dependencias y código. Un contenedor es una instancia en ejecución. Si la imagen es la receta, el contenedor es el plato. Un volumen es almacenamiento que sobrevive al contenedor, y una red permite que los servicios se encuentren por nombre sin exponer puertos innecesarios.

Esa distinción explica por qué reconstruir una imagen cambia el contenedor y por qué los datos escritos dentro de un contenedor desaparecen al recrearlo si no están en un volumen.

Primeros comandos

# Construir una imagen
docker build -t mi-app .

# Ejecutar un contenedor
docker run -d -p 3000:3000 --name app mi-app

# Ver logs
docker logs -f app

# Ejecutar shell dentro del contenedor
docker exec -it app sh

# Listar contenedores e imágenes
docker ps
docker images

# Detener y eliminar un contenedor
docker stop app
docker rm app

Dockerfile: capas, multi-stage y .dockerignore

Un Dockerfile se construye por capas. El orden importa: copiar primero package.json y el lockfile permite reutilizar la capa de dependencias cuando solo cambia el código. Copiar todo antes de npm ci invalida la caché en cada commit.

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]

El multi-stage separa compilación y ejecución, deja una imagen final más pequeña y evita llevar las dependencias de build a producción. Un .dockerignore con node_modules, .git y archivos locales acelera el build y reduce el contexto.

Docker Compose: varios servicios sin dolor

services:
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
  api:
    build: ./api
    ports:
      - "3000:3000"
    depends_on:
      - db

volumes:
  pgdata:

Con un solo docker compose up -d despliego PostgreSQL, Redis, n8n, una API NestJS y un dashboard React. Un docker compose down detiene los servicios, pero el volumen pgdata conserva los datos.

Datos y secretos

Los datos persistentes van en volúmenes; los secretos, en variables de entorno o en un gestor de secretos. Nunca en la imagen ni en el repositorio. Un archivo .env sirve para desarrollo, pero debe estar en .gitignore. Si el contenedor de PostgreSQL no tiene volumen, un docker compose down puede borrar la base de datos.

Redes y reverse proxy

En Compose, los servicios se encuentran por su nombre: la API se conecta a db:5432, no a localhost. Esa red interna evita exponer la base de datos al exterior; solo el servicio que necesita un puerto público lo publica. En producción, un reverse proxy o la plataforma termina TLS y reenvía al contenedor; el contenedor no debería saber si está detrás de Nginx, Caddy o un túnel.

Build cache y CI/CD

En CI construyo la imagen una vez, la etiqueto con el SHA del commit y la promuevo entre entornos. Así lo que se prueba es exactamente lo que se despliega. La caché de capas acelera los builds, pero solo si el orden del Dockerfile la respeta. Para dependencias pesadas, un caché de BuildKit o un registro intermedio evita reconstruir todo en cada pipeline.

Seguridad básica

No ejecutes contenedores como root, no publiques la base de datos y mantén las imágenes base actualizadas. Un escaneo con Trivy en CI detecta vulnerabilidades conocidas antes de que lleguen a producción. Los secretos no van en el Dockerfile: si necesitas pasarlos en build, usa BuildKit secrets en lugar de ARG, porque los argumentos quedan en el historial de la imagen.

Operación y diagnóstico

En producción reviso logs, uso healthchecks y configuro una política de reinicio. Compose permite declarar restart: unless-stopped y un healthcheck que consulta un endpoint. Si el contenedor falla, se reinicia; si no responde, se marca como unhealthy. docker stats muestra CPU y memoria, docker system df revela el espacio usado y docker system prune limpia lo que ya no se usa.

Errores comunes y cómo diagnosticarlos

SíntomaCausa probableAcción
Puerto ocupadoOtro proceso usa el puertoCambia el mapeo o detén el proceso
El contenedor reiniciaFalla el proceso o el healthcheckRevisa docker logs y el comando
No persisten datosFalta un volumenDeclara el volumen en Compose
Permiso denegadoUID distinto al del hostAjusta usuario o permisos
Imagen enormeDependencias de build y capasMulti-stage, Alpine y .dockerignore
Arquitectura incorrectaImagen ARM en un servidor x86Construye con --platform

Docker en desarrollo

En local uso bind mounts para montar el código dentro del contenedor y aprovechar el hot reload. Así el contenedor mantiene las dependencias y el editor sigue trabajando en el host. Para tareas puntuales, docker compose run --rm ejecuta un comando sin dejar contenedores colgando.

No reconstruyo la imagen en cada cambio de código; solo cuando cambian dependencias o el Dockerfile. Esa separación ahorra tiempo y evita builds innecesarios.

Compose, Swarm o Kubernetes

Compose es suficiente para un VPS con pocos servicios y un solo host. Si necesitas alta disponibilidad, escalado automático o varios nodos, el siguiente paso es un orquestador. Kubernetes resuelve eso, pero añade complejidad operativa; no lo adoptaría solo porque suena más profesional.

Para muchos proyectos, un VPS con Compose, healthchecks y backups bien probados es más fiable que un clúster mal administrado.

Registros e imágenes

Etiqueto cada imagen con el SHA del commit y la publico en un registro. El servidor hace pull de esa etiqueta exacta, no de latest. Así puedo volver a una versión anterior sin adivinar qué contenía la imagen.

Límites de recursos y logs

En Compose declaro límites de memoria y CPU para que un servicio no consuma el host entero. También configuro el driver de logs con rotación; sin límite, un contenedor ruidoso puede llenar el disco. Son ajustes pequeños que evitan incidentes grandes.

Docker, Podman y otras alternativas

Podman es una alternativa sin daemon y con soporte rootless; para la mayoría de flujos, los comandos son compatibles con Docker. Docker sigue siendo el estándar y tiene el ecosistema más amplio. La elección depende del entorno y de las políticas de seguridad, no de una preferencia personal.

También existen runtimes más ligeros para casos embebidos, pero no sustituyen a Docker cuando necesitas imágenes portables, Compose y un registro. Antes de cambiar de herramienta, mide qué problema concreto estás resolviendo.

Despliegue sin downtime

Para actualizar un servicio sin cortar la conexión, levanto el nuevo contenedor, espero su healthcheck y solo entonces retiro el anterior. Con un reverse proxy delante, el tráfico se mueve cuando el nuevo está sano. Si el healthcheck falla, el proxy sigue apuntando a la versión anterior.

No es magia de orquestación: es un procedimiento pequeño y comprobable. En un VPS con Compose, un script de despliegue con healthcheck cubre la mayoría de los casos.

Cuándo no usar Docker

Para un script de una sola ejecución o una función serverless pequeña, un contenedor puede ser más infraestructura de la necesaria. Docker brilla cuando hay varios servicios, dependencias del sistema o una diferencia real entre local y producción.

Checklist de producción

  • Imagen con versión fija, no latest.
  • Usuario no root y .dockerignore.
  • Datos en volúmenes y secretos fuera de la imagen.
  • Healthcheck y política de reinicio.
  • Puertos publicados solo los necesarios.
  • Límites de memoria y CPU definidos.
  • Logs accesibles y rotación configurada.
  • Escaneo de vulnerabilidades en CI.
  • Backup de la base de datos probado.
  • Proceso de rollback documentado.

Preguntas frecuentes

¿Docker es una máquina virtual?

No. Un contenedor comparte el kernel del host y aísla el proceso; una máquina virtual emula un sistema completo. Por eso los contenedores arrancan más rápido y pesan menos, aunque el aislamiento es distinto.

¿Puedo usar Docker en producción?

Sí, con healthchecks, reinicios, límites de recursos y una estrategia de despliegue. Para proyectos pequeños, una plataforma gestionada puede ser más simple que administrar el orquestador.

¿Cómo persisto los datos?

Con volúmenes. Los datos escritos en la capa del contenedor se pierden al recrearlo. Declara el volumen en Compose y prueba la restauración de un backup.

¿Cómo reduzco el tamaño de la imagen?

Usa Alpine, multi-stage builds, .dockerignore y solo dependencias de producción en la etapa final. Revisa el tamaño por capas antes de optimizar a ciegas.

Para seguir

La referencia oficial está en la documentación de Docker y Docker Compose. El caso de Convenios, con PostgreSQL, n8n y una API NestJS en contenedores, está en este caso de estudio. Si quieres una segunda opinión sobre tu infraestructura, hablemos. También puedes leer cómo llevo un modelo de IA al servicio en De las series temporales al producto.

← Volver