0Pricing
DevOps Bootcamp · Lección

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, alpine o distroless) y copia únicamente los binarios compilados o los paquetes de la aplicación.
  • La imagen final nunca contiene gcc, make ni 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 capa RUN para evitar dejar la caché de paquetes en capas intermedias.
  • Use --no-cache en 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 paso RUN sin 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 que git clone pueda 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 pipefail para 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 envsubst para 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 /tmp o 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 trap para capturar SIGTERM y SIGINT en 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 exec para 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 tini a 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) o groupadd / useradd (Debian).
  • Cambie el propietario de los archivos de la aplicación con COPY --chown=appuser:appgroup; es más eficiente que una capa RUN chown independiente.
  • Cambie al usuario mediante la instrucción USER. El entrypoint y CMD heredarán este usuario.
  • securityContext.runAsNonRoot: true de 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 --fail o wget -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 curl solo 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 80

Có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 FROM para 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 envsubst y ceden el control a la aplicación mediante exec "$@".
  • La gestión de señales requiere exec (para que la aplicación sea PID 1) o un patrón explícito de trap + kill + wait cuando 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 HEALTHCHECK completan 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

  1. Creación de Dockerfiles compactos y entrypoints de shell
  2. Creación de plantillas de configuración con envsubst y heredocs
  3. Scripting de recursos en la nube mediante CLI y jq
  4. Sondas de estado, puertas de disponibilidad y bucles de espera
← Volver a DevOps Bootcamp