Lo que aprenderas en este tutorial
- Escritura de Dockerfile y significado de cada instruccion
- Build de imagenes y ejecucion de contenedores
- Gestion de multiples contenedores con docker-compose
- Construccion de entorno de desarrollo Node.js + PostgreSQL
Requisitos previos: Docker Desktop instalado. Si
docker --versionmuestra la version, estas listo.
Que es Docker? Por que surgio?
Historia de la tecnologia de contenedores
Las raices de la tecnologia de contenedores se remontan a chroot de UNIX V7 en 1979. Posteriormente, evoluciono de la siguiente manera:
| Ano | Tecnologia | Descripcion |
|---|---|---|
| 1979 | chroot | Aislamiento del sistema de archivos |
| 2000 | FreeBSD Jail | Aislamiento de procesos |
| 2006 | cgroups (Google) | Limitacion de recursos |
| 2008 | LXC (Linux Containers) | Contenedores ligeros para Linux |
| 2013 | Docker | Estandarizacion y popularizacion de contenedores |
El nacimiento de Docker
En 2013, Solomon Hykes (dotCloud) presento Docker.
“Docker hizo posible empaquetar aplicaciones facilmente y ejecutarlas de la misma manera en cualquier lugar” — Docker oficial
Por que Docker fue revolucionario
- Compartir “entornos funcionales”: Resolvio el problema de “funciona en mi maquina”
- Ligero: A diferencia de VMs, comparte el SO, inicio en segundos
- Reproducibilidad de imagenes: Reproduccion completa del entorno mediante Dockerfile
- Ecosistema: Comparticion de imagenes a traves de Docker Hub
Diferencia entre VMs y contenedores
flowchart TB
subgraph VM["Maquina Virtual (VM)"]
direction TB
AppA1["App A"] & AppB1["App B"]
GuestOS1["Guest OS"] & GuestOS2["Guest OS"]
Hypervisor["Hypervisor"]
HostOS1["Host OS"]
HW1["Hardware"]
end
subgraph Container["Contenedor"]
direction TB
AppA2["App A"] & AppB2["App B"]
Runtime["Container Runtime<br/>(Docker)"]
HostOS2["Host OS"]
HW2["Hardware"]
end
| Caracteristica | VM | Contenedor |
|---|---|---|
| Tiempo de inicio | Minutos | Segundos |
| Uso de memoria | GB | MB |
| Nivel de aislamiento | Completo | A nivel de proceso |
| Independencia de SO | Completa | Kernel compartido |
Documentacion oficial: Docker overview
Conceptos basicos de Docker
Imagenes y contenedores
- Imagen (Image): El “plano” de la aplicacion. Solo lectura
- Contenedor (Container): Una “instancia en ejecucion” creada a partir de una imagen
flowchart LR
subgraph Static["Estatico"]
Dockerfile["Dockerfile"]
Image["node:20<br/>(imagen)"]
end
subgraph Dynamic["Dinamico"]
Container["En ejecucion<br/>(contenedor)"]
Stdout["stdout<br/>(logs)"]
end
Dockerfile -->|build| Image
Image -->|run| Container
Container -->|logs| Stdout
Estructura de capas de imagenes
Las imagenes Docker estan compuestas por capas, y solo se reconstruyen las partes modificadas:
FROM node:20-alpine # Capa base (~100MB)
WORKDIR /app # Capa de configuracion (~0KB)
COPY package*.json ./ # Capa de dependencias (~1KB)
RUN npm install # Capa node_modules (~50MB)
COPY . . # Capa de codigo de app (~10KB)
Mejor practica: Coloca los elementos que cambian menos arriba y los que cambian mas abajo para aprovechar el cache eficientemente
Step 1: Crear estructura del proyecto
Primero, creamos la estructura de directorios del proyecto.
mkdir docker-tutorial
cd docker-tutorial
mkdir src
touch Dockerfile docker-compose.yml src/index.js package.json
Step 2: Crear aplicacion Node.js
Creamos un servidor Express simple.
package.json
{
"name": "docker-tutorial",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
src/index.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hola desde Docker!',
timestamp: new Date().toISOString()
});
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Step 3: Crear Dockerfile
El Dockerfile es el plano para construir la imagen.
Dockerfile
# Especificar imagen base
FROM node:20-alpine
# Establecer directorio de trabajo
WORKDIR /app
# Copiar archivos de dependencias (optimizacion de cache)
COPY package*.json ./
# Instalar dependencias
RUN npm install
# Copiar codigo de aplicacion
COPY . .
# Exponer puerto (documentacion)
EXPOSE 3000
# Comando de inicio
CMD ["npm", "start"]
Explicacion detallada de instrucciones Dockerfile
| Instruccion | Descripcion | Ejemplo |
|---|---|---|
FROM | Especifica imagen base | FROM node:20-alpine |
WORKDIR | Establece directorio de trabajo | WORKDIR /app |
COPY | Copia archivos | COPY . . |
RUN | Ejecuta comando durante build | RUN npm install |
CMD | Comando por defecto al iniciar contenedor | CMD ["npm", "start"] |
ENTRYPOINT | Punto de entrada del contenedor | ENTRYPOINT ["node"] |
ENV | Establece variable de entorno | ENV NODE_ENV=production |
EXPOSE | Expone puerto (documentacion) | EXPOSE 3000 |
ARG | Argumento durante build | ARG VERSION=1.0 |
Diferencia entre CMD y ENTRYPOINT
# CMD: Comando por defecto sobrescribible
CMD ["npm", "start"]
# docker run myapp npm run dev ← se sobrescribe
# ENTRYPOINT: Comando que siempre se ejecuta
ENTRYPOINT ["node"]
CMD ["index.js"]
# docker run myapp app.js ← se ejecuta como node app.js
Documentacion oficial: Dockerfile reference
Step 4: Build y ejecucion de imagen
# Build de imagen
docker build -t my-node-app .
# Verificar imagen construida
docker images
# Iniciar contenedor
docker run -p 3000:3000 my-node-app
# Iniciar en segundo plano
docker run -d -p 3000:3000 --name my-app my-node-app
# Acceder a http://localhost:3000 en el navegador
Opciones comunes de docker run
docker run \
-d # Modo detach (segundo plano)
-p 3000:3000 # Mapeo de puerto (host:contenedor)
--name my-app # Nombre del contenedor
-e NODE_ENV=production # Variable de entorno
-v $(pwd):/app # Montaje de volumen
--rm # Eliminar automaticamente al terminar
my-node-app # Nombre de imagen
Step 5: Construccion de entorno con docker-compose
Gestionamos multiples servicios (app + base de datos) a la vez.
docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://user:password@db:5432/mydb
volumes:
- ./src:/app/src # Para hot reload
depends_on:
- db
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
Estructura de docker-compose.yml
version: '3.8' # Version de especificacion Compose
services: # Definicion de servicios (contenedores)
service_name:
image: xxx # o build: ./path
ports: # Mapeo de puertos
environment: # Variables de entorno
volumes: # Volumenes
depends_on: # Dependencias
restart: # Politica de reinicio
volumes: # Volumenes nombrados
networks: # Redes personalizadas
Comandos docker-compose
# Iniciar todos los servicios
docker-compose up
# Iniciar en segundo plano
docker-compose up -d
# Reconstruir imagen e iniciar
docker-compose up --build
# Ver logs
docker-compose logs -f app
# Iniciar solo un servicio especifico
docker-compose up app
# Detener servicios
docker-compose down
# Eliminar incluyendo volumenes
docker-compose down -v
# Verificar estado de servicios
docker-compose ps
Hot reload: Al montar el codigo fuente con
volumes, los cambios en archivos se reflejan inmediatamente en el contenedor.
Documentacion oficial: Docker Compose overview
Mejores practicas de Dockerfile
1. Build multi-etapa
Creamos imagenes ligeras para produccion:
# Etapa de build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Etapa de produccion
FROM node:20-alpine AS production
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
2. Uso de .dockerignore
Excluir archivos innecesarios del contexto de build:
.dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
*.md
.DS_Store
coverage
.nyc_output
3. Ejecutar con usuario no-root
Por seguridad, evitar usuario root:
FROM node:20-alpine
# Crear usuario no-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
WORKDIR /app
COPY . .
# Cambiar a usuario no-root
USER nodejs
CMD ["node", "index.js"]
4. Agregar health check
HEALTHCHECK \
CMD curl -f http://localhost:3000/health || exit 1
5. Principios de 12 Factor App
The Twelve-Factor App son principios de diseno para aplicaciones cloud-native:
- Configuracion: Gestionar configuracion con variables de entorno
- Dependencias: Declarar explicitamente (package.json)
- Puerto: Exponer servicios mediante binding de puertos
- Procesos: Ejecutar como procesos sin estado
- Logs: Emitir como stream a salida estandar
Comandos Docker de uso frecuente
Operaciones de contenedor
# Ver contenedores en ejecucion
docker ps
# Ver todos los contenedores (incluyendo detenidos)
docker ps -a
# Ejecutar comando dentro del contenedor
docker exec -it container_name sh
# Ver logs del contenedor
docker logs container_name
docker logs -f container_name # Seguir
# Detener contenedor
docker stop container_name
# Eliminar contenedor
docker rm container_name
# Eliminar todos los contenedores detenidos
docker container prune
Operaciones de imagen
# Lista de imagenes
docker images
# Eliminar imagen
docker rmi image_name
# Eliminar imagenes no utilizadas
docker image prune
# Ver historial de imagen
docker history image_name
Limpieza
# Eliminar recursos innecesarios de una vez
docker system prune
# Eliminar incluyendo volumenes (cuidado!)
docker system prune -a --volumes
# Verificar uso de disco
docker system df
Solucion de problemas
El contenedor no inicia
# Verificar logs
docker logs container_name
# Iniciar en modo interactivo para debug
docker run -it my-node-app sh
Puerto en uso
# Verificar puertos en uso
lsof -i :3000
# Mapear a otro puerto
docker run -p 3001:3000 my-node-app
Build lento
- Verificar
.dockerignore - Optimizar orden de capas
- Usar build multi-etapa
Imagen muy grande
# Verificar tamano de imagen
docker images
# Usar imagen base ligera
FROM node:20-alpine # ~100MB (version normal ~900MB)
# Eliminar archivos innecesarios
RUN npm ci --only=production && npm cache clean --force
Proximos pasos
Una vez dominados los fundamentos de Docker, aprende orquestacion:
- Introduccion a Kubernetes → Construir cluster Kubernetes
- Pipeline CI/CD → Construir CI/CD con GitHub Actions
Enlaces de referencia
Documentacion oficial
- Documentacion oficial de Docker - Referencia oficial
- Dockerfile reference - Lista de instrucciones Dockerfile
- Docker Compose file reference - Especificacion Compose
- Docker Hub - Registro de imagenes oficial
Mejores practicas
- Dockerfile best practices - Docker oficial
- The Twelve-Factor App - Principios de diseno para aplicaciones cloud-native
- Docker Security Best Practices - OWASP
Herramientas y recursos
- Docker Desktop - Herramienta de gestion GUI
- Dive - Herramienta de analisis de capas de imagenes Docker
- Hadolint - Herramienta de analisis estatico de Dockerfile
Cheat sheets
- Docker Cheat Sheet - Cheat sheet oficial de Docker
- Docker Compose Cheat Sheet - Lista de comandos frecuentes