Conceptos basicos de Docker
Docker es una plataforma que empaqueta aplicaciones como contenedores, garantizando un comportamiento consistente en cualquier entorno.
Virtualizacion tradicional vs Virtualizacion de contenedores
flowchart TB
subgraph VM["Virtualizacion tradicional (VM)"]
direction TB
AppA1["App A"] --> GuestA["Guest OS"]
AppB1["App B"] --> GuestB["Guest OS"]
AppC1["App C"] --> GuestC["Guest OS"]
GuestA & GuestB & GuestC --> Hypervisor["Hypervisor"]
Hypervisor --> HostOS1["Host OS"]
end
subgraph Container["Virtualizacion de contenedores (Docker)"]
direction TB
AppA2["App A"] --> LibsA["Libs A"]
AppB2["App B"] --> LibsB["Libs B"]
AppC2["App C"] --> LibsC["Libs C"]
LibsA & LibsB & LibsC --> DockerEngine["Docker Engine"]
DockerEngine --> HostOS2["Host OS (compartido)"]
end
Comparacion:
- VM: Cada aplicacion necesita un sistema operativo guest completo (pesado)
- Contenedor: Solo bibliotecas, compartiendo el sistema operativo host (ligero)
Fundamentos de Dockerfile
Aplicacion Node.js
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Instalacion de dependencias (aprovechando cache)
COPY package*.json ./
RUN npm ci --only=production
# Copiar codigo de aplicacion
COPY . .
# Ejecutar con usuario no-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Build multi-etapa
# Dockerfile.multi-stage
# =========================================
# Stage 1: Instalacion de dependencias
# =========================================
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# =========================================
# Stage 2: Build
# =========================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
# Variables de entorno (durante build)
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
RUN npm run build
# =========================================
# Stage 3: Imagen de produccion
# =========================================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Seguridad: Usuario no-root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copiar solo archivos necesarios
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
USER nextjs
EXPOSE 3000
CMD ["node", "dist/server.js"]
Aplicacion Next.js
# Dockerfile.nextjs
FROM node:20-alpine AS base
# Stage 1: Dependencias
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm i --frozen-lockfile
# Stage 2: Build
FROM base AS builder
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
# Deshabilitar telemetria de Next.js
ENV NEXT_TELEMETRY_DISABLED=1
RUN corepack enable pnpm && pnpm run build
# Stage 3: Produccion
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY /app/public ./public
# Usar salida standalone
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
Docker Compose
Entorno de desarrollo
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
adminer:
image: adminer
ports:
- "8080:8080"
depends_on:
- db
volumes:
postgres_data:
redis_data:
Entorno de produccion
# docker-compose.prod.yml
services:
app:
image: myapp:${VERSION:-latest}
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
deploy:
resources:
limits:
memory: 1G
volumes:
postgres_data:
driver: local
Optimizacion de Dockerfile
Puntos de optimizacion de Dockerfile
1. Aprovechamiento del cache de capas
| Metodo | Codigo |
|---|---|
| Mal ejemplo | COPY . . → RUN npm install |
| Buen ejemplo | COPY package*.json ./ → RUN npm ci → COPY . . |
2. Minimizacion del tamano de imagen
| Imagen base | Tamano |
|---|---|
| node:20 | 1.1GB |
| node:20-slim | 250MB |
| node:20-alpine | 140MB |
| Build multi-etapa final | 50-100MB |
3. Seguridad
- Ejecutar con usuario no-root
- No incluir paquetes innecesarios
- No incluir secretos en la imagen
- Uso de .dockerignore
.dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env*
.env.local
.env.*.local
Dockerfile*
docker-compose*
.dockerignore
README.md
.next
.cache
coverage
.nyc_output
*.log
.DS_Store
Health Check
# En la aplicacion
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --only=production
# Endpoint de health check
HEALTHCHECK \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
// server.ts - Endpoint de health check
import express from 'express';
const app = express();
app.get('/health', async (req, res) => {
try {
// Verificar conexion a base de datos
await db.query('SELECT 1');
// Verificar Redis
await redis.ping();
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
checks: {
database: 'ok',
redis: 'ok',
},
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message,
});
}
});
app.get('/ready', (req, res) => {
// Verificar si la aplicacion esta lista
if (appIsReady) {
res.status(200).send('Ready');
} else {
res.status(503).send('Not Ready');
}
});
Gestion de logs
// logger.ts - Logging para Docker
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
// Formato JSON recomendado para entornos Docker
transport:
process.env.NODE_ENV === 'development'
? {
target: 'pino-pretty',
options: { colorize: true },
}
: undefined,
});
// Logs estructurados
logger.info({ userId: 123, action: 'login' }, 'User logged in');
logger.error({ err: error, requestId: 'abc-123' }, 'Request failed');
export default logger;
# docker-compose.yml - Configuracion de logs
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Integracion CI/CD
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Comandos de uso frecuente
# Build de imagen
docker build -t myapp:latest .
# Ejecutar contenedor
docker run -d -p 3000:3000 --name myapp myapp:latest
# Ver logs
docker logs -f myapp
# Entrar al contenedor
docker exec -it myapp sh
# Verificar tamano de imagen
docker images myapp
# Eliminar recursos no utilizados
docker system prune -a
# Operaciones con Docker Compose
docker compose up -d
docker compose down
docker compose logs -f app
docker compose exec app sh
Enlaces de referencia
- Documentacion oficial de Docker
- Mejores practicas de Dockerfile
- Especificacion de Docker Compose
- Guia de Docker para Node.js