Introducción
Docker ha revolucionado el desarrollo de aplicaciones al permitir a los desarrolladores crear, desplegar y ejecutar aplicaciones en entornos aislados llamados contenedores (containers). Los usuarios de Mac a veces se encuentran con el error "docker command not found" (comando docker no encontrado), lo cual puede ser frustrante al comenzar con la containerización. Este laboratorio (lab) le guiará a través de la comprensión de los conceptos de Docker, la verificación de su instalación de Docker, la solución de problemas comunes y la configuración de un entorno Docker adecuado para su trabajo de desarrollo.
Comprensión de los conceptos básicos de Docker y verificación de la instalación
Docker proporciona una forma estandarizada de empaquetar aplicaciones y sus dependencias en contenedores (containers), haciéndolas portátiles en diferentes entornos. Antes de solucionar cualquier problema de Docker, asegurémonos de comprender los conceptos básicos y verificar nuestra instalación.
¿Qué es Docker?
Docker es una plataforma que utiliza la tecnología de containerización para facilitar la creación, el despliegue y la ejecución de aplicaciones. A diferencia de las máquinas virtuales, los contenedores de Docker comparten el kernel del sistema host, pero se ejecutan en entornos aislados, lo que los hace ligeros y eficientes.
Los componentes clave de Docker incluyen:
- Docker Engine: El tiempo de ejecución (runtime) que construye y ejecuta contenedores
- Docker Images: Plantillas de solo lectura utilizadas para crear contenedores
- Docker Containers: Instancias en ejecución de Docker images
- Docker Registry: Un repositorio para almacenar y compartir Docker images
- Dockerfile: Un archivo de texto que contiene instrucciones para construir una Docker image
Verificación de la instalación de Docker
Nuestro entorno de laboratorio (lab) ya tiene Docker instalado. Verifiquemos esto comprobando la versión de Docker:
docker --version
Debería ver una salida similar a:
Docker version 20.10.21, build 20.10.21-0ubuntu1~22.04.3
Ahora, comprobemos si el demonio (daemon) de Docker se está ejecutando:
sudo systemctl status docker
Debería ver una salida que indique que Docker está activo (en ejecución). La salida se verá similar a:
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since ...
Presione q para salir de la vista de estado.
Si por alguna razón Docker no se está ejecutando, puede iniciarlo con:
sudo systemctl start docker
Ejecutando su primer contenedor
Verifiquemos que Docker funciona correctamente ejecutando un simple contenedor "hello-world":
docker run hello-world
Este comando descarga la imagen hello-world si no está disponible localmente y la ejecuta en un contenedor. Debería ver una salida similar a:
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
La salida explica lo que Docker hizo para ejecutar este contenedor, proporcionando una buena introducción a cómo funciona Docker.
Comprobación de los contenedores en ejecución
Para ver todos los contenedores que se están ejecutando actualmente, use:
docker ps
Dado que el contenedor hello-world sale inmediatamente después de mostrar su mensaje, probablemente no lo verá en esta lista. Para ver todos los contenedores, incluidos los que se han detenido, use:
docker ps -a
Esto muestra todos los contenedores, sus IDs, las imágenes de las que fueron creados, cuándo fueron creados y su estado actual.
¡Ahora ha verificado que Docker está instalado y funcionando correctamente en su entorno, y ha ejecutado su primer contenedor!
Trabajando con Docker Images y Containers
Ahora que ha verificado que Docker funciona correctamente, aprendamos a trabajar con Docker images y containers con más detalle.
Comprensión de Docker Images
Las Docker images son los planos de los contenedores. Contienen el código de la aplicación, las bibliotecas, las dependencias, las herramientas y otros archivos necesarios para que una aplicación se ejecute.
Exploremos las Docker images utilizando algunos comandos básicos:
Listado de imágenes disponibles
Para ver todas las Docker images disponibles en su sistema:
docker images
Debería ver una salida similar a:
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest feb5d9fea6a5 X months ago 13.3kB
Extracción de imágenes de Docker Hub
Docker Hub es un servicio de registro basado en la nube donde puede encontrar y compartir Docker images. Extraigamos una imagen popular:
docker pull nginx
Este comando descarga la última imagen del servidor web nginx. Verá la salida del progreso a medida que se descargan varias capas de la imagen:
Using default tag: latest
latest: Pulling from library/nginx
...
Digest: sha256:...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
Ejecute docker images de nuevo para ver la imagen nginx recién descargada en su lista.
Trabajando con Containers
Ahora que tenemos algunas imágenes, aprendamos a crear y administrar contenedores.
Ejecutando un Container
Ejecutemos un contenedor nginx que servirá una página web:
docker run --name my-nginx -p 8080:80 -d nginx
Este comando hace varias cosas:
--name my-nginx: Nombra el contenedor "my-nginx"-p 8080:80: Mapea el puerto 8080 en su host al puerto 80 en el contenedor-d: Ejecuta el contenedor en modo desatendido (en segundo plano)nginx: Especifica la imagen a utilizar
Verificando que el Container se está ejecutando
Compruebe que su contenedor se está ejecutando:
docker ps
Debería ver su contenedor nginx en la lista de contenedores en ejecución:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" X seconds ago Up X seconds 0.0.0.0:8080->80/tcp my-nginx
Accediendo al servidor web
Puede acceder al servidor web nginx abriendo un navegador web en su entorno de máquina virtual (VM) de LabEx y navegando a:
http://localhost:8080
Alternativamente, puede usar curl desde la terminal:
curl http://localhost:8080
Debería ver la página HTML de bienvenida predeterminada de nginx.
Visualización de los registros del Container
Para ver los registros de su contenedor:
docker logs my-nginx
Esto muestra los registros de acceso para el servidor nginx.
Deteniendo y eliminando Containers
Para detener un contenedor en ejecución:
docker stop my-nginx
Para eliminar un contenedor (primero debe estar detenido):
docker rm my-nginx
Verifique que el contenedor ha sido eliminado:
docker ps -a
El contenedor llamado "my-nginx" ya no debería aparecer en la lista.
Ahora comprende los conceptos básicos de cómo trabajar con Docker images y containers. Ha extraído imágenes de Docker Hub, ejecutado contenedores, mapeado puertos, visto registros y administrado los ciclos de vida de los contenedores.
Creación de sus propias Docker Images con Dockerfiles
Hasta ahora, hemos utilizado Docker images preconstruidas de Docker Hub. Ahora, aprendamos a crear nuestras propias Docker images personalizadas utilizando Dockerfiles.
¿Qué es un Dockerfile?
Un Dockerfile es un archivo de texto que contiene instrucciones para construir una Docker image. Especifica la imagen base, agrega archivos, instala software, establece variables de entorno y configura el contenedor que se creará a partir de la imagen.
Creación de su primer Dockerfile
Creemos una aplicación web simple utilizando Node.js y empaquetémosla como una Docker image.
Primero, cree un nuevo directorio para su proyecto:
mkdir -p ~/project/node-app
cd ~/project/node-app
Ahora, cree una aplicación Node.js simple. Primero, cree un archivo llamado app.js:
nano app.js
Agregue el siguiente código al archivo:
const http = require("http");
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World from Docker!\n");
});
const port = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Guarde el archivo presionando Ctrl+O, luego Enter, y salga de nano con Ctrl+X.
A continuación, cree un archivo package.json para definir su aplicación Node.js:
nano package.json
Agregue el siguiente contenido:
{
"name": "docker-node-app",
"version": "1.0.0",
"description": "A simple Node.js app for Docker",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"author": "",
"license": "ISC"
}
Guarde y salga de nano.
Ahora, cree un Dockerfile:
nano Dockerfile
Agregue el siguiente contenido:
## Use an official Node.js runtime as the base image
FROM node:14-alpine
## Set the working directory in the container
WORKDIR /usr/src/app
## Copy package.json and package-lock.json
COPY package.json ./
## Install dependencies
RUN npm install
## Copy the application code
COPY app.js ./
## Expose the port the app runs on
EXPOSE 3000
## Command to run the application
CMD ["npm", "start"]
Guarde y salga de nano.
Construyendo su Docker Image
Ahora que tiene un Dockerfile, puede construir su Docker image:
docker build -t my-node-app .
Este comando construye una imagen desde su Dockerfile:
-t my-node-app: Etiqueta la imagen con el nombre "my-node-app".: Especifica que el Dockerfile está en el directorio actual
Verá la salida que muestra el progreso de la construcción:
Sending build context to Docker daemon X.XXkB
Step 1/7 : FROM node:14-alpine
---> XXXXXXXXXX
Step 2/7 : WORKDIR /usr/src/app
---> XXXXXXXXXX
...
Successfully built XXXXXXXXXX
Successfully tagged my-node-app:latest
Ejecutando su Docker Image personalizada
Ahora, ejecute un contenedor utilizando su imagen recién construida:
docker run --name node-app-container -p 3000:3000 -d my-node-app
Verifique que el contenedor se está ejecutando:
docker ps
Debería ver su contenedor en la lista:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
XXXXXXXXXX my-node-app "npm start" X seconds ago Up X seconds 0.0.0.0:3000->3000/tcp node-app-container
Probando su aplicación
Pruebe la aplicación haciendo una solicitud HTTP:
curl http://localhost:3000
Debería ver:
Hello World from Docker!
Comprensión del Dockerfile
Revisemos los componentes clave de nuestro Dockerfile:
FROM node:14-alpine: Especifica la imagen base a utilizarWORKDIR /usr/src/app: Establece el directorio de trabajo dentro del contenedorCOPY package.json ./: Copia archivos del host al contenedorRUN npm install: Ejecuta un comando dentro del contenedor durante el proceso de construcciónEXPOSE 3000: Documenta que el contenedor escucha en el puerto 3000CMD ["npm", "start"]: Especifica el comando a ejecutar cuando se inicia el contenedor
Limpieza
Limpiemos deteniendo y eliminando el contenedor:
docker stop node-app-container
docker rm node-app-container
Ahora ha aprendido a crear sus propias Docker images utilizando Dockerfiles, construir esas imágenes y ejecutar contenedores basados en ellas. Esta es una habilidad fundamental para el desarrollo de Docker.
Gestión de datos con Docker Volumes
Un desafío al trabajar con contenedores Docker es la persistencia de datos. Los contenedores son efímeros, lo que significa que cualquier dato creado dentro de un contenedor se pierde cuando se elimina el contenedor. Los Docker volumes resuelven este problema al proporcionar una forma de persistir los datos fuera de los contenedores.
Comprensión de Docker Volumes
Los Docker volumes son el mecanismo preferido para persistir los datos generados y utilizados por los contenedores Docker. Son completamente gestionados por Docker y están aislados de la estructura de directorios del sistema de archivos del host.
Los beneficios de usar volúmenes incluyen:
- Los volúmenes son más fáciles de respaldar o migrar que los bind mounts
- Puede gestionar volúmenes utilizando comandos de la CLI de Docker
- Los volúmenes funcionan tanto en contenedores Linux como Windows
- Los volúmenes se pueden compartir de forma más segura entre múltiples contenedores
- Los controladores de volumen le permiten almacenar volúmenes en hosts remotos, proveedores de la nube o cifrar el contenido de los volúmenes
Creación y uso de Docker Volumes
Creemos un contenedor de base de datos MySQL simple que utiliza un volumen para persistir sus datos.
Creación de un Volumen
Primero, cree un Docker volume:
docker volume create mysql-data
Puede listar todos los volúmenes con:
docker volume ls
Debería ver su nuevo volumen en la lista:
DRIVER VOLUME NAME
local mysql-data
Ejecución de un contenedor con un volumen
Ahora, ejecutemos un contenedor MySQL que utiliza este volumen:
docker run --name mysql-db -e MYSQL_ROOT_PASSWORD=mysecretpassword -v mysql-data:/var/lib/mysql -p 3306:3306 -d mysql:5.7
Este comando:
--name mysql-db: Nombra el contenedor "mysql-db"-e MYSQL_ROOT_PASSWORD=mysecretpassword: Establece una variable de entorno para configurar MySQL-v mysql-data:/var/lib/mysql: Monta el volumen "mysql-data" en el directorio donde MySQL almacena sus datos-p 3306:3306: Mapea el puerto 3306 en el host al puerto 3306 en el contenedor-d: Ejecuta el contenedor en modo desatendido (detached mode)mysql:5.7: Especifica la imagen a utilizar
Espere un momento a que el contenedor se inicie, luego verifique que se esté ejecutando:
docker ps
Interactuando con la base de datos
Creemos una base de datos y una tabla para demostrar la persistencia de datos. Primero, conéctese al contenedor MySQL:
docker exec -it mysql-db bash
Dentro del contenedor, conéctese al servidor MySQL:
mysql -u root -pmysecretpassword
Cree una nueva base de datos y tabla:
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255));
INSERT INTO users (name) VALUES ('John'), ('Jane'), ('Bob');
SELECT * FROM users;
Debería ver los datos insertados:
+----+------+
| id | name |
+----+------+
| 1 | John |
| 2 | Jane |
| 3 | Bob |
+----+------+
Salga del prompt de MySQL y del contenedor:
exit
exit
Probando la persistencia del volumen
Ahora, detengamos y eliminemos el contenedor, luego creemos uno nuevo usando el mismo volumen:
docker stop mysql-db
docker rm mysql-db
Cree un nuevo contenedor usando el mismo volumen:
docker run --name mysql-db-new -e MYSQL_ROOT_PASSWORD=mysecretpassword -v mysql-data:/var/lib/mysql -p 3306:3306 -d mysql:5.7
Ahora conéctese al nuevo contenedor y verifique si nuestros datos persistieron:
docker exec -it mysql-db-new bash
mysql -u root -pmysecretpassword
USE testdb
SELECT * FROM users
Debería ver los mismos datos que insertamos anteriormente:
+----+------+
| id | name |
+----+------+
| 1 | John |
| 2 | Jane |
| 3 | Bob |
+----+------+
Salga del prompt de MySQL y del contenedor:
exit
exit
Esto demuestra que los datos persistieron incluso después de que se eliminó el contenedor original, porque se almacenaron en un Docker volume.
Inspección y gestión de volúmenes
Puede inspeccionar un volumen para obtener más información al respecto:
docker volume inspect mysql-data
Esto mostrará detalles como el punto de montaje y el controlador utilizado:
[
{
"CreatedAt": "YYYY-MM-DDTHH:MM:SS+00:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
"Name": "mysql-data",
"Options": {},
"Scope": "local"
}
]
Para limpiar, detengamos y eliminemos el contenedor:
docker stop mysql-db-new
docker rm mysql-db-new
Si también desea eliminar el volumen:
docker volume rm mysql-data
Ahora ha aprendido a usar Docker volumes para persistir datos entre los ciclos de vida de los contenedores, lo cual es esencial para aplicaciones con estado como las bases de datos.
Explorando el Networking de Docker
El networking de Docker permite que los contenedores se comuniquen entre sí y con el mundo exterior. Comprender las capacidades de networking de Docker es crucial para construir aplicaciones de múltiples contenedores.
Tipos de Redes Docker
Docker proporciona varios controladores de red (network drivers) listos para usar:
- bridge: El controlador de red predeterminado. Los contenedores en la misma red bridge pueden comunicarse.
- host: Elimina el aislamiento de red entre el contenedor y el host.
- none: Desactiva todo el networking para un contenedor.
- overlay: Conecta múltiples demonios Docker y permite que los servicios de Swarm se comuniquen.
- macvlan: Asigna una dirección MAC a un contenedor, haciéndolo aparecer como un dispositivo físico en la red.
Explorando la red bridge predeterminada
Cuando instala Docker, crea automáticamente una red bridge predeterminada. Exploremosla:
docker network ls
Debería ver una salida similar a:
NETWORK ID NAME DRIVER SCOPE
XXXXXXXXXXXX bridge bridge local
XXXXXXXXXXXX host host local
XXXXXXXXXXXX none null local
Puede inspeccionar la red bridge predeterminada:
docker network inspect bridge
Este comando proporciona información detallada sobre la red, incluidos los contenedores conectados a ella, el rango de direcciones IP y la puerta de enlace.
Creación y uso de redes bridge personalizadas
Creemos una red bridge personalizada para un mejor aislamiento de contenedores:
docker network create my-network
Verifique que la red se haya creado:
docker network ls
Debería ver su nueva red en la lista:
NETWORK ID NAME DRIVER SCOPE
XXXXXXXXXXXX bridge bridge local
XXXXXXXXXXXX host host local
XXXXXXXXXXXX my-network bridge local
XXXXXXXXXXXX none null local
Ahora, ejecutemos dos contenedores en esta red y demostremos la comunicación entre ellos.
Primero, inicie un contenedor NGINX en la red personalizada:
docker run --name web-server --network my-network -d nginx
A continuación, ejecutemos un contenedor Alpine Linux y utilicémoslo para probar la conectividad al contenedor NGINX:
docker run --name alpine --network my-network -it alpine sh
Dentro del contenedor Alpine, instale curl y pruebe la conectividad al contenedor NGINX:
apk add --update curl
curl web-server
La salida debería ser el HTML de la página de bienvenida de NGINX. Esto funciona porque Docker proporciona DNS integrado para contenedores en redes personalizadas, lo que les permite resolver nombres de contenedores en direcciones IP.
Escriba exit para salir del contenedor Alpine:
exit
Conexión de contenedores a múltiples redes
Los contenedores se pueden conectar a múltiples redes. Creemos otra red:
docker network create another-network
Conecte el contenedor web-server existente a esta nueva red:
docker network connect another-network web-server
Verifique que el contenedor ahora esté conectado a ambas redes:
docker inspect web-server -f '{{json .NetworkSettings.Networks}}' | json_pp
Debería ver que el contenedor está conectado tanto a my-network como a another-network.
Ejecución de contenedores con publicación de puertos
Cuando desea que el servicio de un contenedor sea accesible desde fuera del host Docker, necesita publicar sus puertos:
docker run --name public-web -p 8080:80 -d nginx
Este comando mapea el puerto 8080 en el host al puerto 80 en el contenedor. Puede acceder al servidor web NGINX usando:
curl http://localhost:8080
Debería ver la página de bienvenida de NGINX.
Limpieza
Limpiemos los contenedores y las redes que creamos:
docker stop web-server alpine public-web
docker rm web-server alpine public-web
docker network rm my-network another-network
Verifique que los contenedores y las redes se hayan eliminado:
docker ps -a
docker network ls
Comprensión de la comunicación entre contenedores
Este paso ha demostrado cómo el networking de Docker permite:
- Comunicación entre contenedores utilizando redes personalizadas
- Resolución de DNS utilizando nombres de contenedores
- Conexión de contenedores a múltiples redes
- Exposición de servicios de contenedores al mundo exterior mediante la publicación de puertos
Estas capacidades de networking son esenciales para construir aplicaciones complejas de múltiples contenedores donde los componentes necesitan comunicarse entre sí y con servicios externos.
Resumen
¡Felicitaciones por completar este laboratorio de Docker! Ha aprendido conceptos y habilidades esenciales de Docker que forman la base del desarrollo y la implementación basados en contenedores.
En este laboratorio, ha:
- Verificado la instalación de Docker y ejecutado su primer contenedor
- Trabajado con imágenes y contenedores de Docker, incluyendo la extracción de imágenes de Docker Hub y la gestión de los ciclos de vida de los contenedores
- Creado su propia imagen Docker personalizada utilizando un Dockerfile
- Utilizado Docker volumes para persistir datos entre los ciclos de vida de los contenedores
- Explorado el networking de Docker para habilitar la comunicación entre contenedores
Estas habilidades le permitirán:
- Empaquetar aplicaciones y sus dependencias en contenedores portátiles
- Crear entornos estandarizados para el desarrollo, las pruebas y la producción
- Implementar arquitecturas de microservicios donde cada componente se ejecuta en su propio contenedor
- Asegurar la persistencia de datos para aplicaciones con estado
- Construir aplicaciones complejas de múltiples contenedores con la debida aislamiento y comunicación
Docker se ha convertido en una herramienta esencial en el desarrollo de software moderno, y el conocimiento que ha adquirido será valioso en una amplia gama de escenarios de desarrollo y operaciones.



