Introducción a Docker 🐳

Introducción a Docker 🐳
Introducción de Docker en el curso de servidores | postulaciones SCESI 2026

¡Bienvenidos a esta entrada! Este post introductorio a Docker es parte del curso de servidores para las postulaciones SCESI 2026, y está diseñado para brindarles las bases de la contenerización.

Contenido:

  • ¿Qué es un contenedor?
  • Contenedores VS Máquinas Virtuales
  • Casos de uso de los contenedores
  • Instalación de Docker Desktop
  • Instalación de Docker Engine
  • Comprobación de la instalación
  • Conceptos básicos
  • Ejecutar un contenedor
  • Proceso del contenedor y ejecución
  • Política de reinicio
  • Mapeo de puertos
  • Gestión basica de contenedores
  • Las Imágenes en Docker: Plantillas y Capas
  • Creando tus Propias Imágenes: Dockerfile, Docker Build, Docker Push

¿Qué es un contenedor?

Para entenderlo de forma sencilla, un contenedor es como un "mini Linux" ligero que corre dentro de nuestro sistema operativo, donde podemos empaquetar servicios o aplicaciones con todas sus dependencias de forma totalmente aislada. Esta tecnología de virtualización nos permite mover la aplicación a cualquier entorno (desarrollo, pruebas o producción) garantizando que siempre funcionará igual, eliminando por completo los conflictos de compatibilidad con nuestro OS (Operating System). Además, nos ahorra el dolor de cabeza de instalar cientos de librerías directamente en la máquina host, permitiéndonos aprovechar al máximo los recursos sin ensuciar el sistema principal.

Contenedores VS Máquinas Virtuales

Para entender la magia de Docker, hay que compararlo con las Máquinas Virtuales (VMs). Mientras que una VM levanta una computadora entera desde cero —con su propio hardware emulado y un sistema operativo completo consumiendo recursos—, los contenedores son mucho más eficientes porque comparten el kernel de nuestro sistema anfitrión. Esto significa que podemos tener múltiples entornos aislados corriendo al mismo tiempo de manera ultraligera, sin el consumo excesivo de memoria ni la lentitud que implica arrancar un sistema operativo completo por cada servicio que necesitamos.

Comparación entre una VM y un Contenedor
Comparación entre una VM y un Contenedor

Casos de uso de los contenedores

Algunos de los casos de uso más comunes de los contenedores incluyen:

  • Desarrollo y pruebas de aplicaciones
  • Implementación de microservicios
  • Implementación de aplicaciones en la nube
  • CI/CD (Continuous Integration/Continuous Deployment)
  • Aislamiento de aplicaciones
  • Escalabilidad y alta disponibilidad
  • Desarrollo multiplataforma

¿Para qué usamos todo esto? La respuesta corta es: prácticamente para cualquier cosa. Aunque a nivel empresarial se hable de casos de uso como microservicios, flujos de CI/CD, alta disponibilidad o despliegues en la nube, el resumen perfecto es que un contenedor te sirve para levantar cualquier tipo de servicio. Ya sea que necesites correr una aplicación web, una API, un backend complejo, bases de datos o procesos en segundo plano, Docker es la herramienta ideal. Las únicas grandes excepciones son las aplicaciones móviles o de escritorio con interfaces gráficas nativas (como las de Windows o Mac), ya que están muy amarradas a las guías de sus propios sistemas operativos. Para todo lo demás, si corre en un servidor, se puede contenerizar.

Instalación de Docker Desktop

Para la instalación, navegamos a la página oficial de Docker y descargamos el instalador para nuestro sistema operativo Docker Desktop.
En Linux, la instalación requiere de algunos comandos, previa descarga del paquete de instalación. En el vídeo se detalla la instalación en sistemas Debian/Ubuntu. Aunque también hay soporte oficial para Fedora y Red Hat.

¡Aviso importante para GNU Linux!

Como vimos que los contenedores utilizan características del kernel de Linux, por lo que Docker Desktop utiliza una máquina virtual para ejecutar los contenedores en sistemas Windows y Mac. En Linux, por homogeneidad y soporte de los plugins, si utilizamos Docker Desktop, también se ejecutará en una máquina virtual. Si no necesitas las herramientas adicionales de Docker Desktop, puedes instalar Docker Engine directamente para tener un rendimiento nativo, sin utilizar la máquina virtual.

