Docker: diferencia entre imagen y contenedor y cómo correr tu primer contenedor

DUGLAS MORENODUGLAS MORENO 👁 35

Introducción

Si estás dando tus primeros pasos con Docker, seguramente te hayas encontrado con los términos imagen y contenedor y te hayas preguntado qué los distingue realmente. En este artículo vas a aprender, de forma clara y con ejemplos prácticos, qué es cada uno, cómo se relacionan y cómo poner en marcha tu primer contenedor usando los comandos básicos docker run, docker ps y docker images. Está dirigido a desarrolladores, administradores de sistemas o cualquier persona interesada en empaquetar aplicaciones de forma portátil y reproducible. Al finalizar, vas a saber distinguir una imagen de un contenedor, crear una imagen a partir de un Dockerfile sencillo y ejecutar, listar y inspeccionar contenedores en tu máquina local.

¿Qué es una imagen de Docker?

Una imagen es una plantilla de solo lectura que contiene todo lo necesario para ejecutar una aplicación: el código, las bibliotecas, las variables de entorno y las dependencias. Se construye en capas, donde cada capa representa un cambio respecto a la anterior (por ejemplo, instalar un paquete, copiar archivos, etc.). Las imágenes son inmutables: una vez creada, no se pueden modificar; si necesitás cambiar algo, tenés que generar una nueva imagen.

Pensá en una imagen como el plano de una casa: indica cómo debe ser la estructura, qué materiales usar y cómo ensamblarlos, pero no es la casa en sí. En Docker, esa analogía funciona muy bien porque la imagen define el entorno, pero no lo ejecuta.

¿Qué es un contenedor de Docker?

Un contenedor es una instancia en ejecución de una imagen. Cuando le decís a Docker que corra una imagen, él crea una capa de escritura encima de las capas de solo lectura de la imagen y aísla ese proceso del resto del sistema mediante namespaces y cgroups. Dentro del contenedor tenés un sistema de archivos aislado, una red propia y un conjunto de procesos que corren como si estuvieran en una máquina independiente, pero compartiendo el kernel del host.

Volviendo a la analogía de la casa, el contenedor sería la casa construida a partir del plano: podés tener muchas casas idénticas (contenedores) a partir del mismo plano (imagen), y cada una puede estar en un estado diferente (por ejemplo, con muebles distintos o con modificaciones temporales).

Diferencias clave entre imagen y contenedor

Característica Imagen Contenedor
Estado Solo lectura, inmutable Escritura, mutable (capa superior)
Persistencia Se almacena en el registro local o remoto Existe mientras esté en ejecución; se puede detener y volver a iniciar
Uso Plantilla para crear uno o más contenedores Entorno de ejecución aislado
Analogía Plano de una casa Casa construida a partir del plano

Además, podés tener muchas imágenes sin ningún contenedor corriendo, y también podés tener varios contenedores basados en la misma imagen pero con configuraciones distintas (puertos mapeados, variables de entorno, etc.).

Preparando el entorno

Antes de seguir los ejemplos, necesitás tener Docker instalado. En Linux podés usar el gestor de paquetes de tu distribución; en macOS y Windows la forma más sencilla es descargar Docker Desktop desde el sitio oficial. Después de instalar, verificá que el daemon esté corriendo con:

docker version

Si ves información tanto del cliente como del servidor, todo está listo.

Creando una imagen propia con un Dockerfile

Aunque podés usar imágenes oficiales directamente desde Docker Hub, es útil saber cómo construir una propia. Vamos a crear una imagen muy simple que ejecute un servidor web ligero basado en nginx. Primero, creá un directorio para el proyecto y dentro un archivo llamado Dockerfile:

mkdir mi-nginx && cd mi-nginx

Luego, el contenido del Dockerfile:

# Usamos la imagen oficial de nginx como base
FROM nginx:alpine

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

# Exponemos el puerto 80 (el que usa nginx por defecto)
EXPOSE 80

