El problema: «funcionaba en mi máquina»#
En DevOps, uno de los mayores retos es que una aplicación se comporte igual en todos los entornos: local, staging, producción, on-premise y nube. Las diferencias en librerías, versiones del sistema operativo o configuración rompen despliegues que funcionaban en la máquina de quien los escribió.
Los contenedores atacan ese problema de raíz, y el Dockerfile es la pieza que lo hace posible: una receta versionada que describe exactamente con qué se construye y cómo arranca tu aplicación.
En esta guía verás qué es un Dockerfile, por qué es especialmente valioso para apps externas y servicios propios, cómo Docker aporta consistencia en todo el ciclo de vida y las buenas prácticas que marcan la diferencia en producción, con ejemplos que puedes copiar.
Documentación: Docker · Docker overview ↗
Qué es un Dockerfile y por qué importa#
Un Dockerfile es un archivo de texto con instrucciones que Docker ejecuta para construir una imagen. En él defines:
- La imagen base: Alpine, Debian, Ubuntu, distroless o la imagen oficial de tu lenguaje.
- Las versiones del runtime (Node, Python, Java, Go).
- Los paquetes y librerías del sistema que necesitas.
- Los archivos de la aplicación que se copian.
- Variables de entorno con valores por defecto no sensibles.
- El usuario con el que corre y el comando de arranque.
El resultado es una imagen: un artefacto con formato estándar OCI que encapsula todo lo que la aplicación necesita para ejecutarse, salvo el kernel, que comparte con el host. Esa imagen se puede versionar, escanear, guardar en un registry y ejecutar en cualquier plataforma compatible.
En otras palabras: tu aplicación deja de depender de cómo está configurado cada servidor y pasa a depender de una definición explícita y revisable en un pull request.
Documentación: Docker · Dockerfile reference ↗ · Docker · Docker overview ↗ · Open Container Initiative · Image spec ↗
El valor para apps externas: menos sorpresas, más portabilidad#
Con aplicaciones externas o servicios personalizados (APIs propias, workers batch, integraciones, ETLs, dashboards internos, scrapers, microservicios pequeños) el problema se amplifica, porque suelen traer:
- Dependencias difíciles o muy específicas.
- Binarios o runtimes poco comunes.
- Versiones distintas de Python, Node o Java conviviendo en la misma organización.
- Librerías del sistema que hay que instalar a mano.
- Configuración repartida entre ambientes.
Empaquetar cada una en su propia imagen resuelve eso: la app corre igual en AWS, Google Cloud, Azure, Kubernetes u on-premise, no depende de lo que haya instalado en el servidor, cada cambio queda versionado y auditable, puedes replicar el entorno exacto en QA y un cambio en el host no la rompe. Casos típicos: microservicios fuera del core, cron jobs y tareas batch, conectores con terceros, herramientas internas y aplicaciones legacy que quieres mover sin reescribir.
Documentación: Docker · Docker overview ↗
1. Builds reproducibles (y qué significa de verdad)#
La versión original de este artículo afirmaba que dos personas que construyen el mismo Dockerfile obtienen una imagen idéntica. No es exactamente así: una etiqueta como node:22-alpine se actualiza con el tiempo, los paquetes del sistema cambian y las marcas de tiempo varían. Lo que sí consigues es que el entorno quede definido de forma explícita; para acercarte a builds reproducibles tienes que fijar lo que puede moverse.
# syntax=docker/dockerfile:1
# Fija la etiqueta (o el digest @sha256:...) de la imagen base
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# La imagen oficial de Node incluye el usuario sin privilegios node
USER node
EXPOSE 3000
CMD ["node", "src/index.js"]- Fija la imagen base con una etiqueta concreta y, si necesitas máxima estabilidad, con su digest.
- Instala dependencias desde el lockfile con npm ci o el equivalente de tu lenguaje.
- Usa un .dockerignore para que node_modules locales, .git o archivos .env no entren al contexto de build.
- Copia los manifiestos de dependencias antes que el código, para aprovechar la caché de capas.
# .dockerignore
.git
node_modules
.env
.env.*
!.env.example
dist
coverage
*.logDocumentación: Docker · Pin base image versions ↗ · Docker · Building best practices ↗ · Docker · .dockerignore files ↗ · Docker · Reproducible builds ↗ · Node.js Docker image · Best practices ↗
2. Imágenes inmutables#
Una imagen publicada no cambia: sus capas se identifican por contenido y la imagen por su digest. Ese es el principio de la infraestructura inmutable: en lugar de entrar al servidor a parchear, construyes una imagen nueva y reemplazas los contenedores.
Evitas así ambientes contaminados por cambios manuales, dependencias instaladas a mano que nadie documentó y configuraciones ocultas. Ojo: el sistema de archivos de un contenedor en ejecución sí puede modificarse; lo inmutable es la imagen. Si algo cambia dentro de un contenedor, se pierde cuando se reemplaza, y así debe ser.
La metodología Twelve-Factor lo resume en separar build, release y run: el build produce el artefacto una sola vez, el release lo combina con la configuración de un entorno y el run lo ejecuta.
Documentación: Open Container Initiative · Image spec ↗ · The Twelve-Factor App · Build, release, run ↗
3. Portabilidad, con un matiz de arquitectura#
La misma imagen corre en Docker en tu estación de trabajo, en Kubernetes, en Amazon ECS o EKS, en Google Cloud Run, en Azure Container Apps o AKS y en servidores propios. Esa es la promesa de build once, run anywhere, y en gran medida se cumple porque todas esas plataformas aceptan imágenes con formato OCI.
El matiz: una imagen se construye para una arquitectura de CPU. Una imagen solo arm64, construida en un Mac con Apple Silicon, no arranca en un servidor x86_64, y el contrato de Cloud Run, por ejemplo, exige ejecutables Linux x86_64. Si despliegas en varias arquitecturas, como Raspberry Pi o instancias Graviton junto con x86, publica una imagen multiplataforma.
# Versión semántica inmutable y dos arquitecturas en un solo manifiesto
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.example.com/my-app:1.3.2 \
--push .
# Muestra el digest y las plataformas publicadas
docker buildx imagetools inspect registry.example.com/my-app:1.3.2Documentación: Docker · Multi-platform builds ↗ · Google Cloud · Cloud Run container runtime contract ↗ · AWS · What is Amazon ECS ↗ · Microsoft · Azure Container Apps overview ↗
4. Integración natural con CI/CD#
Como la imagen es un artefacto, el pipeline puede tratarla como cualquier otro:
- Construirla una sola vez por commit.
- Escanear vulnerabilidades en sus paquetes.
- Etiquetarla con una versión, por ejemplo 1.3.2, o con el SHA del commit.
- Subirla a un registry (Amazon ECR, Google Artifact Registry, Azure Container Registry, Docker Hub).
- Promover exactamente esa imagen de staging a producción y desplegarla automáticamente.
La clave es promover la misma imagen entre entornos en lugar de reconstruirla en cada uno. Así lo que probaste en staging es, bit a bit, lo que llega a producción.
Documentación: The Twelve-Factor App · Build, release, run ↗ · Kubernetes · Images ↗
Buenas prácticas: imágenes mínimas y multi-stage#
Usa imágenes base pequeñas (Alpine, variantes slim o distroless): menos paquetes significan menos superficie de ataque y menos parches pendientes. Ten en cuenta que Alpine usa musl en lugar de glibc, lo que puede afectar a binarios nativos; si tienes problemas, una variante slim de Debian suele ser la alternativa.
Con un build multi-stage compilas en una imagen completa y copias solo el resultado a una imagen final mínima. Las herramientas de compilación no llegan a producción:
FROM golang:1.23 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]Documentación: Docker · Multi-stage builds ↗ · Google · distroless images ↗ · Docker · Building best practices ↗
Buenas prácticas: la configuración y los secrets, fuera de la imagen#
La imagen debe contener el código y sus dependencias, no la configuración de un entorno. Eso significa:
- No copiar archivos .env dentro de la imagen.
- No fijar valores sensibles con ENV ni ARG en el Dockerfile: quedan visibles en el historial de la imagen.
- Inyectar la configuración en runtime con variables de entorno, gestores de secrets o ConfigMaps y Secrets de Kubernetes.
Si el build necesita un secret, como un token para un registry privado de paquetes, usa los build secrets de BuildKit, que se montan solo durante ese paso:
# Dockerfile
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# El token no queda en ninguna capa de la imagen
docker build --secret id=npmrc,src=$HOME/.npmrc -t my-app:1.3.2 .Así la misma imagen sirve para dev, QA, staging y producción, y solo cambia la configuración externa.
Documentación: Docker · Build secrets ↗ · Kubernetes · ConfigMaps ↗ · Kubernetes · Secrets ↗ · The Twelve-Factor App · Config ↗
Buenas prácticas: no ejecutar como root#
Si no indicas otra cosa, el proceso del contenedor corre como root. Si alguien compromete la aplicación, tendrá más margen para dañar el contenedor y, ante una vulnerabilidad de escape, el host. Crea un usuario sin privilegios y cámbiate a él con USER:
# Alpine (BusyBox)
RUN addgroup -S app && adduser -S app -G app
USER app
# Debian / Ubuntu
RUN groupadd --system app && useradd --system --gid app app
USER appMuchas imágenes oficiales ya traen un usuario preparado, como node en la imagen de Node o nonroot en distroless. Esta práctica reduce el impacto de una vulnerabilidad en tus dependencias y se alinea con las guías de hardening de contenedores habituales.
Documentación: Docker · USER instruction ↗ · Docker · Docker Engine security ↗ · Node.js Docker image · Best practices ↗
Buenas prácticas: versiona cada build y define la salud#
Las etiquetas predecibles facilitan el rollback: my-app:1.3.2 siempre apunta a lo mismo, mientras que latest cambia con cada publicación. Kubernetes recomienda no usar latest en producción porque dificulta saber qué versión corre y volver atrás; usa tags de versión o, mejor aún, despliega por digest.
La salud del contenedor la define sobre todo la plataforma. Docker tiene la instrucción HEALTHCHECK, pero Kubernetes la ignora y usa sus propias probes de liveness, readiness y startup; ECS y Cloud Run también tienen su configuración. Expón un endpoint de salud en tu app y configúralo donde corresponda.
Documentación: Kubernetes · Images ↗ · Docker · HEALTHCHECK instruction ↗ · Kubernetes · Liveness, readiness and startup probes ↗
Por qué las nubes adoptaron los contenedores#
AWS, Google Cloud y Azure ofrecen servicios gestionados cuya unidad de despliegue es una imagen de contenedor. Las razones son prácticas:
| Propiedad | Qué aporta |
|---|---|
| Arranque rápido comparado con una VM | Escalado horizontal más ágil; el tiempo real depende del tamaño de la imagen y de la app |
| Aislamiento por diseño | Cada servicio con sus dependencias, sin conflictos entre ellos |
| Reemplazo en lugar de parcheo | Despliegues progresivos y rollbacks sin downtime si la plataforma y la app lo soportan |
| Formato estándar OCI | La misma imagen sirve en distintos proveedores |
En pocas palabras: la imagen de contenedor se ha convertido en la unidad básica del despliegue moderno.
Documentación: AWS · What is Amazon ECS ↗ · Google Cloud · Cloud Run container runtime contract ↗ · Microsoft · Azure Container Apps overview ↗
Checklist para un Dockerfile de producción#
- Imagen base fijada por etiqueta concreta o digest.
- Dependencias instaladas desde el lockfile.
- .dockerignore que excluye .git, node_modules y .env.
- Build multi-stage: sin compiladores en la imagen final.
- Ni configuración ni secrets dentro de la imagen; build secrets para lo que necesite el build.
- Proceso ejecutándose con un usuario no root.
- Tag de versión inmutable, arquitectura correcta y la misma imagen promovida entre entornos.
Documentación: Docker · Building best practices ↗
Fuentes y alcance
Documentación consultada el 25 de septiembre de 2026. Los ejemplos y criterios de decisión son propuestas editoriales; adáptalos al contrato de tu aplicación y valídalos en tu entorno de pruebas autorizado.
- Docker · Docker overview ↗
- Docker · Dockerfile reference ↗
- Open Container Initiative · Image spec ↗
- Docker · Pin base image versions ↗
- Docker · Building best practices ↗
- Docker · .dockerignore files ↗
- Docker · Reproducible builds ↗
- Node.js Docker image · Best practices ↗
- The Twelve-Factor App · Build, release, run ↗
- Docker · Multi-platform builds ↗
- Google Cloud · Cloud Run container runtime contract ↗
- AWS · What is Amazon ECS ↗
- Microsoft · Azure Container Apps overview ↗
- Kubernetes · Images ↗
- Docker · Multi-stage builds ↗
- Google · distroless images ↗
- Docker · Build secrets ↗
- Kubernetes · ConfigMaps ↗
- Kubernetes · Secrets ↗
- The Twelve-Factor App · Config ↗
- Docker · USER instruction ↗
- Docker · Docker Engine security ↗
- Docker · HEALTHCHECK instruction ↗
- Kubernetes · Liveness, readiness and startup probes ↗
Compara soluciones cloud
Revisa tarifas, límites, condiciones y fuentes de cada opción.
Abrir comparador