Instalación de Docker Engine

La opción que más me gusta, pueden utilizar el script de instalación oficial de Docker, que se encarga de instalar todas las dependencias necesarias y configurar el sistema para que Docker funcione correctamente.

curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh

Instalación de Docker Engine en Linux

Este comando, descarga el script de instalación y lo ejecuta. Este comando, deberás lanzado con permisos root o añadir sudo delante del comando sh que ejecuta el script

Si la instalación les da algún problema o usan un sistema distinto a los que vimos en la clase, les recomiendo ir directo a la documentación oficial en Docker Engine. Solo busquen su sistema en la sección "Supported platforms" y sigan los pasos.

Comprobación de la instalación

Para comprobar que la instalación ha sido correcta, podemos ejecutar el siguiente comando:

docker --version

o lanzar el contenedor de prueba de Docker:

docker run hello-world

Si todo ha ido bien, deberías ver un mensaje de bienvenida de Docker.

Conceptos básicos

Estos son los conceptos básicos dentro de Docker que debemos entender:

  • Contenedor: Es una instancia de una imagen. Es un proceso que se ejecuta en un entorno aislado.
  • Imagen: Es un archivo binario que contiene todos los elementos necesarios para ejecutar un contenedor. Es como una plantilla que se utiliza para crear contenedores.
  • Dockerfile: Es un archivo de texto que contiene las instrucciones necesarias para crear una imagen.
  • Docker Hub: Es un repositorio de imágenes de contenedor. Es como un GitHub pero de imágenes de contenedor.

Ejecutar un contenedor

Para arrancar un contenedor, utilizamos el comando docker run. Este comando, nos permite arrancar un contenedor a partir de una imagen. Su sintaxis básica es la siguiente:

docker run <imagen>

El flujo interno que sigue Docker al ejecutar este comando es:

  • Buscar la imagen localmente en tu sistema.
  • En caso de no encontrarla, la descargará automáticamente desde Docker Hub.
  • Finalmente, levanta el contenedor basándose en esa imagen.

Junto al comando docker run, podemos utilizar una serie de opciones para personalizar el comportamiento del contenedor. Algunas de las opciones más comunes son:

  • -d: Arranca el contenedor en segundo plano.
  • -p: Mapea un puerto del contenedor al puerto del host.
  • -v: Mapea un volumen del host al contenedor.
  • --name: Asigna un nombre al contenedor.
  • --rm: Elimina el contenedor al pararlo.
  • -e: Define una variable de entorno.
  • --env-file: Define un archivo de variables de entorno.

Podemos consultar los contenedores que tenemos en ejecución con el comando docker ps. Por ejemplo, si queremos ver los contenedores que tenemos en ejecución, podríamos hacerlo con el siguiente comando:

docker ps

Además, podemos ver todos los contenedores, tanto los que están en ejecución como los que están parados, con el comando docker ps -a.

Proceso del contenedor y ejecución

Por defecto, cuando ejecutamos un contenedor en docker, la salida del terminal que vemos, es la salida del contenedor. Es decir, el log del proceso que se está ejecutando dentro. Si estamos depurando puede ser útil verlo pero no siempre es así.

Si queremos arrancar un contenedor en segundo plano, podemos utilizar la opción -d. Esto hará que se ejecute el contenedor sin mostrar la salida en el terminal.

docker run -d nginx

Si hemos lanzado un contenedor en segundo plano, podemos ver la salida del contenedor con el comando docker logs. Por ejemplo, si queremos ver la salida del contenedor anterior, podríamos hacerlo con el siguiente comando:

docker logs <id_contenedor>

También podríamos seguir la salida en tiempo real con el comando docker logs -f.

También podríamos acoplarnos al contenedor y ver la salida en tiempo real con el comando docker attach. Por ejemplo, si queremos ver la salida del contenedor anterior en tiempo real, podríamos hacerlo con el siguiente comando:

docker attach <id_contenedor>

Además del ID también podríamos utilizar el nombre del contenedor para referenciarlo.

Por último, si hemos ejecutado un contenedor sin la opción -d, podemos hacer "Control + c" para parar el contenedor y volver al terminal o, si no queremos pararlo, podemos hacer "Control + p + q" para desacoplarnos del contenedor sin pararlo.

Política de reinicio