Ahora creá el archivo index.html con un contenido sencillo:

<!DOCTYPE html>
<html>
<head>
    <title>Mi primer contenedor</title>
</head>
<body>
    <h1>¡Hola desde un contenedor Docker!</h1>
    <p>Esta página se sirve desde nginx dentro de un contenedor.</p>
</body>
</html>

Con esos dos archivos listos, construí la imagen ejecutando:

docker build -t mi-nginx:latest .

El flag -t asigna un nombre y una etiqueta (tag) a la imagen. El punto al final indica que el contexto de construcción es el directorio actual. Durante el proceso vas a ver salida similar a esta:

Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM nginx:alpine
 ---> 8f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1
Step 2/3 : COPY index.html /usr/share/nginx/html/
 ---> Using cache
 ---> a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Step 3/3 : EXPOSE 80
 ---> Running in 9f8e7d6c5b4a3s2d1f0e9d8c7b6a5s4
Removing intermediate container 9f8e7d6c5b4a3s2d1f0e9d8c7b6a5s4
 ---> e6d5c4b3a2f1e0d9c8b7a6s5d4e3f2g1h
Successfully built e6d5c4b3a2f1e0d9c8b7a6s5d4e3f2g1h
Successfully tagged mi-nginx:latest

Esta salida muestra cada paso del Dockerfile, el uso de caché cuando corresponde y el identificador único de la imagen resultante.

Listando las imágenes locales

Para verificar que tu imagen quedó guardada, usá el comando docker images:

docker images

La salida esperada sería algo como:

REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
mi-nginx     latest    e6d5c4b3a2f1   8 seconds ago    23.4MB
nginx        alpine    8f6g5h4j3k2l   2 weeks ago      23.4MB

Acá ves tu imagen mi-nginx con el tag latest, su ID, cuándo fue creada y su tamaño. También aparece la imagen base nginx:alpine que usaste como punto de partida.

Corriendo tu primer contenedor

Ahora que tenés la imagen, es la imagen, es momento de crear y ejecutar un contenedor a partir de ella. El comando más usado es docker run. Vamos a lanzar el contenedor en segundo plano (modo detached) y mapear el puerto 80 del contenedor al puerto 8080 de tu máquina host, de modo que puedas acceder al servidor web desde tu navegador:

docker run -d -p 8080:80 --name mi-primer-contenedor mi-nginx:latest

Desglosemos las opciones:

  • -d: ejecuta el contenedor en modo detached (en segundo plano). Sin él, el contenedor quedaría ligado a la terminal y verías su salida estándar.
  • -p 8080:80: publica el puerto 80 del contenedor en el puerto 8080 del host. El formato es puerto_host:puerto_contenedor.
  • --name mi-primer-contenedor: asigna un nombre amigable al contenedor para referirte a él más fácilmente.
  • mi-nginx:latest: la imagen que querés usar.

Al ejecutar ese comando, Docker devolverá algo como:

9f8e7d6c5b4a3s2d1f0e9d8c7b6a5s4e

Ese string largo es el ID completo del contenedor recién creado. Si querés ver una salida más amigable, podés agregar la opción --format pero por ahora el ID basta.

Listando contenedores en ejecución

Para ver qué contenedores tenés corriendo, usá docker ps:

docker ps

La salida esperada sería similar a:

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                  NAMES
9f8e7d6c5b4a   mi-nginx:latest   "nginx -g 'daemon of…"   8 seconds ago   Up 7 seconds    0.0.0.0:8080->80/tcp   mi-primer-contenedor

Observá los campos importantes:

  • CONTAINER ID: los primeros caracteres del ID completo.
  • IMAGE: de qué imagen se creó el contenedor.
  • COMMAND: el comando que se está ejecutando dentro del contenedor (en este caso, el comando de inicio de nginx).
  • CREATED y STATUS: cuándo se creó y su estado actual (Up significa que está corriendo).
  • PORTS: muestra el mapeo de puertos que definimos (0.0.0.0:8080->80/tcp).
  • NAMES: el nombre que le asignamos con --name.

