Saltar al contenido
← Blog
DevOps3 de agosto de 20267 min de lectura

Docker para developers que tienen miedo de DevOps

Una guía sin tecnicismos innecesarios. Docker explicado como lo necesitas usar: empaquetar tu app, correrla en cualquier lado, y no perder el fin de semana configurando servidores.

MM

softw.engineer

Software Engineer · softw.engineer

Docker para developers que tienen miedo de DevOps

Docker para developers que tienen miedo de DevOps

Si cada vez que alguien dice "Docker" sientes que te estás perdiendo de algo importante pero no sabes qué, este artículo es para ti. Vamos a saltarnos toda la teoría sobre namespaces de Linux y union filesystems. Docker es una caja sellada con tu app adentro. Eso es todo lo que necesitas saber para empezar.

El problema que Docker resuelve

Tienes una app que funciona en tu máquina. La despliegas en producción. No funciona. ¿Por qué? Porque tu máquina tiene Node 20.16.0. Producción tiene Node 20.14.0. Tu app usa un feature que se añadió en .16.

O tu máquina tiene macOS. El servidor tiene Ubuntu. Una dependencia nativa compila diferente. O tienes una variable de entorno que solo existe localmente.

Docker resuelve esto empaquetando todo: tu código, tus dependencias, tu runtime, tus configuraciones. Todo dentro de una imagen inmutable. Esa imagen corre idéntica en cualquier máquina con Docker instalado.

"Works on my machine" deja de ser una excusa. Se convierte en "works everywhere, because the machine comes with the code."

El Dockerfile mínimo viable

No necesitas un Dockerfile de 40 líneas para empezar. Para una app Node, esto es suficiente:

FROM node:20-slim

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

7 líneas. Tu app empaquetada en una imagen que pesa ~200MB. ¿Es perfecta? No. ¿Es suficiente para producción? Probablemente sí para empezar.

Construyes la imagen: docker build -t miapp . La corres: docker run -p 3000:3000 miapp

Tu app está viva. En cualquier máquina. Sin pelear con versiones.

El archivo que todos olvidan: .dockerignore

Este es el error número uno que veo en equipos que adoptan Docker. No tienen .dockerignore.

Sin .dockerignore, Docker copia todo tu directorio al contexto de build. Eso incluye node_modules/ (que en proyectos grandes puede pesar 500MB+), .git/, archivos .env con credenciales, logs de desarrollo.

El build tarda 45 segundos en lugar de 8. Tu imagen pesa 1.2GB en lugar de 200MB. Y potencialmente estás metiendo secretos en la imagen.

# .dockerignore
node_modules
.git
.env*
*.md
dist
build
coverage
.DS_Store
npm-debug.log*

Un archivo. Diez segundos en crearlo. Te ahorra minutos en cada build y elimina riesgos de seguridad.

Docker Compose: porque una app no vive sola

Ninguna aplicación real es un solo contenedor. Tienes tu app, una base de datos, tal vez Redis para cache. Docker Compose orquesta múltiples contenedores con un solo archivo.

version: "3.8"

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/miapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: miapp
    volumes:
      - pgdata:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine

volumes:
  pgdata:

Un comando: docker compose up. Tres servicios levantados. Tu app, Postgres y Redis conectados automáticamente por la red interna de Docker. depends_on garantiza que la base de datos arranque antes que tu app.

Cuando terminas: docker compose down. Todo se limpia.

Multi-stage builds: imágenes que no pesan un gigabyte

Aquí es donde la mayoría se queda. Su imagen de producción pesa 1.2GB porque incluye devDependencies, TypeScript como dependencia, y todo el código fuente.

Multi-stage build resuelve esto usando múltiples etapas. La primera construye tu app. La segunda solo copia el resultado.

# Stage 1: Build
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY --from=build /app/dist ./dist

EXPOSE 3000
CMD ["node", "dist/server.js"]

Tu imagen pasa de 1.2GB a ~180MB. TypeScript, ESLint, y todas tus devDependencies se quedan en el stage de build. La imagen final solo tiene lo que necesita para correr.

Las capas y el cache de Docker

Docker construye imágenes en capas. Cada instrucción del Dockerfile crea una capa. Docker cachea capas que no cambiaron.