Por defecto, cuando un contenedor falla o termina su proceso, Docker no lo reinicia por defecto. Si queremos que un contenedor se reinicie automáticamente, podemos utilizar la opción --restart. Esta opción nos permite definir una política de reinicio. Algunas de las políticas más comunes son:

  • no: No reinicia el contenedor.
  • always: Reinicia el contenedor siempre.
  • unless-stopped: Reinicia el contenedor siempre que no lo paremos.
  • on-failure: Reinicia el contenedor solo si falla.
  • on-failure:<n>: Reinicia el contenedor solo si falla n veces.

Por ejemplo, si queremos que un contenedor se reinicie siempre que falle o se reinicie el servidor anfitrión, podríamos hacerlo con el siguiente comando:

docker run --restart always nginx

Mapeo de puertos

En este ejemplo, lo que estamos ejecutando es un servidor web. Como ya remarcamos en la introducción, un contenedor se ejecuta en un entorno aislado pero, si que tenemos varios mecanismos para comunicarnos con el contenedor. Uno de ellos es el mapeo de puertos.

Podemos utilizar el contenedor de nginx como ejemplo, si hacemos una petición web o accedemos desde el navegador a localhost:80 (localhost o la IP de tu máquina si lo estás haciendo en un servidor externo), no podremos acceder a él.

El parámetro -p, mencionado anteriormente, nos permite mapear un puerto del host a un puerto del contenedor. En este caso, si queremos acceder al servidor web que hemos arrancado, necesitamos mapear el puerto 80 del contenedor a un puerto del host. En este caso he optado por el 8080, ya que en windows y mac el 80 a veces esta en uso. Para ello, podemos utilizar el siguiente comando:

docker run -d -p 8080:80 nginx

Ahora si, si accedemos a localhost:8080, podremos ver el servidor web de nginx respondiendo perfectamente. Esto es porque el puerto 8080 de nuestro host está mapeado al puerto 80 del contenedor y docker se encargará de redirigir las peticiones al contenedor. Esto funciona con cualquier puerto o cualquier aplicación, no solo con servidores web.

Por ejemplo, mapear varios puertos:

docker run -d -p 8080:80 -p 8081:81 nginx

También podríamos mapear un rango de puertos, por ejemplo del 8080 al 8085 incluidos:

docker run -d -p 8080-8085:80 nginx

Gestión básica de contenedores

Conectarse a un contenedor (exec): Si necesitas entrar a un contenedor para depurar o ejecutar un comando sin afectar su proceso principal, usamos exec.

Para un comando rápido: docker exec <id_o_nombre> ls

Para abrir una terminal interactiva (lo más común): docker exec -it <id_o_nombre> bash (necesitas los flags -it para poder interactuar).

Detener (stop) e Iniciar (start):

Para frenar un contenedor en ejecución: docker stop <id_o_nombre>

Para volver a arrancar uno detenido: docker start <id_o_nombre> (ojo a la diferencia: run crea uno nuevo desde cero, start reanuda uno que ya existe).

Reiniciar (restart):

Básicamente hace un stop y start de forma automática: docker restart <id_o_nombre>

Eliminar y Limpiar (rm y prune):

Para borrar un contenedor específico: docker rm <id_o_nombre> (si se queda atascado, añade la bandera -f para forzarlo).

Para limpieza general: Si quieres eliminar todos los contenedores detenidos de un solo golpe, tu mejor amigo es docker container prune.

Las Imágenes en Docker: Plantillas y Capas

Una imagen es, en términos sencillos, la plantilla que contiene todo lo necesario para levantar un contenedor. Tienen dos características fundamentales que hacen la magia de Docker posible:

  1. Son inmutables: Una vez creada la imagen, no cambia. Si necesitas actualizar algo, creas una nueva. Esto es vital para garantizar que tu aplicación se comporte exactamente igual en tu computadora que en el servidor de producción.
  2. Funcionan por capas: Las imágenes se construyen apilando capas (cada instalación o configuración es una capa nueva). ¿La gran ventaja? Si descargas dos imágenes distintas que comparten una base (por ejemplo, Alpine Linux), Docker es lo suficientemente inteligente para reutilizar la capa compartida, ahorrando muchísimo espacio y tiempo de descarga.

Aquí tienes los comandos esenciales para gestionar tus imágenes en local:

Descargar (pull): docker pull <imagen> (ej. docker pull nginx). Al descargar, si la imagen comparte capas con alguna que ya tienes, notarás que Docker recicla esa información marcándola como Already exists.

