En el ecosistema Docker, las imágenes son la base de todo contenedor. Aprender a gestionar estas imágenes de forma eficiente es clave para mantener nuestros entornos de desarrollo y producción organizados, livianos y seguros.
En esta sección exploraremos comandos esenciales como pull, search, rmi, prune y cómo funcionan las capas (layers) que componen cada imagen. Todo esto nos permitirá optimizar el uso del espacio, mejorar tiempos de despliegue y tener mayor control sobre nuestras aplicaciones.
Eso me lo dijo ChatGTP.
Ahora bien, lo primero que debemos entender de una imagen de contenedor son dos conceptos claves: La inmutabilidad -> una vez que creamos una imagen no podemos modificarla. Y la otra es, las imágenes de contenedor están formadas por capas.
En cuanto a la inmutabilidad de la imagen, si queremos modificarla tendríamos que a la imagen modificarla y creer una nueva imagen a partir de la misma, o comenzando desde cero el proceso.
¿Por que es importante? Esto garantiza la integridad cuando movemos a la imagen de un entorno a otro, evitando que se pierda información por el camino y nos darí la certeza de que cuando la vayamos a ejecutar realmente tengamos lo que queremos que se ejecute. Y adicionalmente nos permite crear un solido control de versiones tipo git generando una imagen tras otra con las diferentes versiones de nuestra aplicación.
Cuando hacemos un docker run, podemos fijarnos que tenia varias descargas con diferentes hashes, y cada una de las capas son cada uno de los pasos que se han usado para construir la imagen. Esto explica el concepto de que esta formado por capas, permitiendo así que las imágenes reutilicen las diferentes capas con el fin de optimizar espacio y tiempo.
Por ejemplo, si tenemos dos imágenes basadas en python:3.10, Docker no descarga esa base dos veces. Al construir una nueva imagen con un Dockerfile que use FROM python:3.10, solo se crean nuevas capas desde las instrucciones como COPY o RUN, mientras que las capas ya existentes (como el sistema base y Python) se reutilizan. Esto acelera el proceso de construcción, ahorra ancho de banda y reduce el uso de disco, haciendo el manejo de imágenes más eficiente.
¿Cómo podemos descargar una image Docker manualmente?
Podemos utilizar el comando docker pull nginx
Así, sin parámetros, lo que hace es que nos descarga la ultima versión de nginx entre otras cosas.
Para ver detalladamente todo el proceso de construcción de la imagen ejecutamos el comando docker history nginx
Nos devuelve todo el proceso que se ha usado para construir la imagen, copia varias configuraciones , aplica ciertos permisos, variables de entorno, entre otras cosas.
Es todo un proceso que incluye lo que se necesita, lo que hace que podamos replicar estas configuraciones en cualquier entorno las veces que sea necesario con estos archivos de Dockerfile.
¿De donde vienen todas las images?
Si no hemos especificado lo contrario, todas las imágenes vienen de Docker Hub, que es el repositorio de imágenes donde se almacenan.
Docker Hub es el repositorio público más popular para almacenar, compartir y descubrir imágenes de contenedores. Allí podemos encontrar imágenes oficiales mantenidas por Docker y los proveedores de software (como Nginx, MySQL, Ubuntu, Node.js, entre otros), que garantizan calidad y actualizaciones constantes. También están disponibles imágenes comunitarias creadas por otros usuarios, ideales para necesidades específicas. Cada usuario u organización puede alojar sus propias imágenes, ya sean públicas o privadas. Además, Docker Hub ofrece herramientas para buscar imágenes, acceder a su documentación y ver ejemplos de uso, lo que facilita enormemente el trabajo con contenedores.
Un comando útil para buscar imágenes desde la terminal es:
docker search nginx
Aunque es mas cómodo buscar directo en Docker Hub, ya que nos muestra las diferentes versiones, instrucciones de como se usa, entre otras cosas.
Dockerfile y Docker build
Un Dockerfile es un archivo de texto que contiene un conjunto de instrucciones que Docker utiliza para construir una imagen personalizada. En él se definen todos los pasos necesarios para configurar el entorno del contenedor: desde la imagen base que se usará, la instalación de dependencias, la copia de archivos, hasta los comandos que se ejecutarán al iniciar el contenedor. Gracias al Dockerfile, puedes automatizar la creación de imágenes reproducibles, portables y fácilmente versionables, lo que lo convierte en una pieza fundamental en cualquier flujo de trabajo con Docker.
En un Dockerfile se utilizan una serie de instrucciones clave para construir una imagen paso a paso. Algunas de las más comunes son:
FROM, que define la imagen base sobre la que se construirá;
RUN, que ejecuta comandos en la imagen durante el proceso de construcción (como instalar paquetes);
COPY y ADD, que permiten copiar archivos y directorios desde el host al contenedor;
WORKDIR, establece el directorio de trabajo por defecto para el resto de instrucciones;
CMD y ENTRYPOINT, que definen qué comando se ejecutará por defecto cuando se inicie el contenedor; y
EXPOSE, que indica qué puertos serán utilizados por el contenedor.
Supongamos que tenemos el siguiente código de un archivo index.html, dentro de un directorio /app
<!DOCTYPE html> <html> <head> <title>¡Hola mundo DevOps!</title> </head> <body> <h1>¡Hola mundo DevOps!</h1> <p>Visita mi página web <a href="https://richardaguirre.cloud">richardaguirre.cloud</a>.</p> </body> </html>
Luego creamos el archivo Dockerfile
FROM nginx:alpine COPY index.html /usr/share/nginx/html/index.html
Aquí le decimos que utilizará la imagen de nginx oficial y le decimos que la copie en la ruta por defecto que usa nginx para servidor web.
Esto tan sencillo se aplica a la mayoría de imágenes.
Para construir la imagen que queremos lo hacemos con el comando:
docker build . -t mi-app
Y comenzaría el proceso de construcción.
-> docker build es el comando para construir
-> . indica el contexto donde queremos construir.
-> -t es el párametro para indicar un tag. si no lo especificamos, Docker usará el tag latest por defecto.
-> mi-app es el nombre del tag.
Comprobamos con docker images
Otro ejemplo con Python.
Pidamos una solicitud HTTP a la API pública de GitHub y que nos muestre el código de estado de la respuesta.
Creamos una carpetamkdir mi-app-python
Dentro de la carpeta creamos el archivos app.pynano app.py
Agregamos el código:
import requests response = requests.get("https://api.github.com") print("Código de estado:", response.status_code)
Creamos otro archivo. requirements.txt
nano requirements.txt
Dentro del archivo requirements.txt le indicamos la libreria de python que queremos utilizar:
requests
Ahora creamos el Dockerfile:
# Imagen base de Python FROM python:3.11-slim # Establece el directorio de trabajo dentro del contenedor WORKDIR /app # Copia los archivos de tu proyecto al contenedor COPY requirements.txt . COPY app.py . # Instala las dependencias RUN pip install --no-cache-dir -r requirements.txt # Comando por defecto al ejecutar el contenedor CMD ["python", "app.py"]
Tenemos los archivos listos y configurados.
Procedemos a construir la imagen:
docker build -t mi-python-app .
Ejecutamos el contenedor:
`docker run --rm mi-python-app`

