Tu primera imagen Docker: construirla, ejecutarla y comprobar qué contiene

Construye una imagen Docker con una web pequeña, arranca dos contenedores y comprueba qué ocurre al cambiar el código. Ejemplo descargable y capturas reales.

Tienes unos archivos y quieres convertirlos en algo que puedas ejecutar con Docker. ¿Qué se construye exactamente? ¿La imagen y el contenedor son lo mismo? ¿Por qué cambias el código y la página sigue mostrando lo anterior?

Vamos a resolverlo con una web muy pequeña. Construiremos una imagen, arrancaremos dos contenedores a partir de ella y prepararemos una segunda versión. Veremos las dos versiones funcionando a la vez para comprobar qué ha cambiado y qué sigue igual.

El ejercicio está probado con Docker real en local. No sube imágenes a un registro, no publica la web ni crea recursos cloud.

Ilustración original de un Dockerfile, una imagen v1 y dos contenedores derivados de ella, junto a una segunda imagen v2.

Portada conceptual generada con IA. Las capturas del recorrido muestran resultados reales de Docker y de la aplicación.

Qué necesitas y qué se ha probado

Un motor Docker compatible ya instalado y en marcha, una terminal, un navegador y un editor de texto. El ejemplo usa Node.js dentro de la imagen; no necesitas instalar Node ni ejecutar npm en el ordenador.

La preparación se ha comprobado en Docker Desktop 4.73.0, Engine 29.4.3, macOS 27.0.1 con Apple Silicon, construyendo imágenes Linux/amd64. La imagen base está fijada por digest y el Node observado dentro del contenedor es 24.22.0. No se han probado Windows o Linux como sistemas anfitriones durante esta entrega.

Docker Desktop tiene condiciones de licencia distintas según el uso y la organización; no es gratuito para cualquier entorno profesional. Revisa los requisitos y condiciones oficiales. Esta práctica no necesita una cuenta de Google, tarjeta ni iniciar sesión en un registro.

La primera construcción puede descargar la imagen base pública. Docker Hub aplica límites de uso y descarga; si llegas a ellos, revisa el mensaje y sus límites oficiales, sin intentar eludirlos. No prometemos descargas o servicio ilimitados.

Descarga el ejemplo y extrae su carpeta. El código propio tiene licencia MIT; Node y los componentes de la imagen conservan las suyas.

1. Separar imagen y contenedor

La imagen reúne archivos, runtime y configuración para arrancar. El contenedor es una ejecución creada a partir de una imagen. Puedes tener más de un contenedor de la misma imagen, con nombres y puertos locales distintos. Qué es una imagen y qué es un contenedor.

En esta práctica, la imagen lleva nuestra web y Node. Después crearemos dos procesos aislados a partir de la versión v1, sin instalar dependencias adicionales.

2. Abrir la carpeta correcta

Extrae el ZIP y abre una terminal dentro de primera-imagen-docker, donde aparecen:

Dockerfile
.dockerignore
server.mjs
contenido.json
comprobar.py
LEEME.md
LICENSE

Comprueba que Docker puede hablar con su motor:

docker version

Debes obtener información del cliente y del servidor. Si aparece un error de conexión al daemon, inicia el motor que utilices y vuelve a comprobarlo. No construyas desde una carpeta de trabajo ajena o que contenga datos privados: utiliza esta copia de práctica.

3. Leer el Dockerfile antes de construir

El archivo incluido contiene:

ARG NODE_IMAGE=node:24-alpine@sha256:44fce696f38ba8c987c4db89954051000bbd05ffd8d8659ba15ed4a8602e937f
FROM ${NODE_IMAGE}
WORKDIR /app
COPY --chown=node:node server.mjs contenido.json ./
ENV NODE_ENV=production
USER node
EXPOSE 8080
CMD ["node", "server.mjs"]

Partimos de una base de Node, trabajamos en /app, copiamos solo dos archivos y arrancamos el servidor como usuario node. El programa lee PORT y escucha en 0.0.0.0; por defecto utiliza 8080.

El digest fija la referencia de la base utilizada para repetir esta práctica. No significa que debas mantenerla congelada indefinidamente: en un proyecto real hay que revisar actualizaciones y vulnerabilidades. Tampoco afirmamos que ejecutar como usuario distinto de root garantice por sí solo la seguridad. Referencia de Dockerfile.

4. Entender el contexto y .dockerignore

El contexto es el conjunto de archivos que el constructor puede utilizar. El punto final de docker build ... . señala la carpeta actual.

Nuestro .dockerignore deja fuera todo salvo los archivos necesarios:

**
!Dockerfile
!.dockerignore
!server.mjs
!contenido.json

No usamos COPY . . para copiar a ciegas toda la carpeta de trabajo. En las pruebas se añadieron dos archivos sintéticos a una copia del contexto —un .env sin credenciales y un texto de prueba— y se comprobó que no estaban en /app de los contenedores. Esa comprobación se refiere a este ejemplo, no certifica la ausencia de secretos en cualquier imagen. Contexto y exclusiones de construcción.