Listar (images): docker images te mostrará un listado de todas las imágenes que tienes actualmente descargadas en tu equipo.

Eliminar (rmi): docker rmi <id_o_nombre> sirve para borrar imágenes específicas que ya no necesites.

Limpieza general (prune): docker image prune es el comando ideal para hacer limpieza; eliminará de un plumazo todas las imágenes "huérfanas" que no estén siendo utilizadas por ningún contenedor.

Creando tus Propias Imágenes: Dockerfile y Docker Build

Para crear tus propias imágenes, necesitas un Dockerfile: un archivo de texto simple que actúa como la "receta" con todas las instrucciones paso a paso para empaquetar tu aplicación o servicio.

Instrucciones Esenciales

Cada línea en un Dockerfile se lee de arriba hacia abajo y crea una nueva capa en tu imagen. Las más comunes son:

  • FROM: Define tu imagen base (por ejemplo, nginx:alpine o python:3.9). Siempre debe ser la primera instrucción.
  • WORKDIR: Establece la carpeta de trabajo por defecto dentro del contenedor.
  • COPY: Copia archivos o directorios de tu computadora (el host) hacia el contenedor.
  • RUN: Ejecuta comandos durante la construcción de la imagen (ideal para instalar dependencias, como apt install o pip install).
  • CMD / ENTRYPOINT: Define el comando principal que se ejecutará automáticamente cuando arranque el contenedor.

Un ejemplo rápido con Nginx: 
imaginemos que queremos desplegar una web estática para el curso. Primero, creamos un archivo index.html muy básico:

<!DOCTYPE html>
<html>
<head>
    <title>Postulaciones SCESI</title>
</head>
<body>
    <h1>¡Bienvenidos al Curso de Servidores SCESI 2026!</h1>
    <p>Si estás leyendo esto, tu primer contenedor con Nginx está funcionando a la perfección.</p>
</body>
</html>

Luego, en esa misma carpeta, creamos nuestro Dockerfile para empaquetarlo. Así de simple es usar una imagen oficial de un producto final:

# Utilizamos una versión ligera de Nginx
FROM nginx

# Copiamos nuestro HTML al directorio público por defecto del servidor web
COPY index.html /usr/share/nginx/html/index.html

# Exponemos el puerto 80
EXPOSE 80

El Orden Importa: La Magia del Caché

Docker es inteligente: intenta reutilizar las capas de construcciones anteriores para ahorrar tiempo. Si cambias una línea de código, Docker tendrá que reconstruir esa capa y todas las que le siguen.

Para optimizar los tiempos de construcción, la regla de oro es poner lo que menos cambia al principio y lo que más cambia al final.

Ejemplo optimizado:

FROM python:alpine
WORKDIR /app

# 1. Copiamos SOLO las dependencias primero
COPY requirements.txt .

# 2. Las instalamos (Docker guardará esto en caché si requirements.txt no cambia)
RUN pip install -r requirements.txt

# 3. Copiamos nuestro código (que cambia constantemente) al final
COPY script.py .

CMD ["python", "script.py"]

Construir la Imagen (build)

Una vez que tienes tu Dockerfile en tu directorio, usas docker build. El parámetro -t sirve para darle un nombre (tag), y el punto . al final le indica a Docker que use el directorio actual como contexto:

docker build -t mi-app-python .

Si ejecutas docker images, verás tu nueva creación lista para usarse. Puedes levantarla normalmente con docker run -d --name mi-contenedor mi-app-python.

Cómo publicar tu imagen en Docker Hub

Si quieres que tu imagen esté disponible para descargarla en cualquier otro servidor (o compartirla con la comunidad), puedes subirla a Docker Hub en tres sencillos pasos:

1. Inicia sesión en tu terminal: Te pedirá tu usuario y contraseña (o token) de Docker Hub.

docker login

2. Etiqueta tu imagen: Para subirla, la imagen debe tener como prefijo tu nombre de usuario de Docker Hub. Usamos docker tag para clonar la etiqueta con el formato correcto (usuario/imagen:versión).

docker tag mi-app-python tu_usuario/mi-app-python:v1.0

3. Sube la imagen: Finalmente, empujamos la imagen al repositorio remoto.

docker push tu_usuario/mi-app-python:v1.0