Contenerizacion de aplicaciones - Docker

Adara DenisAdara Denis
5 min read

¿Que es la contenerizacion?

Imagina que guardas todas las herramientas necesarias para reparar tu cocina (martillo, destornillador, tornillos) en una caja. Esa caja te permite:

  • Tener todo organizado (sin mezclar con herramientas de otros proyectos).

  • Llevarla a cualquier lugar (funcionará igual en tu casa, en la de un amigo o en obra nueva).

Llevando esto al desarrollo de software

Ahora llevando este concepto al desarrollo de software, un contenedor es como esa caja, pero para aplicaciones. En él empaquetas:

  • Tu código (Ej: una app en NextJS).

  • Dependencias (React, TypeScript, TailwindCSS, etc).

  • Herramientas (Jest para pruebas).

  • Configuraciones (variables de entorno, puertos, etc).

¿Por qué usar contenedores?

Hay tres puntos claves para responder a esta pregunta:

  1. Portabilidad, una vez que tu aplicación este contenerizada funcionara en cualquier entorno que soporte contenedores y este preparado para su ejecución, es decir, en tu computadora personal, en un servidor o en la nube. Por ejemplo si conterizaste tu aplicación NextJS utilizando Docker tu aplicación correrá igual en macOS, Linux o Windows.

  2. Aislamiento, No interfiere con otras aplicaciones (evita conflictos entre versiones de Node.js o librerías) y se puede probar de forma aislada.

  3. Eficiencia, comparte recursos con el sistema anfitrión (a diferencia de una máquina virtual).

¡Manos a la obra!

Ejemplo técnico - Contenerizacion de una aplicación web

Pre-requisitos

  • NodeJS instalado en tu entorno local.

  • NPM instalado en tu entorno local.

  • Cuenta en Docker hub para alojar la imagen de la aplicación que generaremos.

  • Docker Desktop instalado en tu entorno local.

Primer paso - Crear aplicación

Vamos a crear una aplicación básica en NextJS e instalarle las librerías anteriormente mencionadas.

Ejecutamos los siguientes comandos:

npx create-next-app@latest my-personal-blog
cd my-personal-blog
npm install tailwindcss @tailwindcss/vite jest typescript

Nos quedara un proyecto con la siguiente estructura:

.
├── public/                 # Archivos públicos accesibles desde el navegador
│   ├── favicon.ico         # Favicon del proyecto
│   ├── images/             # Imágenes estáticas
│   └── robots.txt          # Configuración de bots
├── app/                    # Directorio de Router app de NextJS
│   ├── layout.tsx          # Layout principal de NextJS.
│   ├── page.tsx            # Archivo que contiene el componente incial que se renderizara.
│   ├── globals.css         # Estilos globales.
│   ├── page.module.css     # Estilos de page y configuraciones de TailwindCSS
├── .eslintrc.js            # Configuración de ESLint
├── .gitignore              # Archivos y carpetas a ignorar por Git
├── jest.config.ts          # Configuración de Jest
├── next.config.ts          # Configuración de Next.js
├── package.json            # Dependencias y scripts del proyecto
├── postcss.config.js       # Configuración de PostCSS para TailwindCSS
├── tailwind.config.js      # Configuración personalizada de TailwindCSS
├── tsconfig.json           # Configuración de TypeScript
└── yarn.lock / package-lock.json  # Bloqueo de dependencias

Segundo paso - Definir Dockerfile

Agregar en la raíz del proyecto un archivo con el nombre Dockerfile para optmizar nuestra imagen base utilizaremos una técnica llamada multi-stage build para crear una imagen liviana, eliminando dependencias innecesarias.

En el archivo Dockerfile creado agregar el siguiente contenido:

FROM node:20-alpine as builder_stage
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder_stage /app/public ./public
COPY --from=builder_stage /app/package.json ./
COPY --from=builder_stage /app/.next/standalone ./
COPY --from=builder_stage /app/.next/static ./.next/static

CMD ["node", "server.js"]

Como se puede observar el Dockerfile definido cuenta con dos etapas, la de construcción build_stage y por defecto la de ejecución o runtime.

Explicación del contenido Dockerfile

Etapa de Build

  1. Base image:

    • Usa node:20-alpine (imagen mínima con Node.js 20 y Alpine Linux).

    • La etiqueta as builder_stage nombra esta etapa para utilizarla en la etapa de ejecución o runtime u otras etapas que contenga nuestro Dockerfile.

  2. Copia de dependencias:

    • Copia solo package.json (no el código fuente completo) esto es para aprovechar el caching de Docker.
  3. Instalación dependencias:

    • RUN npm install instala las dependencias solo una vez, acelerando futuros builds.
  4. Build de la app:

    • COPY . . copia el resto del código.

    • RUN npm run build genera los archivos optimizados en .next/.

Etapa de ejecución o runtime

  1. Nueva base image:

    • El segundo FROM de nuestro Dockerfile parte de una imagen limpia de node:20-alpine (sin las herramientas de construcción).
  2. Copia selectiva:

    • public/: Assets estáticos (imágenes, favicons, etc).

    • package.json: Para posibles scripts de inicio.

    • .next/standalone: La app compilada en modo autónomo (NextJS 12+).

    • .next/static: Archivos generados durante el build (JS/CSS optimizados).

  3. Elimina lo innecesario:

    • No copia:

      • Dependencias de desarrollo (devDependencies).

      • Código fuente original (solo usa el resultado del build).

  4. Punto de entrada:

    • CMD ["node", "server.js"] inicia el servidor de NextJS desde los archivos compilados.

Tercer paso - Pruebas locales

Realizar pruebas locales a partir de la imagen que se generara.

Ejecutar el siguiente comando para la construcción de la imagen Docker en forma local, donde $IMAGE_NAME es el nombre de la imagen que se desee colocar.

docker build -t $IMAGE_NAME .

Ejecutar el siguiente comando para iniciar el contenedor en forma local -p 3000:3000 es el puerto que ejecuta la aplicación NextJS por defecto.

docker run -p 3000:3000 -t $IMAGE_NAME

En este punto ya tenemos nuestra aplicación NextJS contenerizada en forma local, y podemos probar su funcionamiento accediendo al contenedor luego de haber ejecutado el comando anterior a través de localhost:3000 desde tu browser.

Cuarto paso - Subir imagen a Docker Hub

En este paso veremos como pushear (subir) nuestra imagen contenerizada a una registry publica en este caso utilizaremos la registry de Docker Hub.

Nos dirigimos a https://hub.docker.com/ y creamos un nuevo repositorio.

Ejecutamos localmente el siguiente comando para tager tu imagen:

docker tag nextjs-app tuusuario/nextjs-app:1.0.0

Reemplaza tuusuario por tu nombre de usuario en Docker Hub.

Subir la imagen con el siguiente comando:

docker push tuusuario/nextjs-app:1.0.0

Conclusión

Con estos pasos cubrimos los siguientes puntos:

  1. Construcción de una imagen optimizada con Docker.

  2. Pruebas locales para validar el funcionamiento.

  3. Publicación en Docker Hub.

Puedes desplegar tu aplicacion contenerizada en cualquier servicio que soporte Docker ya sea en entornos cloud o serverless.

Ahora tu aplicación es portable, escalable y esta lista para desplegarla en produccion.

0
Subscribe to my newsletter

Read articles from Adara Denis directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Adara Denis
Adara Denis