5. Construir la primera imagen

Desde la carpeta del ejemplo:

docker build --platform linux/amd64 -t davidcanton-docker:c04-v1 .

-t da un nombre y una etiqueta a la imagen. --platform linux/amd64 fija la plataforma de esta práctica; en el Mac de pruebas se ejecuta mediante la compatibilidad de Docker con esa arquitectura. También permite preparar el caso para el requisito de arquitectura que vimos en Cloud Run.

Captura real de la salida de docker build para la imagen v1, con los pasos completados y el nombre local asignado.

Salida original de la construcción, guardada y abierta como texto en el navegador. No es una terminal dibujada ni una captura de Docker Desktop.

Que el log muestre naming to docker.io/library/... no significa que hayamos subido la imagen. En este paso se asigna la referencia local. Construir y publicar en un registro son operaciones diferentes. Construcción y etiquetado.

6. Inspeccionar la imagen que acabas de crear

docker image inspect davidcanton-docker:c04-v1

La salida completa contiene bastante información. Para ver solo algunos campos:

docker image inspect davidcanton-docker:c04-v1 --format "ID={{.Id}} Plataforma={{.Os}}/{{.Architecture}} Usuario={{.Config.User}}"

Captura real de metadatos de la imagen: identificador, plataforma linux/amd64, usuario node y comando de arranque.

Metadatos reales de la imagen local, extraídos con docker image inspect.

El ID local, la etiqueta y el digest de una imagen en un registro no son conceptos intercambiables. Aquí identificamos la imagen disponible en el motor local; no estamos comprobando una publicación en Artifact Registry o Docker Hub. Inspección oficial de imágenes.

7. Arrancar un contenedor de esa imagen

docker run -d --rm --name mi-web-v1 -p 127.0.0.1:19100:8080 davidcanton-docker:c04-v1

-d deja el proceso en segundo plano. --rm indica que ese contenedor se retirará cuando lo detengas. --name permite referirnos a él sin copiar un identificador largo.

El mapeo lleva 19100 del ordenador a 8080 del contenedor. Lo publicamos únicamente en 127.0.0.1, para esta práctica local. EXPOSE 8080 por sí solo no publica un puerto del ordenador. Mapeo de puertos.

Abre http://127.0.0.1:19100/.

Captura real de la página servida por el contenedor v1: versión v1, puerto real 8080, Node 24.22.0 y UID 1000.

La versión procede de contenido.json, copiado dentro de la imagen. El puerto y el UID se leen del proceso real.

8. Verificar una respuesta que puedas comprobar

Pulsa Ver los datos en JSON, o abre http://127.0.0.1:19100/api/info.

Captura real del endpoint JSON con ok true, versión v1, puerto 8080 y UID 1000.

Respuesta HTTP 200 generada por el servidor del contenedor. No es un JSON escrito a mano para simular el resultado.

También puedes ejecutar:

curl -i http://127.0.0.1:19100/healthz

En PowerShell utiliza curl.exe si curl está asociado a otro comando. Este endpoint confirma que responde el servidor del ejemplo; no comprueba dependencias externas ni convierte el contenedor automáticamente en «healthy» para Docker. No hemos definido una instrucción HEALTHCHECK.

9. Crear otro contenedor de la misma imagen

docker run -d --rm --name mi-web-v1-b -p 127.0.0.1:19101:8080 davidcanton-docker:c04-v1

Abre http://127.0.0.1:19101/. También verás v1. Dentro, ambos servidores escuchan en 8080; fuera, usamos 19100 y 19101 para diferenciarlos.

Puedes comparar los contenedores:

docker container inspect mi-web-v1 mi-web-v1-b --format "Contenedor={{.Name}} Imagen={{.Image}}"

En la prueba, sus identificadores de contenedor son distintos y la referencia .Image coincide. La imagen se reutiliza; no hemos reconstruido una copia para cada ejecución. Ejecución de contenedores.

Captura real de docker ps filtrado a los contenedores de la práctica: dos instancias de v1 con entradas locales distintas, además de la v2 de un paso posterior.

Salida real recogida al finalizar el laboratorio; por eso ya aparece también v2. Los nombres internos de las pruebas difieren de los nombres cortos de estos comandos.

10. Editar el contenido y comprobar qué NO cambia

En el ordenador, abre contenido.json y sustitúyelo por:

{
  "version": "v2",
  "message": "He cambiado el contenido y he construido una segunda imagen."
}

Guarda y recarga http://127.0.0.1:19100/. Seguirá mostrando v1.

No es un fallo de la caché del navegador: el contenedor usa los archivos que se copiaron a la imagen anterior. No hemos montado la carpeta del ordenador dentro del contenedor. La imagen original no cambia porque edites su contexto en el anfitrión. Contenido e inmutabilidad de una imagen.

11. Construir y arrancar la segunda versión

