¿Qué problema resuelve Docker?

Si alguna vez oíste "en mi máquina funciona" sabes exactamente el problema. Una aplicación depende de versiones específicas de librerías, configuraciones del sistema operativo y variables de entorno. Reproducir ese entorno en otro servidor era un trabajo tedioso y propenso a errores.

Docker empaqueta la aplicación junto con todo lo que necesita en un contenedor: unidad aislada, ligera y reproducible que funciona igual en cualquier máquina que tenga Docker instalado.

Conceptos básicos

  • Imagen: plantilla de solo lectura con el sistema de archivos y configuración. Piénsala como una "foto" del entorno
  • Contenedor: instancia en ejecución de una imagen. Puedes tener varios contenedores de la misma imagen
  • Dockerfile: archivo de texto que define cómo construir una imagen
  • Docker Hub: registro público de imágenes. Como npm o PyPI pero para contenedores
  • Volumen: almacenamiento persistente que sobrevive a la destrucción del contenedor

Comandos esenciales

# Descargar y ejecutar una imagen
docker run nginx
docker run -d -p 8080:80 --name mi-nginx nginx
# -d: en segundo plano, -p: mapear puerto host:contenedor

# Gestionar contenedores
docker ps           # Contenedores en ejecución
docker ps -a        # Todos (incluidos parados)
docker stop mi-nginx
docker start mi-nginx
docker rm mi-nginx   # Eliminar contenedor

# Ver logs
docker logs mi-nginx
docker logs -f mi-nginx   # Seguir en tiempo real

# Entrar en un contenedor
docker exec -it mi-nginx bash

# Gestionar imágenes
docker images
docker pull ubuntu:22.04
docker rmi ubuntu:22.04

# Limpiar todo lo que no se usa
docker system prune -a

Dockerfile: construir tu propia imagen

FROM node:20-alpine

WORKDIR /app

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

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]
# Construir y ejecutar
docker build -t mi-app:1.0 .
docker run -d -p 3000:3000 mi-app:1.0

Buenas prácticas en el Dockerfile:

  • Usa imágenes base alpine (más pequeñas y seguras)
  • Pon COPY package.json antes del código para aprovechar la caché de capas
  • No ejecutes como root — añade un usuario sin privilegios
  • Usa .dockerignore para excluir node_modules, .git, etc.

Docker Compose: orquestar múltiples servicios

La mayoría de aplicaciones necesitan varios servicios: web, base de datos, caché... Docker Compose los define en un archivo compose.yml:

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/miapp
    depends_on:
      - db

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

volumes:
  postgres_data:
docker compose up -d      # Arrancar todo
docker compose down       # Parar y eliminar
docker compose logs -f    # Ver logs de todos los servicios
docker compose ps         # Estado de todos los servicios

Seguridad básica en Docker

  • No uses la imagen latest en producción. Especifica versiones
  • Escanea imágenes con docker scout o Trivy antes de usarlas
  • No montes el socket de Docker (/var/run/docker.sock) en contenedores sin necesidad absoluta
  • Lee-only filesystem cuando el contenedor no necesita escribir: --read-only
  • Limita recursos: --memory="512m" --cpus="0.5"

Casos de uso que más uso

En mi homelab uso Docker Compose para casi todo:

  • Nextcloud (almacenamiento personal)
  • Portainer (interfaz web para gestionar Docker)
  • Nginx Proxy Manager (reverse proxy con SSL automático)
  • Vaultwarden (Bitwarden self-hosted)
  • Grafana + Prometheus (monitorización)

Docker convirtió lo que antes requería horas de configuración en un docker compose up de 2 minutos.