La mayoría de los Dockerfiles corren como root, están inflados y no fijan versiones, y como los arreglos son tan mecánicos, la gente pega una reescritura de 'best practices' sin entender qué resolvió. Este es un prompt completo y listo para copiar que reescribe tu Dockerfile a multi-etapa, sin root, con versiones fijadas y con las capas ordenadas para cache, y conecta cada cambio con el riesgo concreto que elimina, además de cómo adaptarlo según el lenguaje, cuáles son los placeholders que cargan el valor, qué variantes vale la pena guardar y el detalle que convierte una salida limpia del chat en un CI en rojo.

En resumen
- El prompt reescribe un Dockerfile a multi-etapa, sin root, con versiones fijadas y las capas ordenadas para cache, y obliga a una justificación de una línea por cada cambio.
- Exige un mapa de riesgo al final: cada cambio queda ligado a lo que elimina (tamaño de imagen, superficie de ataque, reproducibilidad o velocidad de build).
- Dos placeholders cargan el valor: el lenguaje/runtime y las reglas de tu entorno (registry, política de imágenes base, puertos, comando de healthcheck).
- El detalle que más duele: el modelo se inventa un digest de imagen base que no existe. Verifica cada digest fijado contra el registry antes de hacer commit.
- Es un buen primer borrador, no una imagen terminada. Todavía tienes que escanear el resultado y confirmar que el contenedor de verdad arranca sin root.
La mayoría de los Dockerfiles en producción se escribieron una sola vez, con prisa, y nunca se volvieron a tocar. Corren como root, arrastran toda la toolchain de build hasta la imagen de runtime, no fijan ninguna versión y reinstalan todas las dependencias con cada cambio de código porque las capas están en el orden equivocado. Los arreglos para todo esto son conocidos y casi mecánicos, y ahí está justo el problema, porque "mecánico" es precisamente la forma en que una reescritura copiada y pegada cuela un digest sutilmente equivocado o un USER roto en tu build. Este es un prompt completo que hace la reescritura y además obliga al modelo a justificar cada cambio frente al riesgo concreto que elimina, así terminas entendiendo el nuevo Dockerfile en vez de confiar en él a ciegas. Te quedas con un prompt que puedes pegar hoy mismo, los ajustes para adaptarlo a tu lenguaje y tu registry, y una explicación honesta del único punto donde te va a mentir.
La pieza central está más abajo. Úsala tal cual la primera vez. Pega un Dockerfile real, lee el mapa de riesgo que produce, y adáptala solo después de ver cómo se comporta por defecto. Las secciones que vienen después explican por qué está cada instrucción y en qué puntos la salida necesita ojo humano antes de tocar CI.
01 · El prompt
Pégalo como system prompt de un subagente de endurecimiento, o como el mensaje justo antes de tu Dockerfile actual en el chat. Llena primero los dos placeholders entre corchetes. Están documentados en la sección 03.
You are a senior platform engineer hardening a Dockerfile for production. You make
the image smaller, safer, and reproducible, and you justify every change. You do
not add complexity that the project does not need.
## Context
- Language / runtime: [LANGUAGE_AND_RUNTIME]
- House constraints: [HOUSE_CONSTRAINTS]
- I will paste the current Dockerfile after these instructions.
## What to apply, and justify EACH
1. Multi-stage build. Put build dependencies, compilers, and dev tooling in a
builder stage. Copy ONLY the final artifacts (compiled binary, installed
node_modules without dev deps, built assets) into a slim runtime stage. The
runtime stage must not contain a package manager or build tools if avoidable.
2. Non-root runtime. Create a dedicated unprivileged user and group, set file
ownership on what the app needs, and end with a USER directive. The process
must NOT run as root. Do not chown the whole filesystem — only what is needed.
3. Pin the base image by digest (image@sha256:...), not just a tag. Pin OS and
language package versions where the package manager allows it. If you cannot
verify a digest, use a specific minor tag and flag it as needing a real digest.
4. Layer order for cache. Copy and install dependencies BEFORE copying source, so
a code change does not bust the dependency layer. Use lockfiles. Combine
related RUN commands and clean package caches in the same layer.
5. Drop build tooling and secrets from the final image. No build-time secrets baked
into layers; use build args or mounts, never ENV for secrets. Strip caches.
6. Add a HEALTHCHECK that actually tests the app (e.g. hits its health endpoint or
runs a cheap liveness check), not just "process is up".
7. Set a sensible WORKDIR, EXPOSE the real port, and prefer exec-form CMD/ENTRYPOINT
(JSON array) so signals reach the process for clean shutdown.
## Hard rules
- Do NOT invent a base-image digest. If you provide one, label it [VERIFY] so I
know to check it against the registry before committing.
- Do NOT use 'latest' or an unpinned tag in the final image.
- Do NOT run the app as root, and do NOT use sudo.
- Preserve the app's actual behavior: same entrypoint command, same port, same
required runtime files. If a change could alter behavior, say so explicitly.
- If a best practice does not apply to this project, skip it and say why. Do not
add a multi-stage split to an image that has no build step.
## Output, in this order
1. The complete rewritten Dockerfile, ready to drop in.
2. A risk map: a bullet per meaningful change, each in the form
"change -> risk it removes (size | attack surface | reproducibility | build speed)".
3. A short "verify before commit" checklist of anything I must confirm myself
(digests marked [VERIFY], the healthcheck command, the exposed port).
Consejo
Pruébalo una vez sobre un Dockerfile que conozcas bien antes de confiarle uno que no. El mapa de riesgo te dice si el modelo de verdad entendió tu imagen o si solo aplicó una plantilla. Si un "cambio" apunta a un riesgo que ni siquiera existe en tu setup, esa es la señal para leer el resto con desconfianza.
02 · Por qué cada instrucción carga peso
Este prompt es largo porque endurecer contenedores tiene concesiones reales, y una instrucción vaga te devuelve una reescritura vaga. Casi cada línea evita un fallo concreto que ya he visto pasar.
- "Copy ONLY the final artifacts." Todo el sentido del multi-etapa es que el compilador, las dependencias de desarrollo y el cache de paquetes nunca lleguen a producción. Una reescritura floja arma el build multi-etapa y después copia la etapa builder completa hacia adelante, con lo cual no ganas nada. El "solo los artefactos" explícito es lo que hace que la imagen de runtime quede de verdad pequeña.
- "Do not chown the whole filesystem." Una reescritura típica para correr sin root termina con un chown recursivo sobre el directorio de la app o, peor, sobre el root. En una imagen grande eso agrega una capa pesada y puede duplicar el tamaño. Acotar la propiedad a lo que la app necesita evita que el arreglo para correr sin root infle la imagen sin que te des cuenta.
- "Copy and install dependencies BEFORE copying source." La regla del cache, y la que más rinde en el día a día. Si la pones mal, cada cambio de un solo carácter reinstala todas las dependencias y convierte un rebuild de 5 segundos en uno de 3 minutos.
- "Add a HEALTHCHECK that actually tests the app." Un healthcheck que solo verifica que el proceso está vivo reporta "healthy" mientras la app devuelve 500. Un chequeo de liveness real es la diferencia entre atrapar un proceso colgado y tener pura decoración.
- La nota del CMD en exec-form. El CMD en shell-form envuelve tu proceso en /bin/sh -c, así que SIGTERM le llega al shell y no a tu app, y el apagado graceful se rompe sin avisar. Esto aparece durante los rolling deploys, no en desarrollo, y por eso conviene forzarlo en el prompt.
La instrucción que la gente subestima
"If a best practice does not apply to this project, skip it and say why." Sin esta línea, el modelo trata el checklist como una cuota que tiene que cumplir: le mete un split multi-etapa a una imagen de assets estáticos que ni siquiera tiene paso de build, o le agrega un healthcheck a un contenedor de un job de una sola corrida. Dejarlo que se salte algo, y que justifique por qué, mantiene la reescritura a la medida del proyecto en vez de copiada a ciegas.
03 · Los placeholders, y qué poner en ellos
Hay dos placeholders. Cargan casi todo el valor de la adaptación, así que llénalos con cuidado.
- [LANGUAGE_AND_RUNTIME] es el lenguaje, la versión y cómo se construye y se corre la app. Sé lo bastante específico para que el modelo elija la imagen base correcta y el artefacto correcto a copiar. Para una app de Node: Node.js 20, pnpm, Next.js con output standalone, corre como servidor de larga duración. Para Go: Go 1.22, binario estático, sin CGO, un solo binario como entrypoint. Esto define si la etapa de runtime puede ser distroless, alpine o un Debian slim, y si de plano necesitas una etapa builder.
- [HOUSE_CONSTRAINTS] son las reglas que impone tu entorno. El registry de donde bajas las imágenes base, cualquier política de imágenes aprobadas, el puerto real, el endpoint de health y las rarezas de la plataforma. Para un proyecto de Ilustrari podría decir: un solo VPS detrás de Traefik; la app escucha en 3000; el endpoint de health es /api/health; preferir distroless o alpine; bajar las bases de Docker Hub; la imagen va a correr bajo un servicio de compose sin root.
Importante
Si dejas [HOUSE_CONSTRAINTS] vacío, el modelo adivina tu puerto y tu healthcheck, y muchas veces adivina mal. Un healthcheck que pega al path equivocado reporta unhealthy para siempre y tu orquestador puede negarse a enrutar tráfico. Dale siempre el puerto real y el endpoint de health. Es la edición más barata, pero la que más daño hace si te la saltas.
Un ejemplo ya armado del par de placeholders, listo para usar:
[LANGUAGE_AND_RUNTIME]: Node.js 20 with pnpm. Next.js 14 built with output:
"standalone". Runtime needs the .next/standalone server, .next/static, and public.
No build tools at runtime. Long-lived HTTP server.
[HOUSE_CONSTRAINTS]: Single VPS behind Traefik. App listens on 3000. Health
endpoint: GET /api/health returns 200 when ready. Prefer a slim Node base or
distroless/nodejs. Bases pulled from Docker Hub. Container runs as a non-root
service in docker-compose; do not assume root at runtime.
04 · Variantes que vale la pena guardar
El prompt base es el default correcto. Unas pocas variantes puntuales se ganan su lugar. No armes más de las que vayas a usar de verdad.
Pasada solo distroless
Cuando la imagen corre un binario compilado o una app de Node que no necesita shell en runtime, agrega: Use a distroless runtime base. There is no shell and no package manager in the final image; the HEALTHCHECK must run as an exec-form command using only the app binary or a built-in, not curl or sh. Distroless recorta la superficie de ataque en serio, pero cambia la forma en que escribes el healthcheck y te quita el debug por shell, así que se merece su propia variante en vez de quedar como una nota al pie.
Solo auditoría, sin reescribir
A veces no quieres una reescritura. Quieres saber qué está mal antes de decidir. Reemplaza la sección de salida con: Do not rewrite the file. Produce a findings list ranked HIGH / MEDIUM / LOW, each with the offending line, the risk, and the one-line fix. End with whether this Dockerfile is safe to ship as-is. Esto combina bien con la revisión de código: corre la variante de auditoría en un comentario del PR y haz la reescritura solo en los archivos que no pasan.
Modo diff mínimo
Una reescritura completa puede ser demasiado disruptiva en un Dockerfile del que depende más gente. Agrega: Make the smallest set of changes that closes the HIGH risks (non-root, no secrets in layers, pinned base). Do not restructure layers or switch base images unless required to close a HIGH risk. Output a unified diff, not a full file. Sacrificas algo de optimización a cambio de un cambio lo bastante pequeño como para revisarlo y fusionarlo hoy mismo.
05 · Detalles que deciden si puedes confiar en él
- El digest inventado. El más grande de todos. El modelo va a fijar con total confianza una imagen base a un digest sha256 que no existe, que es de la arquitectura equivocada o que ya está viejo. Funcionó en el chat porque nadie lo descargó; falla en CI en el instante en que algo lo intenta. Por eso el prompt fuerza una etiqueta [VERIFY] en cada digest. Corre docker buildx imagetools inspect contra el tag y pega el digest real antes de hacer commit.
- Distroless y Alpine rompen cosas de formas distintas. Alpine usa musl en lugar de glibc, lo que puede romper módulos nativos con errores de runtime confusos que nunca se vieron sobre una base Debian. Distroless no trae shell, así que el debug con docker exec ... sh se acabó. Si el modelo te cambia la base, trátalo como un cambio que hay que probar, no como una victoria gratis.
- Sin root y permisos de volúmenes. Un USER sin root muchas veces saca a la luz un error de permisos al día siguiente, cuando la app escribe en un volumen montado o en un directorio de cache que es de root. El arreglo entra dentro del alcance (hazte dueño de los directorios donde la app escribe), pero confírmalo contra tus mounts reales. El modelo no ve los volúmenes de tu compose.
- No ve tu build context. El modelo reescribe el Dockerfile pero no tiene idea de qué deja pasar tu .dockerignore. Una imagen "pequeña" igual puede empaquetar tu directorio .git o tus archivos de env locales si el context está sucio. Endurecer el Dockerfile e ignorar bien el context son las dos mitades de un mismo trabajo.
- Escanea el resultado, no te quedes solo con leerlo. Una reescritura que se ve limpia igual puede heredar CVEs conocidos de la imagen base. Corre un escáner (Trivy, Grype o docker scout) sobre la imagen ya construida. El prompt reduce la superficie que controlas; el escáner atrapa la que heredaste.
Atención
No hagas commit de un Dockerfile generado sin construirlo y confirmar que el contenedor arranca como el usuario sin root y pasa su healthcheck. Un digest que no resuelve, un chown que se saltó un directorio escribible o un CMD en shell-form que se traga el SIGTERM pasan una revisión de código y fallan en producción. Constrúyelo, córrelo y revisa el usuario con "docker exec <c> whoami" antes de que salga a producción.
Un buen prompt de endurecimiento no convierte al modelo en mejor ingeniero. Lo convierte en uno disciplinado que muestra su trabajo. El split multi-etapa, el usuario sin root y el orden de capas para cache son todas jugadas conocidas. El valor está en el mapa de riesgo que ata cada una a lo que elimina, para que puedas distinguir un cambio con sentido de un puro reflejo. Pégalo, llena el lenguaje y las reglas de tu entorno, córrelo sobre un Dockerfile que entiendas y verifica cada digest antes de que toque CI. La imagen en la que confías es la que construiste y viste arrancar sin root, no la que se leía bien en el chat.
Puntos clave
- El valor del prompt no es la reescritura. Es el mapa de riesgo que ata cada cambio a lo que elimina, para que entiendas la imagen en vez de confiar en ella a ciegas.
- Llena los dos placeholders: el lenguaje/runtime elige la imagen base y los artefactos correctos, y las reglas de tu entorno fijan el puerto real, el healthcheck y la política de imágenes base.
- Verifica siempre cada digest de imagen base contra el registry. Ese sha256 que el modelo da con tanta confianza es el fallo que pasa en el chat y rompe en CI.
- Guarda las variantes de solo auditoría y de diff mínimo para los Dockerfiles que no puedes reescribir del todo, y la de distroless para cuando la imagen no necesita shell.
- Endurece la superficie que controlas. Aun así, constrúyelo, confirma que arranca sin root y pasa su healthcheck, y corre un escáner para los CVEs que heredas.
Preguntas frecuentes
¿Por qué el cuerpo del prompt está en inglés si yo trabajo en español?
Deja el set de instrucciones en inglés. Es el idioma en el que el modelo sigue mejor el razonamiento de infraestructura, y de todos modos las directivas de Docker son keywords en inglés. Aun así puedes recibir el mapa de riesgo y el checklist en español: agrega una línea, 'Write the risk map and checklist in Spanish, keep Dockerfile keywords and command names in English.' Así te llegan las explicaciones en español con los términos técnicos intactos.
¿Qué es lo único que siempre debo verificar en la salida?
Cada digest de imagen base. El modelo va a fijar con total confianza un sha256 que no existe, que ya está viejo o que es de la arquitectura equivocada, y va a construir bien en el chat pero romper en CI. Por eso el prompt fuerza la etiqueta [VERIFY]. Corre 'docker buildx imagetools inspect' contra el tag y pega el digest real antes de hacer commit. Por defecto, da por no verificado cualquier digest que produzca el modelo.
¿Va a achicar la imagen solo por agregar un build multi-etapa?
Por sí solo no. Un build multi-etapa solo ayuda si la etapa final copia únicamente los artefactos, no el builder completo. Una reescritura floja parte en etapas y después copia todo hacia adelante, lo que no ahorra nada. Por eso el prompt insiste en 'copy ONLY the final artifacts' y pide una entrada de tamaño en el mapa de riesgo, así ves si el split de verdad te dejó una imagen de runtime más pequeña o solo la reacomodó.
Mi contenedor funcionaba como root pero ahora falla sin root. ¿Qué pasó?
Casi siempre es un tema de permisos en algo donde la app escribe: un volumen montado, un directorio de cache o de tmp, o una ruta de logs que sigue siendo de root. El prompt acota la propiedad a lo que la app necesita, pero no ve los volúmenes de tu compose, así que confirma que los directorios donde la app escribe en runtime pertenezcan al usuario sin root. Reprodúcelo construyendo, corriendo y revisando 'docker exec <c> whoami', y probando además una escritura en cada ruta que la app toca.
¿Debo cambiar a Alpine o distroless solo porque el modelo lo sugirió?
Solo después de probarlo. Alpine usa musl en lugar de glibc y puede romper módulos nativos con errores que no verás hasta runtime; distroless quita el shell, así que tu debug de siempre con 'docker exec sh' desaparece. Los dos recortan la superficie de ataque de verdad, pero un cambio de base es un cambio de comportamiento que hay que verificar, no una victoria gratis. Si quieres la imagen más pequeña sin la sorpresa, usa el modo diff mínimo y quédate con tu base actual por ahora.
¿Esto reemplaza a un escáner de imágenes como Trivy o Docker Scout?
No, cubren superficies distintas. El prompt endurece la superficie que controlas: cómo construyes, como qué usuario corres, qué copias adentro, cómo cachean las capas. Un escáner encuentra los CVEs conocidos que heredas de la imagen base y de tus dependencias, que el prompt no ve. Usa los dos: endurece el Dockerfile con el prompt y después escanea la imagen ya construida, y pon el candado de CI en el escáner, no en la palabra del modelo.
¿Prefieres que lo hagamos por ti?
Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.
Escríbenos por WhatsAppPrimera conversación gratis. Te responde el fundador.
Recursos relacionados

El skill Dockerfile Author: imágenes pequeñas, cacheadas y sin root por defecto
Un skill de Claude Code que escribe un Dockerfile multi-stage pequeño, que aprovecha la caché y corre como usuario non-root con un healthcheck de verdad, para que un cambio de código no reinstale cada paquete y un escape de contenedor no le regale root a un atacante.

MCP de Docker para operar contenedores empezando por lectura
Dale a Claude visibilidad sobre tu stack de contenedores (listar servicios, leer logs, inspeccionar redes) para que diagnosticar un incidente deje de ser tú entrando por SSH a forzar la vista mirando la salida. El detalle es que el socket de Docker equivale a root en el host, así que esta guía habilita primero las tools de lectura y deja cada acción destructiva del ciclo de vida detrás de tu aprobación explícita.

Rota un secreto filtrado con env y Docker, en el orden seguro y sin downtime
Que se filtre una API key es algo de rutina, no una catástrofe, siempre que hayas dejado la rotación lista antes de necesitarla. Este es el recorrido completo que sigo en cada contenedor: dónde van de verdad los secretos, el orden emitir-desplegar-verificar-revocar que nunca tumba el tráfico en producción, las trampas de Docker que dejan tus claves grabadas en la imagen para siempre, y un ejemplo paso a paso que puedes copiar. Terminas sabiendo rotar bajo presión sin tener que adivinar.