docker build --platform linux/amd64 -t davidcanton-docker:c04-v2 .
docker run -d --rm --name mi-web-v2 -p 127.0.0.1:19102:8080 davidcanton-docker:c04-v2

Abre http://127.0.0.1:19102/.

Captura real del contenedor creado con la nueva imagen: versión v2 y el mensaje modificado, mientras v1 sigue ejecutándose por separado.

La construcción de v2 incorpora el nuevo archivo. En las pruebas, las imágenes y referencias de contenedor de v1 y v2 son distintas; v1 continúa respondiendo con su contenido anterior.

No necesitas borrar el contenedor anterior para demostrar el cambio. Precisamente conservar ambos durante esta práctica permite comparar las dos versiones sin confundirlas.

12. Comprobar qué hace una nueva etiqueta

docker image tag davidcanton-docker:c04-v1 davidcanton-docker:c04-copia
docker image inspect davidcanton-docker:c04-v1 davidcanton-docker:c04-copia --format "{{.Id}}"

Los dos IDs mostrados deben coincidir. La nueva etiqueta apunta a la misma imagen local; no reconstruye el código ni abre otro contenedor. Tampoco sube nada a Internet. Referencia de etiquetado.

Las etiquetas pueden reasignarse. Para este ejercicio usamos v1 y v2 de forma explícita y comprobamos también identidades, en lugar de deducir el contenido solo por un nombre como latest.

13. Leer los logs del proceso correcto

docker logs mi-web-v1

Captura real de logs del contenedor v1: evento listening y peticiones HTTP con versión y estado.

Salida real de docker logs, guardada sin reescribir y abierta como texto. No es Cloud Logging ni una captura de una consola remota.

El programa escribe un evento de arranque y eventos de peticiones. No imprime el entorno completo ni cabeceras de autorización. Consulta el contenedor que estás investigando: los logs de v1 no describen la ejecución de v2. Referencia de logs.

14. Repetir la prueba automática del descargable

Si ya tienes Python 3.9 o posterior, el ZIP incluye un comprobador sin paquetes adicionales:

python3 comprobar.py

En Windows adapta el nombre del ejecutable a tu instalación; ese sistema no se ha probado aquí. El script usa Docker, crea un contexto temporal y comprueba dos construcciones, tres contenedores, las respuestas v1/v2, el UID, archivos de prueba excluidos y una segunda etiqueta.

El comprobador inicializa v1 en su contexto temporal, aunque tu archivo del ejercicio ya esté en v2; no modifica el contenido.json original. Arranca contenedores propios con nombres únicos y puertos locales, y los detiene al terminar; las imágenes quedan en la caché. Se ha ejecutado también desde una extracción nueva del ZIP. Sus pruebas validan este ejemplo local, no un despliegue cloud o un análisis de vulnerabilidades.

15. Detener solo los contenedores que has creado

docker stop mi-web-v1 mi-web-v1-b mi-web-v2

Al haberse creado con --rm, Docker retira esos contenedores después de detenerlos. Las imágenes no desaparecen por ello.

Puedes conservar las etiquetas para repetir el ejercicio. Si quieres retirarlas y no las utiliza otro contenedor:

docker image rm davidcanton-docker:c04-copia davidcanton-docker:c04-v1 davidcanton-docker:c04-v2

No hace falta una limpieza global. Si te falta un contenedor porque no seguiste todos los pasos, detén únicamente los nombres que sí creaste.

16. Saber qué falta antes de llevarlo a Google Cloud

Tenemos una imagen construida y probada localmente. Todavía no está en un registro ni desplegada en Cloud Run. Subirla a Artifact Registry, conceder acceso al registro y desplegar una revisión son recorridos separados.

El tutorial de PORT en Cloud Run explica por qué leer el puerto y escuchar en 0.0.0.0 importa. La base Linux/amd64 de esta práctica concuerda con el contrato de arquitectura de Cloud Run, pero eso no certifica permisos, red, registro o facturación remotos.

Antes de conceder roles, puedes revisar la entrega de identidad, rol y recurso en IAM. Construir una imagen no necesita resolver permisos cloud; desplegarla en un proyecto sí requiere su propia preparación autorizada.

Qué demuestra esta práctica

Dos imágenes, tres contenedores y respuestas reales v1/v2. Se ha comprobado que dos contenedores usan la misma imagen, que cambiar el archivo del contexto no actualiza un proceso en marcha y que añadir una etiqueta no reconstruye la imagen.

Siete capturas reales con Chrome 153 en una sesión aislada, además de la portada original. Resultado y vista previa revisados también a 390 píxeles, sin teléfono físico. Código y descargable revisados el 20 de octubre de 2026.

No hay una publicación en Docker Hub o Artifact Registry, un despliegue cloud, una prueba en Windows/Linux anfitrión ni un escaneo de seguridad. Los logs y metadatos mostrados son de la práctica local y no incluyen recursos ajenos.