Si quisieras ver todos los contenedores, incluidos los detenidos, agregá la bandera -a:

docker ps -a

Deteniendo y eliminando el contenedor

Cuando ya no necesites el contenedor, podés detenerlo y luego eliminarlo. Primero, deténgalo:

docker stop mi-primer-contenedor

Luego, eliminálo:

docker rm mi-primer-contenedor

Si querés hacer ambas cosas en un solo paso, podés usar:

docker rm -f mi-primer-contenedor

La bandera -f fuerza la detención antes de eliminar.

Eliminando imágenes que ya no usás

Si decidís que ya no necesitás la imagen mi-nginx:latest, podés borrarla con:

docker rmi mi-nginx:latest

Docker solo permitirá borrar la imagen si no hay contenedores que la estén usando. Si hubiera, tendrías que detener y eliminar esos contenedores primero.

Buenas prácticas al trabajar con imágenes y contenedores

  1. Usá tags específicos en lugar de latest cuando sea posible, para asegurar reproducibilidad.
  2. Mantené las imágenes lo más pequeñas posible: elegí bases oficiales mínimas (como alpine) y eliminá archivos innecesarios después de la instalación.
  3. No guardes datos persistentes dentro del contenedor: usá volúmenes (-v) o bind mounts para que la información sobreviva a la eliminación del contenedor.
  4. Revisá la capa de registro: cada instrucción en el Dockerfile genera una capa; agrupar comandos relacionados reduce el número de capas y el tamaño final.
  5. Documentá el propósito de cada imagen con un LABEL o un README dentro del repositorio.
  6. Escaneá tus imágenes en busca de vulnerabilidades con herramientas como trivy o docker scan.

Errores comunes y cómo evitarlos

  • Olvidar publicar un puerto: si te olvidás del -p, la aplicación quedará escuchando dentro del contenedor pero no será accesible desde afuera. La solución es volver a crear el contenedor con el mapeo correcto o usar docker port para ver qué puertos están expuestos.
  • Usar nombres de imágenes ambiguos: si no especificás un tag, Docker asume latest, lo que puede traer sorpresas si la imagen base se actualiza. Siempre especificá la versión cuando la conocés.
  • Dejar contenedores huérfanos: ejecutar docker run sin --rm y olvidarse de detenerlos consume recursos. Usá --rm para contenedores temporales que se eliminen automáticamente al finalizar.
  • Confundir docker images con docker ps: el primero lista plantillas, el segundo lista instancias en ejecución. Recordá que una imagen puede tener cero, uno o muchos contenedores asociados.
  • Intentar modificar una imagen en ejecución: recordá que las imágenes son inmutables; si necesitás cambiar algo, modificá el Dockerfile y reconstruí la imagen.

Conclusión

En este post vimos la diferencia esencial entre una imagen y un contenedor: la imagen es la plantilla inmutable que define el entorno, mientras que el contenedor es la instancia aislada y mutable que se ejecuta a partir de esa imagen. Aprendimos a crear una imagen propia mediante un Dockerfile, a listar nuestras imágenes con docker images, a lanzar un contenedor con docker run (incluyendo opciones útiles como -d, -p y --name), a verificar su estado con docker ps y a detenerlo y eliminarlo cuando ya no lo necesitamos.

Con estos conceptos básicos ya podés comenzar a empaquetar tus propias aplicaciones, probar diferentes versiones de software y desplegar entornos consistentes en cualquier máquina que tenga Docker instalado. El próximo paso podría ser explorar el uso de docker-compose para orquestar múltiples contenedores, o aprender a publicar tus imágenes en un registro como Docker Hub o GitHub Packages para compartirlas con tu equipo o con la comunidad.

¡Manos a la obra y seguí experimentando con contenedores!

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario