Creación de Dockerfiles compactos y entrypoints de shell
Escriba scripts de compilación multietapa y entrypoints robustos con gestión de señales y plantillas de configuración.
Creación de Dockerfiles compactos y entrypoints de shell es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.
Por qué son importantes los Dockerfiles ligeros en DevOps
En entornos de producción, cada megabyte de una imagen de Docker tiene un coste: descargas más lentas, una mayor superficie de ataque y espacio de almacenamiento desperdiciado en el registro. Los Dockerfiles ligeros, combinados con entrypoints de shell robustos, son una característica distintiva de una práctica madura de DevOps.
- Las compilaciones multietapa separan las herramientas necesarias durante la compilación de la imagen final de ejecución, lo que reduce drásticamente su tamaño.
- Los shims de entrypoint son pequeños scripts de shell que inicializan los contenedores: generan archivos de configuración a partir de plantillas, validan variables de entorno, gestionan señales y, finalmente, ejecutan el proceso principal mediante exec.
- Juntos constituyen la base de cargas de trabajo de contenedores fiables y portátiles en Kubernetes, ECS y entornos bare metal.
En esta lección se abordan ambas disciplinas de principio a fin, con patrones de calidad de producción que puede incorporar directamente a sus pipelines.
Anatomía de un Dockerfile multietapa
Un Dockerfile multietapa utiliza varias instrucciones FROM. Cada etapa es un conjunto de capas aislado; solo se copian a la etapa siguiente los artefactos que se necesitan.
- Etapa 0 (builder): instala compiladores, ejecutores de pruebas y dependencias de compilación.
- Etapa 1 (runtime): parte de una base mínima (por ejemplo,
alpineodistroless) y copia únicamente los binarios compilados o los paquetes de la aplicación. - La imagen final nunca contiene
gcc,makeni el código fuente, a menos que se copien explícitamente.
Use --from=<stage> en COPY para traer archivos a través de los límites entre etapas. Asigne nombres a las etapas con AS <name> para mejorar la legibilidad y seleccionar etapas concretas mediante docker build --target.
# ---- Stage 0: builder ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /app/server ./cmd/server
# ---- Stage 1: runtime ----
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]Minimización de capas e invalidación de la caché
Cada instrucción RUN, COPY y ADD crea una capa nueva. Ordenar mal las instrucciones invalida innecesariamente la caché de compilación y ralentiza los pipelines de CI.
- Copie los manifiestos de dependencias (
package.json,go.mod,requirements.txt) antes de copiar el código fuente, para que la instalación de dependencias se almacene en caché de forma independiente. - Encadene los comandos relacionados con
&&y realice la limpieza en la misma capaRUNpara evitar dejar la caché de paquetes en capas intermedias. - Use
--no-cacheen los gestores de paquetes y elimine los archivos de listas después de la instalación.
FROM python:3.12-slim AS builder
WORKDIR /app
# 1. Install deps first (cached until requirements change)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 2. Copy source (cache busted only when code changes)
COPY src/ ./src/
FROM python:3.12-slim
COPY --from=builder /install /usr/local
COPY --from=builder /app/src /app/src
WORKDIR /app
CMD ["python", "-m", "src.main"]Secretos de BuildKit y reenvío de SSH
Los registros privados, las claves SSH y los tokens de API nunca deben aparecer en las capas de una imagen. Docker BuildKit proporciona dos mecanismos seguros:
--secret: monta un archivo secreto dentro de un único pasoRUNsin incorporarlo a una capa. Se accede a él mediante/run/secrets/<id>.--ssh: reenvía el socket del agente SSH del host a la compilación para quegit clonepueda autenticarse sin incrustar claves privadas.
Active BuildKit con DOCKER_BUILDKIT=1 o mediante docker buildx build. La directiva # syntax=docker/dockerfile:1 habilita estas funciones.
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
# Mount NPM token as a secret — never stored in the image
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm ci --prefer-offline
# Usage at build time:
# DOCKER_BUILDKIT=1 docker build \
# --secret id=npm_token,src=~/.npmrc-token \
# -t myapp:latest .Cómo escribir un shim de entrypoint robusto
El shim de entrypoint es un script de shell configurado como ENTRYPOINT en el Dockerfile. Su función es preparar el entorno de ejecución antes de ceder el control al proceso principal.
Un shim bien estructurado sigue este orden:
- Paso 1: Establezca
set -euo pipefailpara que cualquier error interrumpa pronto la ejecución. - Paso 2: Valide las variables de entorno obligatorias y termine rápidamente mostrando un mensaje útil si falta alguna.
- Paso 3: Genere archivos de configuración a partir de plantillas usando variables de entorno.
- Paso 4: Registre controladores de señales para permitir un apagado ordenado.
- Paso 5:
exec "$@"— sustituya el shell por el proceso principal para que PID 1 sea la aplicación y no el shim.
El exec final es fundamental: sin él, las señales enviadas por Kubernetes o por el runtime de Docker no se reenvían al proceso hijo.
#!/usr/bin/env bash
set -euo pipefail
# Step 2: validate required env vars
REQUIRED_VARS=(DATABASE_URL APP_SECRET PORT)
for var in "${REQUIRED_VARS[@]}"; do
if [[ -z "${!var:-}" ]]; then
echo "[entrypoint] ERROR: required env var '$var' is not set" >&2
exit 1
fi
done
# Step 3: template config (see next scene)
# Step 4: signal handling (see scene after that)
# Step 5: hand off to CMD
exec "$@"Generación de plantillas de configuración con envsubst
envsubst (del paquete GNU gettext, disponible en Alpine como gettext) sustituye los marcadores ${VAR} de un archivo de plantilla por los valores actuales de sus variables de entorno.
- Incluya en la imagen una plantilla de configuración
*.tmpl; el entrypoint la genera al iniciarse. - Pase explícitamente la lista de variables a
envsubstpara que no expanda accidentalmente signos de dólar no relacionados (por ejemplo, en una expresión regular de nginx). - Escriba el archivo generado en una ruta con permisos de escritura, como
/tmpo un volumen de configuración dedicado.
#!/usr/bin/env bash
# Template: /etc/nginx/conf.d/app.conf.tmpl contains:
# server { listen ${NGINX_PORT}; server_name ${SERVER_NAME}; ... }
export NGINX_PORT=${NGINX_PORT:-8080}
export SERVER_NAME=${SERVER_NAME:-localhost}
envsubst '${NGINX_PORT} ${SERVER_NAME}' \
< /etc/nginx/conf.d/app.conf.tmpl \
> /etc/nginx/conf.d/app.conf
echo "[entrypoint] nginx config rendered:"
grep -E 'listen|server_name' /etc/nginx/conf.d/app.conf
exec "$@"Gestión de señales y apagado ordenado
Los contenedores reciben SIGTERM cuando Kubernetes, ECS o docker stop los detienen. Si su shim de entrypoint es PID 1 y no reenvía las señales, el proceso principal termina con SIGKILL una vez transcurrido el periodo de gracia, lo que puede provocar la pérdida de solicitudes o la corrupción de datos.
- Use
trappara capturarSIGTERMySIGINTen el shim. - Reenvíe la señal al PID hijo mediante
kill -TERM "$child". - Use
wait "$child"para bloquear la ejecución hasta que termine el proceso hijo y, después, propagar su código de salida. - Como alternativa, use
execpara sustituir completamente el shell; así, el sistema operativo entrega las señales directamente al proceso hijo y no se necesita ningún trap. Este es el patrón recomendado para los casos sencillos.
#!/usr/bin/env bash
set -euo pipefail
# Start main process in background
"$@" &
child=$!
# Forward SIGTERM and SIGINT to the child
trap 'echo "[entrypoint] caught SIGTERM, forwarding..."; kill -TERM "$child"' TERM
trap 'echo "[entrypoint] caught SIGINT, forwarding..."; kill -INT "$child"' INT
# Wait for child to exit and capture its exit code
wait "$child"
exit $?Uso de tini como proceso init mínimo
Cuando el contenedor crea procesos hijos (por ejemplo, un shell que genera workers), necesita un init real que recoja los procesos zombis. tini es un binario init pequeño diseñado específicamente para contenedores.
- Añada
tinia la imagen y configúrelo como wrapper del entrypoint. - Recoge los procesos hijos zombis, reenvía correctamente las señales y termina con el código de estado del proceso hijo.
- Docker incluye un tini integrado que se activa con
docker run --init, pero incorporarlo a la imagen garantiza un comportamiento coherente entre runtimes (Kubernetes, ECS, etc.).
FROM node:20-alpine
# Install tini for proper signal handling and zombie reaping
RUN apk add --no-cache tini
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
# tini wraps CMD; forwards SIGTERM and reaps zombies
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]Ejecución como usuario no root
Los contenedores que se ejecutan como root (UID 0) suponen un riesgo de seguridad importante: una salida del contenedor permite obtener acceso total al host. Cambie siempre a un usuario sin privilegios antes de ejecutar el proceso principal.
- Cree un usuario y un grupo de sistema dedicados en el Dockerfile con
addgroup/adduser(Alpine) ogroupadd/useradd(Debian). - Cambie el propietario de los archivos de la aplicación con
COPY --chown=appuser:appgroup; es más eficiente que una capaRUN chownindependiente. - Cambie al usuario mediante la instrucción
USER. El entrypoint y CMD heredarán este usuario. securityContext.runAsNonRoot: truede Kubernetes rechazará iniciar una imagen que todavía se ejecute como root.
FROM python:3.12-slim
# Create non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app files with correct ownership in a single layer
COPY --chown=appuser:appgroup src/ ./src/
COPY --chown=appuser:appgroup entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER appuser
ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "-m", "src.main"]Comprobaciones de estado y sondas de disponibilidad en la imagen
Las sondas de actividad y disponibilidad de Kubernetes se definen en los manifiestos, pero también puede incorporar un HEALTHCHECK al Dockerfile para entornos independientes con docker run y Docker Compose.
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...- Use
curl --failowget -qO-para servicios HTTP; en demonios que no usen HTTP, compruebe un socket con/dev/tcp/localhost/PORT. - Instale únicamente lo necesario: en imágenes distroless, evite añadir
curlsolo para las comprobaciones de estado; use un binario de sonda diseñado para ese fin o el propio binario de estado de la aplicación.
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/ /usr/share/nginx/html/
# Lightweight health check using bash TCP pseudo-device
# (no curl needed — works on any image with bash)
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD bash -c 'exec 3<>/dev/tcp/localhost/80 && echo -e "GET /health HTTP/1.0\r\n" >&3 && cat <&3 | grep -q "200 OK"' || exit 1
EXPOSE 80Cómo unirlo todo: un entrypoint de producción
A continuación se muestra un shim de entrypoint completo y listo para producción que combina todos los patrones de esta lección: validación del entorno, generación de plantillas de configuración, reenvío de señales y transferencia mediante exec. Este patrón se utiliza en microservicios reales de Node.js, Python y Go implementados en Kubernetes.
- Cada sección incluye comentarios para servir como plantilla autodocumentada.
- El shim ocupa menos de 50 líneas; los entrypoints deben ser sencillos y fáciles de auditar.
- Observe el
exec "$@"final: una vez completada toda la configuración, el shell se sustituye por el proceso de la aplicación, que se convierte en PID 1 y recibe directamente todas las señales del sistema operativo.
#!/usr/bin/env bash
# entrypoint.sh — production-grade container entrypoint shim
set -euo pipefail
# ── 1. Validate required environment variables ──────────────────
REQUIRED=(DATABASE_URL APP_SECRET PORT LOG_LEVEL)
for var in "${REQUIRED[@]}"; do
[[ -n "${!var:-}" ]] || { echo "[entrypoint] FATAL: $var is not set" >&2; exit 1; }
done
# ── 2. Set safe defaults for optional variables ─────────────────
export HOST=${HOST:-0.0.0.0}
export WORKERS=${WORKERS:-2}
# ── 3. Render config template ───────────────────────────────────
if [[ -f /etc/app/app.conf.tmpl ]]; then
envsubst '${DATABASE_URL} ${PORT} ${LOG_LEVEL} ${HOST}' \
< /etc/app/app.conf.tmpl \
> /etc/app/app.conf
echo "[entrypoint] config rendered at /etc/app/app.conf"
fi
# ── 4. Wait for dependent services (optional, fast) ─────────────
if [[ -n "${WAIT_FOR_HOST:-}" ]]; then
echo "[entrypoint] waiting for ${WAIT_FOR_HOST}:${WAIT_FOR_PORT:-5432}..."
until bash -c "exec 3<>/dev/tcp/${WAIT_FOR_HOST}/${WAIT_FOR_PORT:-5432}" 2>/dev/null; do
sleep 1
done
echo "[entrypoint] dependency ready"
fi
# ── 5. Hand off to CMD (PID 1 becomes the application) ──────────
echo "[entrypoint] starting: $*"
exec "$@"Comprobación de conocimientos: gestión de señales en entrypoints
Compruebe su comprensión de la gestión de señales en scripts de entrypoint de contenedores.
Resumen: Dockerfiles ligeros y entrypoints de shell
Ha cubierto todos los aspectos de la creación de contenedores para producción:
- Las compilaciones multietapa utilizan varias instrucciones
FROMpara mantener los compiladores y las herramientas de compilación fuera de la imagen final, lo que produce capas de ejecución ligeras y mínimas. - El orden de las capas —copiar los manifiestos de dependencias antes del código fuente— maximiza los aciertos de caché y acelera los pipelines de CI.
- Los secretos y montajes SSH de BuildKit mantienen las credenciales fuera del historial de la imagen sin interrumpir las compilaciones autenticadas.
- Los shims de entrypoint validan las variables de entorno, generan archivos de configuración con
envsubsty ceden el control a la aplicación medianteexec "$@". - La gestión de señales requiere
exec(para que la aplicación sea PID 1) o un patrón explícito detrap+kill+waitcuando se utilizan trabajos en segundo plano. - tini añade la recolección de procesos zombis y el reenvío correcto de señales cuando el contenedor crea varios procesos.
- Los usuarios no root y las instrucciones
HEALTHCHECKcompletan una imagen segura y observable, lista para cargas de trabajo de producción en Kubernetes.
Combine estos patrones de forma coherente y sus imágenes serán más pequeñas, se implementarán más rápido y serán mucho más robustas en condiciones de producción.
Preguntas frecuentes
¿La lección «Creación de Dockerfiles compactos y entrypoints de shell» es gratis?
Sí — el texto completo de «Creación de Dockerfiles compactos y entrypoints de shell» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.
¿Qué aprenderé en «Creación de Dockerfiles compactos y entrypoints de shell»?
Escriba scripts de compilación multietapa y entrypoints robustos con gestión de señales y plantillas de configuración. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar DevOps Bootcamp?
No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.
¿Cuánto tiempo toma la lección «Creación de Dockerfiles compactos y entrypoints de shell»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?
Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Creación de Dockerfiles compactos y entrypoints de shell
- Creación de plantillas de configuración con envsubst y heredocs
- Scripting de recursos en la nube mediante CLI y jq
- Sondas de estado, puertas de disponibilidad y bucles de espera