Introducción

Docker es una tecnología de virtualización “ligera” cuyo elemento básico es la utilización de contenedores en vez de máquinas virtuales y cuyo objetivo principal es el despliegue de aplicaciones encapsuladas en dichos contenedores.

Docker está formado por varios componentes:

  • Docker Engine: Es un demonio que corre sobre cualquier distribución de Linux y que expone una API externa para la gestión de imágenes y contenedores. Con ella podemos crear imágnenes, subirlas y bajarla de un registro de docker y ejecutar y gestionar contenedores.

  • Docker Client: Es el cliente de línea de comandos (CLI) que nos permite gestionar el Docker Engine. El cliente docker se puede configurar para trabajar con con un Docker Engine local o remoto, permitiendo gestionar tanto nuestro entorno de desarrollo local, como nuestro entorno de producción.

  • Docker Registry: La finalidad de este componente es almacenar las imágenes generadas por el Docker Engine. Puede estar instalada en un servidor independiente y es un componente fundamental, ya que nos permite distribuir nuestras aplicaciones. Es un proyecto open source que puede ser instalado gratuitamente en cualquier servidor, pero, como hemos comentado, el proyecto nos ofrece Docker Hub.

Instalación docker

En debian vamos a instalar la versión de la comunidad:

# apt install docker.io

Si queremos usar el cliente de docker con un usuario sin privilegios:

usermod -aG docker usuario

Volvemos acceder con el usuario al sistema, y comprobamos que ya podemos usar el cliente docker con el usuario sin privilegios, por ejemplo, podemos comprobar la versión que hemos instalado:

$ docker --version
Docker version 18.09.1, build 4c52b90

Docker Hello-world

Vamos a comprobar que todo funciona creando nuestro primer contendor desde la imagen hello-world:

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:95ddb6c31407e84e91a986b004aee40975cb0bda14b5949f6faac5d2deadb4b9
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Pero, ¿qué es lo que está sucediendo al ejecutar esa orden?:

  • Al ser la primera vez que ejecuto un contenedor basado en esa imagen, la imagen hello-word se descarga desde el repositorio que se encuentra en el registro que vayamos a utilizar, en nuestro caso DockerHub.

  • Muestra el mensaje de bienvenida que es la consecuencia de crear y arrancar un contenedor basado en esa imagen.

Si listamos los contenedores que se están ejecutando:

docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Comprobamos que este contenedor no se está ejecutando. Un contenedor ejecuta un proceso y cuando termina la ejecución, el contenedor se para.

Para ver los contenedores que no se están ejecutando:

$ docker ps -a
CONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                      PORTS               NAMES
19124e55fbc2        hello-world                           "/hello"                 19 minutes ago      Exited (0) 19 minutes ago                       vigorous_easley

Para eliminar el contenedor podemos identificarlo con su id:

$ docker rm 372ca4634d53

o con su nombre:

$ docker rm vigorous_easley

Docker run Ubuntu

Con el comando run vamos a crear un contenedor donde vamos a ejecutar un comando, en este caso vamos a crear el contenedor a partir de una imagen ubuntu. Si la tenemos ya en nuestro ordenador no será necesario la descarga.

docker run ubuntu /bin/echo 'Hello world'
Hello world

Comprobamos que el contenedor ha ejecutado el comando que hemos indicado y se parado:

docker ps -a
CONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                     PORTS               NAMES
653204833ed3        ubuntu                                "/bin/echo 'Hello wo…"   6 seconds ago       Exited (0) 5 seconds ago                       hungry_golick

Con el comando docker images podemos visualizar las imágenes que ya tenemos descargadas en nuestro registro local:

docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
nextcloud                     latest              c6d390bc4501        4 weeks ago         800MB
jenkins/jenkins               lts                 2302e5864777        4 weeks ago         717MB
mariadb                       latest              ade39f0469a3        4 weeks ago         407MB
ubuntu                        latest              f63181f19b2f        4 weeks ago         72.9MB
gcr.io/k8s-minikube/kicbase   v0.0.17             a9b1f16d8ece        4 weeks ago         985MB
debian                        latest              e7d08cddf791        6 weeks ago         114MB
hello-world                   latest              bf756fb1ae65        13 months ago       13.3kB

Contenedor interactivo

En este caso usamos la opción -i para abrir una sesión interactiva, -t nos permite crear un pseudo-terminal que nos va a permitir interaccionar con el contenedor, indicamos un nombre del contenedor con la opción --name, y la imagen que vamos a utilizar para crearlo, en este caso ubuntu, y por último el comando que vamos a ejecutar, en este caso /bin/bash, que lanzará una sesión bash en el contenedor:

$  docker run -it --name contenedor1 ubuntu /bin/bash 
root@4027d65709aa:/#

