PureTools

Dockerfile: Buenas Prácticas para Builds Más Pequeños y Rápidos

PureTools Team· 9 min de lectura
Dockerfile: Buenas Prácticas para Builds Más Pequeños y Rápidos

Dockerfile: De 1.2GB a 80MB

Un Dockerfile ingenuo para una app Node.js produce una imagen de 1.2GB. Uno bien escrito produce 80MB. La diferencia es entender cómo funcionan las capas de Docker y aplicar unos patrones consistentemente.

El Dockerfile Ingenuo

# NO hagas esto
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

Problemas: imagen Node.js completa (1GB+), copia node_modules si existen localmente, instala devDependencies, sin caché de capas, corre como root.

El Dockerfile de Producción

# Stage 1: Instalar dependencias
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 3: Imagen de producción
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# No corras como root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]

Optimizaciones Clave Explicadas

TécnicaImpactoPor Qué
node:20-alpine~900MB más pequeñoAlpine Linux tiene ~5MB vs ~120MB de Debian
Multi-stage buildSin herramientas de build en imagen finalSolo artefactos de producción se copian al stage final
npm ci vs npm installBuilds determinísticosUsa lockfile exactamente, más rápido, sin sorpresas
Copiar package.json primeroMejor caché de capasDependencias solo se reinstalan cuando package.json cambia
USER appuserSeguridadContainer no corre como root
--only=productionnode_modules más pequeñoSin devDependencies en producción

Caché de Capas: El Orden Importa

Docker cachea cada capa. Cuando una capa cambia, todas las subsecuentes se reconstruyen. Así que pon cosas que cambian raramente arriba:

# Raramente cambia → cache hit
FROM node:20-alpine
WORKDIR /app

# Cambia cuando cambian las dependencias
COPY package.json package-lock.json ./
RUN npm ci

# Cambia con cada cambio de código → invalida solo esta capa
COPY . .

.dockerignore

Tan importante como .gitignore:

node_modules
.git
.env
*.md
Dockerfile
docker-compose.yml
.next
coverage
tests
.vscode
.DS_Store

Sin esto, COPY . . envía todo tu node_modules (e historial git) al daemon Docker, haciendo builds lentos antes siquiera de empezar.

Dockerfile Python

# Build multi-stage Python
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .

RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

Dockerfile Go (Imagen Final Diminuta)

# Go compila a binario estático — imagen final puede ser scratch
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .

FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# Imagen final: ~10-20MB

Checklist de Seguridad

  • Nunca corras como root — siempre crea y usa un usuario no-root
  • No almacenes secretos en la imagen — usa variables de entorno o secret mounts
  • Fija versiones de imagen — node:20.11.1-alpine no node:latest
  • Escanea vulnerabilidades — docker scout quickview o trivy image myapp
  • Usa .dockerignore para excluir .env, .git y archivos sensibles

Referencia Docker: Cheatsheet Docker — todos los comandos que necesitas, categorizados y buscables.