Dockerfile

Dockerfile
Dockerfile | curso de servidores | postulaciones SCESI 2026

¡Bienvenidos a esta nueva entrada! Este post dedicado a Dockerfile forma parte del curso de servidores para las postulaciones SCESI 2026, y está diseñado para brindarles las herramientas necesarias para empaquetar sus propias aplicaciones paso a paso.

Contenido

  • ¿Qué es un Dockerfile?
  • Sintaxis
  • Hello World en Dockerfile
  • Dockerizando un Backend
  • Multi-stage Builds para un Frontend
  • .dockerignore

¿Qué es un Dockerfile?

Un Dockerfile es un archivo de texto plano (sin extensión) que contiene una lista de instrucciones secuenciales. Docker lee este archivo de arriba hacia abajo y ejecuta cada comando para ensamblar y construir una Imagen.

Mientras que en el pasado teníamos que entrar a un servidor y ejecutar comandos manualmente para instalar dependencias, configurar variables y mover archivos, el Dockerfile nos permite definir toda esa infraestructura como código.

Piensen en ello con esta analogía: si un contenedor en ejecución es un pastel ya listo para comer, el Dockerfile es la receta paso a paso. Gracias a este archivo, cualquier miembro del equipo (o servidor) puede seguir la "receta" y obtener exactamente el mismo resultado final, eliminando para siempre la famosa frase "en mi máquina sí funciona".

Sintaxis

Cada instrucción en este archivo (escrita en mayúsculas por convención) crea una nueva "capa" en nuestra imagen. Estas son las instrucciones fundamentales que más utilizaremos:

  • FROM: Es la base de nuestra receta. Define qué sistema operativo o entorno preconfigurado usaremos (ej. Ubuntu, Node, Python). Siempre debe ser la primera instrucción.
  • WORKDIR: Define el directorio de trabajo dentro del contenedor. Es como hacer un cd hacia una carpeta. Todos los comandos siguientes se ejecutarán ahí.
  • COPY: Copia archivos o carpetas desde tu computadora (tu entorno local) hacia adentro del contenedor.
  • RUN: Ejecuta comandos en la terminal durante la construcción de la imagen (ej. instalar dependencias con npm installapt-get install).
  • ENV: Establece variables de entorno que estarán disponibles dentro del contenedor.
  • EXPOSE: Es a modo de documentación. Le dice a Docker y a otros desarrolladores en qué puerto va a escuchar nuestra aplicación, aunque no lo publica automáticamente al exterior.
  • CMD: Es el comando por defecto que se ejecutará cuando el contenedor se encienda (no durante la construcción). Solo puede haber un CMD por Dockerfile.

Hello World en Dockerfile

Para empezar, levantaremos un servidor web básico.


Escenario: Tenemos un archivo index.html simple y queremos servirlo usando Nginx.

# Usamos una imagen base ligera de Nginx
FROM nginx:alpine

# Copiamos nuestro archivo HTML al directorio por defecto de Nginx
COPY index.html /usr/share/nginx/html/

# Exponemos el puerto 80 (opcional, pero buena práctica documentarlo)
EXPOSE 80
  • Buena práctica: Usar versiones alpine (imágenes basadas en Alpine Linux) reduce drásticamente el peso de la imagen final y mejora la seguridad al tener menos herramientas instaladas que puedan ser vulneradas.

Dockerizando un Backend

Subimos de nivel. Aquí dockerizaremos una API típica (como un proyecto en NestJS o Express), instalando dependencias e introduciendo el uso de variables de entorno.

FROM node:24.12.0-alpine3.23

WORKDIR /app

# Declaramos variables de entorno que la app pueda necesitar
ENV NODE_ENV=production
ENV PORT=3000

# Copiamos SOLO los archivos de dependencias primero
COPY package*.json ./

# Instalamos las dependencias
RUN npm install

# Copiamos el resto del código fuente
COPY . .