El parámetro –rm elimina automáticamente el contenedor cuando finaliza su ejecución. Esto evita que se acumulen contenedores “muertos” que ocupen espacio.
Debemos organizar las instrucciones del Dockerfile de forma secuencial y lógica, minimizando las repeticiones innecesarias.
En el archivo requirements.txt podemos incluir todas las librerías necesarias para nuestra aplicación (flask, numpy, pandas, etc). Si lo agregamos y luego ejecutamos docker build, Docker descargará esas librerías, lo cual puede tardar un poco. Por eso, es una buena práctica copiar primero el archivo requirements.txt al contenedor y ejecutar el comando RUN pip install antes de copiar el resto del código.
De esta forma, si solo hay cambios en el código fuente pero no en las dependencias, Docker reutilizará la capa de caché donde ya se instalaron las librerías, acelerando el proceso de construcción.
CMD y ENTRYPOINT
Cuando definimos un Dockerfile, usamos las instrucciones CMD y ENTRYPOINT para establecer el comando que se ejecutará al iniciar el contenedor. Aunque son similares, tienen diferencias importantes.
ENTRYPOINT define el comando principal que siempre se ejecutará, y no puede ser sobrescrito fácilmente. Cualquier parámetro adicional proporcionado mediante CMD o al ejecutar docker run se pasará como argumentos a este comando.
En cambio, CMD establece un comando por defecto que puede ser sobrescrito si el usuario proporciona otro comando al ejecutar el contenedor.
Esto resulta útil, por ejemplo, cuando tenemos una aplicación que acepta múltiples parámetros. Podemos definir un ENTRYPOINT fijo que se ejecute siempre, y luego pasar distintos argumentos desde la línea de comandos.
Dockerfile ->
FROM alpine ENTRYPOINT ["echo", "Hola"] CMD ["Devops"]
docker run test
Salida: Hola Devops
Sobrescribiendo CMD desde la terminal:
docker run test Docker
Salida: Hola Docker
Esto nos da flexibilidad y control sobre cómo se comporta el contenedor.
Existen otras instrucciones dentro del Dockerfile. Una de las fundamentales es la instrucción ARG. También las Variables de entorno.
La instrucción ARG en un Dockerfile permite definir variables de construcción que se pueden pasar al momento de ejecutar docker build. Estas variables solo existen durante la fase de construcción de la imagen y no están disponibles en el contenedor en tiempo de ejecución, a diferencia de ENV.
FROM alpine ARG NOMBRE="Devops" RUN echo "Hola $NOMBRE"
O podemos definirlas en el docker build:docker build . -t test --build-arg=NOMBRE=Docker
Salida: Hola Docker
La instrucción ARG tiene algunas consideraciones:
Si no pasas un valor y no hay un valor por defecto en el Dockerfile
, la variable quedará vacía.
No puedes usar ARG directamente en CMD o ENTRYPOINT porque no están disponibles en el contenedor ya creado. Para eso se usa ENV.
ARG es útil para personalizar la construcción de imágenes sin dejar variables sensibles o temporales dentro del contenedor final.
Otro ejemplo:
FROM alpine ARG service=web RUN echo "Instalando $service" COPY $service /$service/app
VARIABLES DE ENTORNO
En Docker, las variables de entorno (ENV) se utilizan para definir valores que estarán disponibles dentro del contenedor en tiempo de ejecución. Son muy útiles para configurar el comportamiento de la aplicación sin modificar el código fuente.
Para usarlas, tenemos el Dockerfile:
FROM python:3.9 ENV PUERTO=5000 ENV ENTORNO=produccion
Estas variables estarán disponibles para cualquier proceso que se ejecute dentro del contenedor.
Podemos sobrescribir o definir nuevas variables con el parámetro -e :
docker run -e PUERTO=8080 -e ENTORNO=desarrollo mi-app
Accedemos a ellas, si tenemos una aplicación python por ejemplo:
import os puerto = os.getenv("PUERTO", "3000") # Usa 3000 si PUERTO no está definido
La diferencia con ARG, es que ENV esta disponible en runtime, mientras que ARG no.
Las variables de entorno (ENV) permiten configurar tu aplicación dentro del contenedor sin tocar el código, haciendo que tus contenedores sean más reutilizables, configurables y seguros.
Una vez que tenemos nuestras imágenes de Docker listas, surge la pregunta: ¿cómo podemos administrarlas? Es decir, cómo obtener imágenes a partir del estado de ejecución de un contenedor (commit), etiquetarlas, versionarlas, interactuar con repositorios remotos para subirlas, así como exportarlas e importarlas.
Lo primero que debemos conocer es la instrucción docker commit. Esta permite guardar el estado actual de un contenedor en ejecución, similar a un snapshot en una máquina virtual, pero aplicado a contenedores. Este comando crea una nueva imagen que incluye todos los cambios realizados en el sistema de archivos del contenedor, así como metadatos, configuraciones y personalizaciones que se hayan hecho.
Por ejemplo, si tenemos un contenedor de Nginx llamado web al que le realizamos modificaciones, podemos crear una imagen nueva a partir de él con el siguiente comando:
docker commit web nginx:modificada
Esto generará una nueva imagen llamada nginx
con el tag modificada
, que contiene el estado actual del contenedor web
. En esencia, es una copia personalizada del contenedor, lista para reutilizar o compartir.
¿Podríamos exportar esa imagen Docker como un archivo .tar?
Por su puesto que se puede.
El comando docker save hace eso.
Esto es muy útil cuando necesitas compartir una imagen sin usar un registro (como Docker Hub), por ejemplo, para moverla a otro servidor o equipo sin conexión a internet.
Su sintaxis es la siguiente:
docker save -o nombre-archivo.tar nombre-imagen:tag
Con el ejemplo anterior quedaría así:
docker save -o mi-imagen.tar nginx:modificada
Este comando guarda la imagen nginx:modificada en un archivo llamado mi-imagen.tar
, que puedes copiar, enviar o almacenar.
Y para restaurarla en otro sistema usaríamos el comando docker load, justamente para importar la imagen desde ese archivo .tar.
docker load -i mi-imagen.tar
Ideal para copias de seguridad o distribución manual de imágenes.
Y si queremos cambiar el nombre que tiene en TAG?
Eso se puede hacer con el comando
docker tag y se utiliza para asignar una nueva etiqueta (tag) a una imagen existente. Esto es útil para versionar imágenes, prepararlas para ser subidas a un registro remoto (como Docker Hub) o simplemente para renombrarlas localmente.
Su sintacis es la siguiente:
docker tag nombre-imagen:tag nombre-nuevo:tag-nuevo
Para que nos sirve?
Para cambiar el nombre o la versión de una imagen.
Para preparar imágenes con nombres que siguen una convención.
Para trabajar con múltiples entornos (ej. app:dev, app:prod).
Para subir imágenes a un registro remoto (necesitan el formato usuario/imagen:tag).
Cambiemos el tag de la imagen que tenemos de nginx:modificada por la versión de nginx v3.2. Quedaría así:
docker tag nginx:modificada nginx:v3.2
Listo.
docker tag no crea una nueva imagen, solo añade una nueva referencia (alias) a una imagen existente, lo que facilita su gestión, organización y distribución. Para eso se creó.
Si hacemos un docker images nos mostraría las referencias de ambas.
Y si la queremos subir al repositorio de Docker Hub? simplemente ejecutamos el comando:
docker tag nginx:modificada richardaguirrecloud/nginx:v3.2
docker push richardaguirrecloud/nginx:v3.2
Teniendo el nombre del repositorio por supuesto.
