Docker Command Not Found en Mac: Solución de Problemas y Configuración del Entorno

DockerBeginner
Practicar Ahora

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:

  1. FROM node:14-alpine: Especifica la imagen base a utilizar
  2. WORKDIR /usr/src/app: Establece el directorio de trabajo dentro del contenedor
  3. COPY package.json ./: Copia archivos del host al contenedor
  4. RUN npm install: Ejecuta un comando dentro del contenedor durante el proceso de construcción
  5. EXPOSE 3000: Documenta que el contenedor escucha en el puerto 3000
  6. CMD ["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:

  1. Comunicación entre contenedores utilizando redes personalizadas
  2. Resolución de DNS utilizando nombres de contenedores
  3. Conexión de contenedores a múltiples redes
  4. 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.