EXPOSE 3000

# El comando que arranca nuestro backend
CMD ["npm", "run", "start:prod"]
  • Buena práctica (Caché de capas): Al copiar el package.json y ejecutar npm install antes de copiar el resto del código, Docker guardará en caché esa instalación. Si luego solo modificas tu código y no agregas nuevas librerías, la construcción de la imagen tomará segundos en lugar de minutos.
  • Variables de Entorno: Usar ENV permite configurar el comportamiento de la aplicación (como decirle a Node que estamos en producción) directamente desde la estructura de la imagen.

Multi-stage Builds para un Frontend

Este es el concepto que separa a los principiantes de los profesionales. En aplicaciones frontend compiladas (VueJS, Angular, React), no necesitamos el código fuente ni Node.js en producción, solo los archivos estáticos finales.

# --- Etapa 1: Construcción (Build Stage) ---
FROM node:18-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# Generamos los archivos estáticos (la carpeta dist/)
RUN npm run build

# --- Etapa 2: Producción (Production Stage) ---
FROM nginx:alpine AS production-stage
# Copiamos SOLO los estáticos generados en la etapa anterior
COPY --from=build-stage /app/dist /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
  • Buena práctica: Las construcciones multi-stage permiten usar imágenes pesadas con todas las herramientas de desarrollo en el primer paso, pero generar una imagen final extremadamente limpia y ligera en el segundo.

Usuario sin Privilegios

Por defecto, los contenedores de Docker ejecutan sus procesos como el usuario root. Esto es un riesgo de seguridad crítico: si alguien vulnera tu aplicación, tendrá control total sobre el contenedor. La solución es crear y usar un usuario con permisos limitados.

Veamos cómo aplicar esto a nuestro ejemplo de Node.js:

FROM node:24.12.0-alpine3.23

# Creamos un grupo y un usuario del sistema sin privilegios de root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Cambiamos el propietario de la carpeta de trabajo al nuevo usuario
RUN chown -R appuser:appgroup /app

COPY package*.json ./
RUN npm install
COPY . .

# ¡Importante! A partir de esta línea, pasamos a usar el usuario seguro
USER appuser

EXPOSE 3000
CMD ["npm", "run", "start:prod"]

Ahora veamos cómo aplicarlo en un ejemplo con Multi-stage:

# =========================
# Stage 1: Builder
# =========================
FROM node:24-bookworm AS builder

# Creamos carpeta de trabajo
WORKDIR /app

# Copiamos únicamente archivos de dependencias
# para aprovechar el cache de Docker
COPY package*.json ./

# Instalamos dependencias
RUN npm ci

# Copiamos el resto del proyecto
COPY . .

# Compilamos la aplicación
RUN npm run build


# =========================
# Stage 2: Production
# =========================
FROM node:24-bookworm-slim

# Creamos un usuario y grupo sin privilegios
RUN groupadd -r appgroup && \
    useradd -r -g appgroup -d /app -s /sbin/nologin appuser

# Definimos directorio de trabajo
WORKDIR /app

# Copiamos únicamente lo necesario desde el builder
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

# Cambiamos propietario de los archivos
RUN chown -R appuser:appgroup /app

# A partir de aquí la app ya no corre como root
USER appuser

# Puerto de la aplicación
EXPOSE 3000

# Comando de inicio
CMD ["node", "dist/main.js"]

(Nota rápida: La imagen oficial de node ya incluye un usuario llamado node por defecto, así que en ese caso específico bastaría con poner USER node, pero el ejemplo de arriba les servirá para cualquier sistema basado en Alpine).

.dockerignore

Para cerrar, nunca olviden que el archivo .dockerignore es tan importante como el .gitignore. Crea este archivo junto a tu Dockerfile para evitar que carpetas pesadas como node_modules o archivos sensibles como .env se copien accidentalmente dentro de tu imagen.

node_modules
npm-debug.log
.git
.env
Dockerfile