··
Imágenes, contenedores, volúmenes y redes explicados para QA. Lo mínimo que necesitas saber para no depender de DevOps.

Llevo años en QA y durante mucho tiempo Docker era algo que "usaban los de infra". Yo me limitaba a ejecutar tests contra un entorno que alguien configuraba por mí, y cuando ese entorno se rompía, abría un ticket y esperaba. Hasta que un día el entorno de staging llevaba tres días caído, nadie lo priorizaba, y yo tenía una suite de regresión que necesitaba correr antes del release. Ese día decidí aprender Docker, y fue una de las mejores inversiones de tiempo que he hecho como QA.
Este artículo es el primero de una serie donde voy a explicar Docker desde la perspectiva de alguien que viene de testing, no de DevOps. Sin abstracciones innecesarias, con ejemplos reales y centrado en lo que de verdad te va a servir en tu día a día.
Antes de Docker, levantar un entorno de pruebas era un proceso frágil. Necesitabas la versión correcta de Node, la base de datos con el schema actualizado, las variables de entorno bien configuradas, un servicio de caché corriendo en el puerto correcto. Si cualquiera de esas piezas fallaba, tus tests no funcionaban y perdías media mañana investigando qué había cambiado.
Docker empaqueta todo eso en un artefacto que funciona igual en cualquier máquina. No importa si tu compañero usa macOS y tú Linux, si el CI corre en una instancia efímera de GitHub Actions o si lo ejecutas en tu portátil. El entorno es idéntico porque está definido en código.
Para QA esto tiene una implicación directa: tus tests son tan fiables como el entorno donde corren. Si el entorno es inconsistente, el resultado de los tests no es de fiar. Docker elimina esa variable.
Docker tiene muchos conceptos, pero para empezar solo necesitas entender tres.
Una imagen es una plantilla de solo lectura que define un entorno. Piensa en ella como una receta de cocina: describe qué ingredientes necesitas (sistema operativo base, dependencias, tu aplicación) y cómo combinarlos, pero no es el plato en sí. Las imágenes se construyen a partir de un Dockerfile y se almacenan en un registro como Docker Hub.
Un contenedor es una instancia en ejecución de una imagen. Si la imagen es la receta, el contenedor es el plato servido. Puedes crear múltiples contenedores a partir de la misma imagen, cada uno con su propio estado. Y aquí viene el punto clave: los contenedores son desechables. Cuando algo se rompe, no lo reparas, lo destruyes y creas uno nuevo. Este cambio de mentalidad es fundamental.
Un volumen es almacenamiento persistente que sobrevive a la destrucción de un contenedor. Si tu aplicación usa una base de datos SQLite o necesita guardar archivos entre reinicios, el volumen es donde viven esos datos. Sin volúmenes, todo lo que un contenedor escribe desaparece cuando se detiene.
# La imagen es la plantilla
docker pull node:22-alpine
# El contenedor es la instancia viva
docker run --name mi-runner node:22-alpine node -e "console.log('hola')"
# El volumen persiste datos entre contenedores
docker volume create datos-test
docker run -v datos-test:/data node:22-alpine sh -c "echo 'resultado' > /data/output.txt"No necesitas memorizar cien comandos. Con estos seis cubres el 90% de lo que un QA necesita en su día a día.
docker run crea y arranca un contenedor a partir de una imagen. Es el comando más importante y el que usarás con más frecuencia.
# Arrancar un contenedor con un nombre, mapear puertos y ejecutar en segundo plano
docker run -d --name app-test -p 3000:3000 mi-app:latest
# Ejecutar un comando puntual y destruir el contenedor al terminar
docker run --rm node:22-alpine node -e "console.log(process.version)"El flag --rm es tu amigo. Destruye el contenedor automáticamente cuando termina, evitando que se acumulen contenedores huérfanos que ocupan espacio.
docker ps muestra los contenedores en ejecución. Con -a incluye los detenidos. Es tu primer paso cuando algo no funciona: verificar qué está corriendo y en qué estado.
# Ver contenedores activos
docker ps
# Ver todos, incluidos los detenidos
docker ps -a
# Formato personalizado para ver solo lo relevante
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"docker logs muestra la salida estándar de un contenedor. Cuando un test falla contra un servicio dockerizado, lo primero que haces es mirar sus logs.
# Ver los últimos 50 registros
docker logs --tail 50 app-test
# Seguir los logs en tiempo real (como tail -f)
docker logs -f app-test
# Incluir timestamps para correlacionar con los fallos de tus tests
docker logs -t app-testdocker exec ejecuta un comando dentro de un contenedor que ya está corriendo. Es enormemente útil para depurar: entras al contenedor, inspeccionas el sistema de archivos, revisas variables de entorno, verificas que los servicios responden.
# Abrir una shell interactiva dentro del contenedor
docker exec -it app-test sh
# Verificar que la base de datos existe
docker exec app-test ls -la /app/data/
# Comprobar las variables de entorno
docker exec app-test env | grep DATABASEdocker build construye una imagen a partir de un Dockerfile. Lo usarás cuando quieras crear tu propio entorno de testing personalizado.
# Construir una imagen con un tag descriptivo
docker build -t test-runner:latest -f Dockerfile .
# Construir sin cache (útil cuando algo se cachea mal)
docker build --no-cache -t test-runner:latest .docker stop y docker rm detienen y eliminan contenedores. Limpiar después de ti es un hábito que te ahorrará problemas de disco y conflictos de puertos.
# Detener y eliminar un contenedor específico
docker stop app-test && docker rm app-test
# Eliminar todos los contenedores detenidos de golpe
docker container prune -fVamos a construir algo práctico. Un Dockerfile que empaqueta un proyecto Node.js con sus tests, de forma que cualquiera pueda ejecutar la suite completa con un solo comando, sin instalar nada en su máquina.
# Imagen base: Alpine es ligera (menos de 50 MB)
FROM node:22-alpine
# Directorio de trabajo dentro del contenedor
WORKDIR /app
# Copiar solo los ficheros de dependencias primero (aprovecha la cache de capas)
COPY package.json pnpm-lock.yaml ./
# Instalar pnpm y las dependencias
RUN corepack enable && corepack prepare pnpm@latest --activate && \
pnpm install --frozen-lockfile
# Copiar el resto del código fuente
COPY . .
# Comando por defecto: ejecutar los tests
CMD ["pnpm", "test:ci"]Fíjate en el orden de las instrucciones COPY. Primero copiamos solo los archivos de dependencias (package.json y el lockfile), instalamos, y después copiamos el código fuente. Esto es importante porque Docker cachea cada capa. Si cambias el código pero no las dependencias, Docker reutiliza la capa del pnpm install y la reconstrucción es casi instantánea.
Ahora cualquier persona del equipo puede ejecutar los tests así.
# Construir la imagen del runner
docker build -t mi-proyecto-tests .
# Ejecutar la suite completa
docker run --rm mi-proyecto-tests
# Ejecutar un test específico
docker run --rm mi-proyecto-tests pnpm test:ci -- --grep "login"No necesitan tener Node instalado, ni pnpm, ni la versión correcta de nada. Todo está dentro de la imagen.
Este es el cambio de mentalidad más importante que Docker exige, y el que más cuesta cuando vienes de un mundo donde los entornos son pets, no cattle.
En el modelo tradicional, un servidor es un pet. Tiene nombre, lo cuidas, le instalas cosas manualmente, y cuando se pone enfermo intentas curarlo. En el modelo de contenedores, cada instancia es cattle. No tiene nombre significativo, no acumula estado, y cuando falla lo sacrificas y levantas uno nuevo.
Para QA esto es liberador. Si un test deja la base de datos en un estado inconsistente, no necesitas escribir scripts de limpieza elaborados. Destruyes el contenedor y creas uno limpio. Si sospechas que el entorno tiene algo raro que está afectando a los resultados, no pierdes tiempo investigando. Destruyes y recreas.
# Un ciclo típico de testing con contenedores
docker run -d --name test-env mi-app:latest # levantar
docker exec test-env pnpm test:ci # ejecutar tests
docker logs test-env > test-output.log # capturar logs
docker stop test-env && docker rm test-env # destruirEste patrón es el fundamento de todo lo que viene después en la serie. Compose, multi-stage builds, depuración de CI, todo se apoya en la idea de que los contenedores son desechables y el entorno es reproducible.
Hay unos cuantos tropiezos que casi todos cometemos al empezar con Docker.
No limpiar contenedores. Cada docker run sin --rm deja un contenedor detenido que ocupa disco. Después de unas semanas acabas con decenas de contenedores zombi. Acostúmbrate a usar --rm para contenedores efímeros y docker container prune -f periódicamente.
Ignorar el .dockerignore. Sin un archivo .dockerignore, el COPY . . de tu Dockerfile envía al daemon todo lo que haya en el directorio, incluyendo node_modules, .git, ficheros de datos y cualquier cosa que no debería estar en la imagen. Esto hace que el build sea lento y la imagen innecesariamente grande.
node_modules
.git
.env
data
*.log
.nextCorrer todo como root. Por defecto, los procesos dentro de un contenedor se ejecutan como root. Esto es un riesgo de seguridad innecesario. En el Dockerfile de producción siempre deberías crear un usuario sin privilegios y usarlo con la instrucción USER.
Usar latest en producción. El tag latest es mutable, lo que significa que la misma referencia puede apuntar a diferentes imágenes en momentos distintos. Si hoy construyes con node:latest y mañana tu compañero hace lo mismo, pueden obtener versiones diferentes de Node. Fija siempre la versión de la imagen base.
Con estos fundamentos ya puedes levantar contenedores, construir imágenes y ejecutar tests en un entorno reproducible. Pero el poder real de Docker para QA aparece cuando combinas varios servicios, tu aplicación, la base de datos, el servicio de caché, todo arrancando con un solo comando. Eso es exactamente lo que veremos en el siguiente artículo de la serie, donde usaremos Docker Compose para levantar entornos de test completos en segundos.

Jose, autor del blog
QA Engineer. Escribo en voz alta sobre automatización, IA y arquitectura de software. Si algo te ha servido, escríbeme y cuéntamelo.
¿Qué te ha parecido? ¿Qué añadirías? Cada comentario afina la siguiente entrada.
Si esto te ha gustado
Los scripts E2E necesitan datos sensibles —tokens de API, credenciales, URLs privadas— sin que aparezcan en el código. En JMO Labs hemos añadido variables de script con modo privado: se inyectan automáticamente, se enmascaran en los logs y se acceden con una sintaxis limpia.

Los tests E2E se rompen con cada cambio de interfaz. En JMO Labs construimos un pipeline de 5 fases con IA que planifica, ejecuta, repara selectores, diagnostica fallos y verifica resultados de forma autónoma. La caché de selectores hace que cada ejecución sea más rápida que la anterior.

Playwright no es solo para tests E2E. En JMO Labs lo usamos como motor completo: 9 fases de comprobación, localizador de 9 estrategias con self-healing, grabación de vídeo, testing responsive con viewports reales y accesibilidad con axe-core.