Por eso el orden importa. Si haces COPY . . antes de npm install, cada cambio en tu código invalida el cache y reinstala todas las dependencias. Si lo haces al revés:

COPY package*.json ./     # solo si package.json cambia
RUN npm ci                # se re-ejecuta
COPY . .                  # esto cambia seguido, pero no invalida lo anterior

Un proyecto con 500 dependencias pasa de 45 segundos a 3 segundos en builds incrementales. Mismo Dockerfile, diferente orden.

Variables de entorno: el patrón correcto

Nunca pongas secretos en el Dockerfile. Es código versionado. Tu password de base de datos en un archivo que va a Git es un incidente de seguridad esperando a pasar.

Docker soporta variables de entorno de tres formas:

docker-compose.yml con .env file:

services:
  app:
    environment:
      - DATABASE_URL=${DATABASE_URL}
# .env (en .gitignore)
DATABASE_URL=postgresql://user:secret@db:5432/miapp

Runtime:

docker run -e DATABASE_URL=postgresql://... miapp

Docker secrets (para producción):

echo "mysecret" | docker secret create db_password -

El patrón correcto: .env para desarrollo local, Docker secrets o un gestor de secretos (Vault, Doppler) para producción.

Healthchecks: que Docker sepa si tu app está viva

Por default, Docker considera un contenedor "corriendo" mientras el proceso principal esté vivo. Pero tu app puede estar colgada: aceptando conexiones pero no respondiendo. Un healthcheck le dice a Docker cómo verificar de verdad.

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Cada 30 segundos, Docker hace un request a /health. Si falla 3 veces seguidas, el contenedor se marca como unhealthy. En Docker Swarm o Kubernetes, esto activa reinicios automáticos.

La regla del 80/20

Docker tiene cientos de features. Volumes, networks, plugins, buildkit, buildx, scan, scout, compose profiles, overrides. Puedes pasar meses aprendiendo todo.

No lo hagas. Necesitas esto:

  1. Saber escribir un Dockerfile (ya lo sabes)
  2. Saber usar docker compose (ya lo sabes)
  3. Entender capas y cache (ya lo entiendes)
  4. Multi-stage builds (ya lo sabes)
  5. Variables de entorno (ya lo sabes)

Eso es el 80% de lo que vas a usar el 80% del tiempo. El resto lo aprendes cuando lo necesitas. Docker tiene excelente documentación. Cuando te encuentres con un problema específico, busca esa feature.

Errores que cometí para que tú no los cometas

No limpiar imágenes viejas. Docker acumula imágenes, capas y contenedores parados. Después de 6 meses, tienes 20GB de basura. Solución: docker system prune -a --volumes una vez al mes.

Usar latest tag en producción. latest suena bien, pero significa "lo último que se publicó". Una actualización mayor de Node puede romper tu app sin que sepas qué cambió. Pinea versiones específicas: node:20.16.0-slim.

No leer logs. docker logs <container> es tu mejor herramienta de debug. docker logs -f <container> para seguir en tiempo real. Si algo no funciona, ahí está la respuesta.

Correr como root. Por default, Docker corre todo como root dentro del contenedor. En producción, crea un usuario sin privilegios:

RUN useradd -m appuser
USER appuser

Si hay un RCE en tu app, el atacante al menos no tiene root.

Empezando de verdad

Lee todo lo que quieras. Pero no vas a aprender Docker leyendo. Vas a aprender Docker haciéndolo.

Toma cualquier proyecto que tengas. Crea un Dockerfile. Construye la imagen. Córrelo. Luego añade docker-compose con una base de datos. Luego haz un multi-stage build.

En 2 horas tendrás más conocimiento práctico que en 10 horas de tutoriales. Docker no es complicado. Es intimidante porque tiene demasiado ruido alrededor. La realidad: necesitas 5 conceptos y media docena de comandos. El resto es ruido.


Recursos:

#Docker#DevOps#deploy#tutorial#contenedores

Compartir

¿Qué te pareció esto?

Tu feedback nos ayuda a crear mejor contenido

¿Qué opinas?

Déjanos tu comentario o pregunta sobre este artículo.