El contenedor se para cuando salimos de él. Para volver a conectarnos a él:

$ docker start contendor1
contendor1
$ docker attach contenedor1 
root@4027d65709aa:/#

Si el contenedor se está ejecutando podemos ejecutar comando en él con el subcomando exec:

$ docker start contendor1
contendor1
$ docker exec contenedor1 ls -al

Con la orden docker restart reiniciamos el contendor, lo paramos y lo iniciamos.

Para mostrar información de un contenedor ejecutamos docker inspect:

docker inspect contenedor1             
[
    {
        "Id": "4027d65709aa2c4d40a6959d371a2379d6ca3ac248a0ff7f765a6fefd8430f61",
        "Created": "2021-02-24T11:06:44.535829615Z",
        "Path": "/bin/bash",
        "Args": [],
        "State": {
...

Nos muestra mucha información, está en formato JSON y nos da datos sobre aspectos como:

  • El id del contenedor.
  • Los puertos abiertos y sus redirecciones
  • Los bind mounts y volúmenes usados.
  • El tamaño del contenedor
  • La configuración de red del contenedor.
  • El ENTRYPOINT que es lo que se ejecuta al hacer docker run.
  • El valor de las variables de entorno.
  • Y muchas más cosas….

En realidad, todas las imágenes tienen definidas un proceso que se ejecuta, en concreto la imagen ubuntu tiene definida por defecto el proceso bash, por lo que podríamos haber ejecutado:

$  docker run -it --name contenedor1 ubuntu

Contenedor demonio

En esta ocasión hemos utilizado la opción -d del comando run, para que la ejecución del comando en el contenedor se haga en segundo plano.

$ docker run -d --name contenedor2 ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

Si comprobamos si se está ejecutando:

docker ps -a
CONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                    PORTS               NAMES
31c8546fa4f2        ubuntu                                "/bin/sh -c 'while t…"   50 seconds ago      Up 50 seconds                                 contenedor2

Y si comprobamos el log:

docker logs contenedor2
hello world
hello world
hello world
hello world
hello world
hello world
hello world

Por último podemos parar el contenedor y borrarlo con las siguientes instrucciones:

$ docker stop contenedor2
$ docker rm contenedor2

Hay que tener en cuenta que un contenedor que esta ejecutándose no puede ser eliminado. Tendríamos que para el contenedor y posteriormente borrarlo. Otra opción es borrarlo a la fuerza:

$ docker rm -f contenedor2

Creando un contenedor con un servidor web

Tenemos muchas imágenes en el registro público docker hub, por ejemplo podemos crear un servidor web con nginx:

$ docker run -d --name my-nginx -p 8080:80 nginx

Vemos que el contenedor se está ejecutando, además con la opción -p mapeamos un puerto del equipo donde tenemos instalado el docker, con un puerto del contenedor. Para probarlo accede desde un navegador a la ip del servidor con docker y al puerto 8080.

Para acceder al log del contenedor podemos ejecutar:

$ docker logs -f my-nginx

Con la opción logs -f seguimos visualizando los logs en tiempo real.

Contenedores con variables de entorno

Más adelante veremos que al crear un contenedor que necesita alguna configuración específica, lo que vamos a hacer es crear variables de entorno en el contenedor, para que el proceso que inicializa el contenedor pueda realizar dicha configuración.

Para crear una variable de entorno al crear un contenedor usamos el flag -e o --env:

$ docker run -it --name prueba -e USUARIO=prueba ubuntu bash 
root@2eb0a2f61c0b:/# echo $USUARIO
prueba
root@2eb0a2f61c0b:/# 

En ocasiones es obligatorio el inicializar alguna variable de entorno para que el contenedor pueda ser ejecutado. si miramos la documentación en Docker Hub de la imagen mariadb, observamos que podemos definir algunas variables de entorno para la creación del contenedor (por ejemplo: MYSQL_DATABASE,MYSQL_USER, MYSQL_PASSWORD,…). Pero hay una que la tenemos que indicar de forma obligatoria, la contraseña del usuario root (MYSQL_ROOT_PASSWORD), por lo tanto:

$ docker run --name some-mariadb -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mariadb
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED                STATUS              PORTS               NAMES
9c3effd891e3        mariadb             "docker-entrypoint.s…"   8 seconds ago       Up 7   seconds        3306/tcp            some-mariadb

Podemos ver que se ha creado una variable de entorno:

$ docker exec -it some-mariadb env
...
MYSQL_ROOT_PASSWORD=asdasd
...

Y para acceder podemos ejecutar:

$ docker exec -it some-mariadb bash
root@84f8a67d9e54:/# mysql -u root -p"$MYSQL_ROOT_PASSWORD"
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.8-MariaDB-1:10.5.8+maria~focal mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>