1. Objetivo de este curso
El objetivo de este curso es aprender a utilizar las nuevas tecnologías y paradigmas de contenerización basadas en Docker.
2. Contenido, sesiones y ponentes
2.1. Bloque I: Docker (10 horas)
-
19 de Noviembre, de 16:30 a 20:30
-
21 de Noviembre, de 16:30 a 20:30
-
26 de Noviembre, de 16:30 a 18:30
2.2. Bloque II: Kubernetes (10 horas)
-
26 de Noviembre, de 18:30 a 20:30
-
28 de Noviembre, de 16:30 a 20:30
-
3 de Diciembre, de 16:30 a 20:30
2.3. Ponentes
-
Manolo Torres (Profesor titular de la UAL)
-
José Antonio Martínez (Profesor titular de la UAL)
-
José Juan Sánchez (PES Informática)
3. Conceptos básicos
3.1. ¿Qué es Docker?
Docker es una plataforma para que desarrolladores y administradores puedan desarrollar, desplegar y ejecutar aplicaciones en un entorno aislado denominado contenedor.
Docker empaqueta software en unidades estandarizadas llamadas contenedores que incluyen todo lo necesario para que el software se ejecute (librerías, código, archivos de configuración, etc).
Antes de Docker ya existían implementaciones de aislamiento de recursos como:
-
Chroot, en el año 1982.
-
FreeBSD Jails, en el año 2000.
-
Linux Containers (LXC), en el año 2008.
Docker empezó a ganar popularidad en el año 2013 permitiendo a los desarrolladores crear, ejecutar y escalar rápidamente sus aplicaciones creando contenedores.
El uso de contenedores es actualmente uno de los mecanismos más comunes para desplegar software.
Empresas como Google, Microsoft, Amazon, Oracle, WMware, IBM y RedHat están apostando fuertemente por las tecnologías de contenerización.
El pasado 13 de noviembre de 2019, la empresa desarrolladora de Docker, Docker Inc, fue adquirida por Mirantis por 35 millones de dólares.
Uno de los principales problemas que resuelve es el de It works on my machine.
Referencia:
3.2. Analogía con el transporte marítimo de contenedores
Los contenedores de transporte marítimo:
-
Cumplen un estándar para enviar mercancías.
-
No nos importa el contenido sino que su forma sea estándar.
-
Pueden ser transportados en cualquier embarcación que cumpla el estándar.
Los contenedores software:
-
Cumplen un estándar para empaquetar software.
-
No nos importa el contenido sino que su "forma" sea estándar.
-
Pueden ser ejecutados en cualquier servidor que "cumpla el estándar".
3.3. ¿Qué es una Máquina Virtual?
Es un software que simula un sistema de computación y puede ejecutar programas como si fuese una computadora real.
Una característica esencial de las máquinas virtuales es que los procesos que ejecutan están limitados por los recursos y abstracciones proporcionados por ellas.
Referencia: Wikipedia
3.4. ¿Qué es un contenedor?
Un contenedor es un proceso que ha sido aislado de todos los demás procesos en la máquina anfitriona (máquina host). Ese aislamiento aprovecha características de Linux como los namespaces del kernel y cgroups.
|
Aunque es posible tener más de un proceso en un contenedor las buenas prácticas nos recomiendan ejecutar sólo un proceso por contenedor (PID 1). |
Referencia: Docker 101 Tutorial
3.5. Contenedores vs Máquinas Virtuales
-
Los contenedores son más ligeros que las máquinas virtuales porque comparten el kernel del host.
-
Con el mismo hardware, es posible tener un mayor número de contenedores que de máquinas virtuales.
-
Los contenedores se pueden ejecutar en hosts que sean máquinas virtuales.
3.5.1. Ventajas para los desarrolladores
-
Soluciona el problema "It works on my machine".
-
Permite tener un entorno de desarrollo limpio, seguro y portátil.
-
Permite la automatización de pruebas, integración y empaquetado.
-
Permite empaquetar una aplicación con todas las dependencias que necesita (código fuente, librerías, configuración, etc.) para ser ejecutada en cualquier plataforma.
3.5.2. Ventajas para administradores
-
Se eliminan inconsistencias entre los entornos de desarrollo, pruebas y producción.
-
El proceso de despliegue es rápido y repetible.
Referencia:
3.6. Arquitectura de Docker
-
Docker Daemon
-
Docker Client
-
Docker Registries
-
Docker Objects
-
Images
-
Containers
-
Volumes
-
Networks
-
Referencias:
3.7. Docker Engine
El Docker Engine es una aplicación cliente-servidor formada por los siguientes componentes:
-
Docker daemon
-
Docker REST API
-
Docker CLI
Referencia:
3.8. Dockerfiles vs Imágenes vs Contenedores
Un Dockerfile es un archivo de texto que contiene los comandos necesarios para crear una imagen.
Una imagen se crear a partir de un archivo Dockerfile. Contienen la unión de sistemas de archivos apilados en capas, donde cada capa representa una modificación de la imagen y equivale a una instrucción en el archivo Dockerfile.
Un contenedor es una instancia en ejecución de una imagen.
3.9. ¿Qué tecnología hay detrás de Docker?
3.9.1. Espacio de nombres
Es una característica de aislamiento de recursos del kernel de Linux. Nos permiten realizar visualizaciones restringidas de los recursos.
Cuando ejecutamos un contenedor, Docker crea un conjunto de namespaces para ese contenedor.
-
Process trees (PID namespace)
-
Mounts (MNT namespace)
-
Network (NET namespace)
-
Unix Timesharing System (UTS namespace)
-
Inter Process Communication (IPC Namespace)
3.9.2. Cgroups (Control Groups)
Es una característica del kernel de Linux que permite limitar y aislar recursos (CPU, memoria, disco I/O, red, etc.) utilizados por un grupo de procesos.
3.9.3. Sistemas de archivos en capas (Union File Systems)
Estos sistemas de archivos que funcionan creando capas, haciéndolos muy ligeros y rápidos. Docker Engine utiliza UnionFS para proporcionar los bloques de construcción para contenedores.
Docker Engine puede usar múltiples variantes de UnionFS, como: AUFS, btrfs, vfs y DeviceMapper.
Referencias:
3.10. Productos de Docker
-
Docker Enterprise Edition (EE): Es la versión empresarial y es de pago.
-
Docker Community Edition (CE): Es la versión de uso gratuito, open source y se puede usar en Windows, Mac y Linux.
-
Docker for Linux
-
Docker Desktop for MacOS
-
Docker Desktop for Windows
-
|
Docker Toolbox: Para versiones previas a Windows 10 Professional o Enterprise 64-bit |
Cuando instalamos Docker Desktop en Windows o Mac viene acompañado de una máquina virtual llamada MobyLinux que nos permite ejecutar contenedores para Linux.
Docker CE puede funcionar en Windows de dos modos:
-
Windows Containers: Permite ejecutar contenedores con imágenes de Windows Server Core o Windows Nano Server.
-
Linux Containers: Crea y arranca automáticamente una máquina virtual en Hyper-V llamada MobyLinux donde se ejecutarán nuestros contenedores con imágenes basadas en Linux.
Referencia:
3.11. El stack de contenerización
4. Instalación de Docker
-
Pasos posteriores a la instalación en Linux.
-
Configurar nuestro usuario para trabajar con Docker.
-
Configurar el servicio de Docker para que se inicie automáticamente.
-
|
Errores comunes después de la instalación Si nos aparece el siguiente error después de la instalación en Linux es porque el usuario con el que estamos ejecutando
|
4.1. Configuración del usuario
El daemon de Docker utiliza un socket Unix y por defecto, el socket Unix es
propiedad del usuario root, de modo que los demás usuarios solo pueden acceder a
él usando sudo.
Para evitar tener que escribir sudo cada vez que vayamos a ejecutar un comando
de docker tenemos que añadir el usuario con el que vamos a trabajar al grupo
docker.
$ sudo usermod -aG docker $USER
Para activar los cambios en los grupos sin tener que cerrar la sesión podemos ejecutar lo siguiente.
$ newgrp docker
4.2. Configurar el servicio de Docker para que se inicie automáticamente.
$ sudo systemctl enable docker
4.3. Comprobamos si docker está instalado correctamente
$ docker version
5. Administración básica de contenedores Docker
5.1. Ciclo de vida de un contenedor Docker
5.2. Docker client query verbs
5.3. Docker client query actions
5.4. Comandos de Docker CLI
$ docker --help
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Options:
--config string Location of client config files (default "/Users/josejuansanchez/.docker")
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and
default context set with "docker context use")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/Users/josejuansanchez/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/Users/josejuansanchez/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/Users/josejuansanchez/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Management Commands:
builder Manage builds
config Manage Docker configs
container Manage containers
context Manage contexts
image Manage images
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
stack Manage Docker stacks
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Commands:
attach Attach local standard input, output, and error streams to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
exec Run a command in a running container
export Export a container's filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
info Display system-wide information
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
login Log in to a Docker registry
logout Log out from a Docker registry
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
ps List containers
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save one or more images to a tar archive (streamed to STDOUT by default)
search Search the Docker Hub for images
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
version Show the Docker version information
wait Block until one or more containers stop, then print their exit codes
6. Imágenes Docker
6.1. Buscar imágenes en Docker Hub
$ docker search --help
Usage: docker search [OPTIONS] TERM
Search the Docker Hub for images
Options:
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print search using a Go template
--limit int Max number of search results (default 25)
--no-trunc Don't truncate output
Ejemplo:
Vamos a buscar la imagen de Alpine Linux, que es una distribución Linux muy ligera. Esta imagen ocupa menos de 6 MB.
$ docker search alpine
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
alpine A minimal Docker image based on … 5797 [OK]
mhart/alpine-node Minimal Node.js built on… 444
anapsix/alpine-java Oracle Java 8 (and 7) with GLIBC … 428 [OK]
frolvlad/alpine-glibc Alpine Docker image with gl 218 [OK]
gliderlabs/alpine Image based on Alpine Linux will … 180
...
|
Docker Hub nos informa de cuales son las imágenes oficiales. Por seguridad, se recomienda hacer uso exclusivamente de las imágenes oficiales. Puede encontrar más información sobre las imágenes oficiales de Docker en la documentación de la web oficial. |
|
Las imágenes que aparecen marcadas como Puede encontrar más información sobre los builds automáticos en la documentación oficial de Docker. |
En el listado del resultado de la búsqueda podemos ver que la descripción de las
imágenes aparece truncada. Podemos hacer uso de la opción --no-trunc para
poder visualizar la descripción completa de cada una de las imágenes.
Ejemplo:
$ docker search alpine --no-trunc
Por defecto, cada búsqueda devolverá un máximo de 25 resultados. Pero es posible
incrementar el número de resultados de la búsqueda utilizando el flag --limit.
Este flag nos permite indicar un número entre 1 y 100.
Ejemplo:
$ docker search alpine --limit 100
Referencia:
6.2. Buscar imágenes en Docker Hub utilizando filtros
Los únicos filtros que podemos realizar desde están basados en las siguientes condiciones:
-
stars=(number) -
is-automated=(true|false) -
is-official=(true|false)
A continuación se muestran algunos ejemplos de uso:
Por el número de estrellas
Buscar las imágenes de Alpine Linux que tengan al menos 10 estrellas.
$ docker search --filter stars=10 alpine
Sólo imágenes oficiales
Buscar las imágenes de Alpine Linux que sean oficiales.
$ docker search --filter is-official=true alpine
Sólo imágenes con build automático
Buscar las imágenes de Alpine Linux que tengan un build automático.
$ docker search --filter is-automated=true alpine
Referencia:
6.3. Imágenes interesantes de Docker
Algunas imágenes de Docker que podemos destacar por su popularidad son las siguientes:
-
alpine: Linux reducido
-
nginx: Servidor web Nginx
-
httpd: Servidor web Apache
-
ubuntu: Ubuntu
-
redis: Base de datos Redis (clave-valor)
-
mongo: Base de datos MongoDB (documentos)
-
mysql: Base de datos MySQL (relacional)
-
postgres: Base de datos PostgreSQL (relacional)
-
node: Node.js
-
registry: Registro de imágenes on-premise
-
php, haproxy, wordpress, rabbitmq, python, openjdk, tomcat, jenkins, redmine, elasticsearch…
|
En la web de la documentación oficial puede encontrar un listado de las imágenes Docker más populares. |
6.4. Descargar imágenes desde Docker Hub
$ docker pull --help
Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]
Pull an image or a repository from a registry
Options:
-a, --all-tags Download all tagged images in the repository
--disable-content-trust Skip image verification (default true)
-q, --quiet Suppress verbose output
Ejemplo:
Vamos a descargar la última versión de la imagen alpine. Para descargarse la
última versión (latest) no es necesario indicar ninguna etiqueta.
$ docker pull alpine
El comando anterior es equivalente a:
$ docker pull alpine:latest
Si quisiera descargar una versión específica, por ejemplo la versión 3.7 de
alpine tendría que indicarlo de la siguiente manera:
$ docker pull alpine:3.7
Referencia:
6.5. Descargar imágenes desde un Registry diferente a Docker Hub
Por defecto, Docker descarga las imágenes desde Docker Hub, pero es posible descargar imágenes desde un registry diferente si indicamos el path donde está la imagen que queremos descargar. El path es similar a una URL, pero no es necesario indicar el protocolo (https://).
Ejemplo:
El siguiente comando descargará la imagen curso-docker/test-image de un registry local que estará escuchando en el puerto 5000 (myregistry.local:5000):
$ docker pull myregistry.local:5000/curso-docker/test-image
Referencia:
6.6. Layers de una imagen
Una imagen está formada por distintas capas que contienen las modificaciones que se han ido realizando sobre una imagen base.
A las capas también se les puede llamar imágenes intermedias.
Cada capa representa cada una de las instrucciones del archivo Dockerfile con el que se ha creado la imagen.
|
Recuerda que un contenedor es una instancia de una imagen. En un contenedor todas las capas son de lectura, excepto la última capa que será de lectura/escritura. |
Ejemplo
Suponga que partimos del siguiente archivo Dockerfile:
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
El archivo contiene cuatro instrucciones, por lo tanto se crearán cuatro capas.
La primera capa (d3a1f33e8a5a) será la que hace referencia a la instrucción
FROM ubuntu:18:04 y ocupa 188.1 MB. Sobre esta capa se crea la de la
instrucción COPY (c22013c84729), en tercer lugar se crear la capa de la
instrucción RUN (d7508fb6632). En último lugar se crea la capa de la
instrucción CMD (91e54dfb1179) que ocupa 0 Bytes.
Las capas de la imagen son de sólo lectura. Cuando instanciamos un contenedor, se crea una nueva capa encima de la imagen que será de lectura/escritura.
ubuntu. Imagen de DockerLa siguiente figura muestra como varios contenedores en ejecución comparten la misma imagen. Cada contenedor tiene su propia capa de lectura/escritura, donde almacenan todos los cambios. Cuando eliminamos un contenedor, sólo borramos su capa de lectura/escritura.
ubuntu. Imagen de DockerEjemplo
Cuando descargamos una imagen obtenemos una salida similar a la del siguiente
ejemplo, donde descargamos la imagen de alpine:
$ docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
89d9c30c1d48: Pull complete (1)
Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
| 1 | Esta línea indica que hemos descargado una capa de la imagen. |
En este caso la imagen de alpine está formada por una única capa.
Si observamos el archivo Dockerfile de la imagen de alpine encontramos lo siguiente:
FROM scratch
ADD alpine-minirootfs-3.10.3-x86_64.tar.gz /
CMD ["/bin/sh"]
Todas las instrucciones del archivo Dockefile crean una capa, pero sólo las
líneas que incluyen las instrucciones RUN, ADD y COPY generan una capa con
contenido (el resto de capas ocupan 0 bytes). Por lo tanto en el Dockerfile del
ejemplo anterior sólo existe una capa con la instrucción ADD, que es la que se
ha descargado cuando hemos hecho docker pull apine.
Más adelante profundizaremos un poco más sobre este tema, cuando veamos la sección sobre cómo crear nuestras propias imágenes con Dockerfiles.
Ejemplo
Vamos a observar la salida que obtenemos cuando descargamos la imagen de ubuntu:
$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
7ddbc47eeb70: Pull complete (1)
c1bbdc448b72: Pull complete (2)
8c3b70e39044: Pull complete (3)
45d437916d57: Pull complete (4)
Digest: sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
| 1 | Se descarga la 1ª capa |
| 2 | Se descarga la 2ª capa |
| 3 | Se descarga la 3ª capa |
| 4 | Se descarga la 4ª capa |
En este ejemplo podemos ver como ha sido necesario descargar cuatro capas para la imagen de ubuntu.
Si observamos el archivo Dockerfile de la imagen de ubuntu encontramos lo siguiente:
FROM scratch
ADD ubuntu-bionic-core-cloudimg-amd64-root.tar.gz /
# verify that the APT lists files do not exist
RUN [ -z "$(apt-get indextargets)" ]
# (see https://bugs.launchpad.net/cloud-images/+bug/1699913)
# a few minor docker-specific tweaks
# see https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap
RUN set -xe \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L40-L48
&& echo '#!/bin/sh' > /usr/sbin/policy-rc.d \
&& echo 'exit 101' >> /usr/sbin/policy-rc.d \
&& chmod +x /usr/sbin/policy-rc.d \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L54-L56
&& dpkg-divert --local --rename --add /sbin/initctl \
&& cp -a /usr/sbin/policy-rc.d /sbin/initctl \
&& sed -i 's/^exit.*/exit 0/' /sbin/initctl \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L71-L78
&& echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L85-L105
&& echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean \
&& echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean \
&& echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L109-L115
&& echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L118-L130
&& echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes \
\
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L134-L151
&& echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests
# make systemd-detect-virt return "docker"
# See: https://github.com/systemd/systemd/blob/aa0c34279ee40bce2f9681b496922dedbadfca19/src/basic/virt.c#L434
RUN mkdir -p /run/systemd && echo 'docker' > /run/systemd/container
CMD ["/bin/bash"]
Podemos ver que hay una instrucción de tipo ADD y tres de tipo RUN, por lo tanto tendremos que descargar cuatro capas.
Ejemplo
Una capa puede ser reutilizada por otras imágenes para ahorrar espacio.
En el siguiente ejemplo podemos ver cómo una misma capa puede ser compartida por varias imágenes.
$ docker pull php
Using default tag: latest
latest: Pulling from library/php
8d691f585fa8: Pull complete
cba12d3fd8b1: Pull complete
cda54d6474c8: Pull complete
412447ed0729: Pull complete
be73f2c7a508: Pull complete
ccea27a56d46: Pull complete
e652349e8aa0: Pull complete
35d8aa4ba783: Pull complete
dd9d93e7999d: Pull complete
66f02e7e72bd: Pull complete
Digest: sha256:bcb652b60a7c3d9ca36d7bea93573fe052e18487b4ccd39dc9455222f03252eb
Status: Downloaded newer image for php:latest
docker.io/library/php:latest
$ docker pull php:apache
apache: Pulling from library/php
8d691f585fa8: Already exists (1)
cba12d3fd8b1: Already exists (2)
cda54d6474c8: Already exists (3)
412447ed0729: Already exists (4)
84de6fc539c3: Pull complete
d67567ed6145: Pull complete
22ca6c438da4: Pull complete
aaaf25e57dd6: Pull complete
fbccd385090a: Pull complete
15b403f621d7: Pull complete
1cae2d7071d0: Pull complete
5c0cbd6e0573: Pull complete
1b48a6c1e889: Pull complete
855d31502496: Pull complete
Digest: sha256:2dd2c5f682306c0738f2ac826fae0c98f467a447ef2a9d6bc4ee86eed97eefc6
Status: Downloaded newer image for php:apache
docker.io/library/php:apache
| 1 | Esta capa no es necesario descargarla porque ya se descargó previamente para otra imagen. Ocurre lo mismo para 2, 3 y 4. |
Referencia:
6.7. Consultar el historial de una imagen
Usage: docker history [OPTIONS] IMAGE
Show the history of an image
Options:
--format string Pretty-print images using a Go template
-H, --human Print sizes and dates in human readable format (default true)
--no-trunc Don't truncate output
-q, --quiet Only show numeric IDs
Ejemplo
Vamos a consultar el historial de la imagen alpine.
$ docker history alpine
IMAGE CREATED CREATED BY SIZE COMMENT
965ea09ff2eb 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:fe1f09249227e2da2… 5.55MB
Ejemplo
Vamos a consultar el historial de la imagen ubuntu.
$ docker history ubuntu
IMAGE CREATED CREATED BY SIZE COMMENT
775349758637 11 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 11 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 11 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B
<missing> 11 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 987kB
<missing> 11 days ago /bin/sh -c #(nop) ADD file:a48a5dc1b9dbfc632… 63.2MB
Referencia:
6.8. Mostrar las imágenes que tenemos descargadas
$ docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Options:
-a, --all Show all images (default hides intermediate images)
--digests Show digests
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print images using a Go template
--no-trunc Don't truncate output
-q, --quiet Only show numeric IDs
Ejemplo
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 775349758637 11 days ago 64.2MB
httpd latest d3017f59d5e2 12 days ago 165MB
alpine latest 965ea09ff2eb 3 weeks ago 5.55MB
mariadb latest a9e108e8ee8a 3 weeks ago 356MB
mediawiki latest 1d774b717f24 3 weeks ago 733MB
joomla latest 37b651a98b60 3 weeks ago 457MB
mysql latest c8ee894bd2bd 3 weeks ago 456MB
hello-world latest fce289e99eb9 10 months ago 1.84kB
6.9. Mostrar las imágenes intermedias (ocultas por defecto)
También podemos mostrar las imágenes intermedias que se han ido generando en nuestro equipo cada vez que hemos creado una nueva imagen a partir de un archivo Dockerfile.
Estas imágenes intermedias están ocultas por defecto.
Para poder mostrarlas necesitaremos utilizar la opción -a.
$ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 775349758637 10 days ago 64.2MB
alpine latest 965ea09ff2eb 2 weeks ago 5.55MB
<none> <none> 2ca708c1c9cc 7 weeks ago 64.2MB
...
Para conocer más detalles sobre este tema se recomienda la lectura del siguiente artículo: What are Docker <none>:<none> images? |
6.10. Mostrar el ID de las imágenes
Para mostrar el ID de las imágenes utilizamos la opción -q.
$ docker images -q
El siguiente comando mostraría el ID de todas las imágenes que tenemos, incluyendo las imágenes intermedias. Este comando nos será útil más adelante para eliminar todas las imágenes que tenemos en nuestro host, incluyendo las dangling images.
$ docker images -aq
Referencia:
6.11. Eliminar imágenes
$ docker rmi --help
Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
Remove one or more images
Options:
-f, --force Force removal of the image
--no-prune Do not delete untagged parents
Referencia:
6.12. Eliminar una imagen por su nombre (REPOSITORY)
Ejemplo:
En este ejemplo vamos a eliminar la imagen ubuntu.
$ docker rmi ubuntu
Untagged: ubuntu:latest
Untagged: ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d
Deleted: sha256:775349758637aff77bf85e2ff0597e86e3e859183ef0baba8b3e8fc8d3cba51c
Deleted: sha256:4fc26b0b0c6903db3b4fe96856034a1bd9411ed963a96c1bc8f03f18ee92ac2a
Deleted: sha256:b53837dafdd21f67e607ae642ce49d326b0c30b39734b6710c682a50a9f932bf
Deleted: sha256:565879c6effe6a013e0b2e492f182b40049f1c083fc582ef61e49a98dca23f7e
Deleted: sha256:cc967c529ced563b7746b663d98248bc571afdb3c012019d7f54d6c092793b8b
Referencia:
6.13. Eliminar una imagen por su ID
El IMAGE ID puede ser:
-
el identificador largo del contenedor (64 caracteres).
-
el identificador corto del contenedor (16 caracteres).
-
también es posible utilizar solamente los primeros caracteres del identificador, siempre que no existan contenedores que empiecen con esos caracteres.
Ejemplo:
En este ejemplo estamos eliminando la imagen de alpine:3.7 que tiene como IMAGE ID el valor 6d1ef012b567.
$ docker rmi 6d1ef012b567
En este caso podría haber utilizado los primeros caracteres del identificador.
$ docker rmi 6d
También es posible eliminar varias imágenes de una vez indicando una lista con todos los ID de las imágenes que queremos eliminar.
$ docker rmi 6d1ef012b567 965ea09ff2eb 4c9d84aeed9f
Referencia:
6.14. Eliminar todas las imágenes que tenemos descargadas
Para eliminar todas las imágenes podemos utilizar el siguiente comando:
$ docker rmi $(docker images -aq)
|
Recuerda que la opción
|
7. Creación y ejecución de contenedores Docker
docker run --help
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
-a, --attach list Attach to STDIN, STDOUT or STDERR
--blkio-weight uint16 Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)
--blkio-weight-device list Block IO weight (relative device weight) (default [])
--cap-add list Add Linux capabilities
--cap-drop list Drop Linux capabilities
--cgroup-parent string Optional parent cgroup for the container
--cidfile string Write the container ID to the file
--cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
--cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota
--cpu-rt-period int Limit CPU real-time period in microseconds
--cpu-rt-runtime int Limit CPU real-time runtime in microseconds
-c, --cpu-shares int CPU shares (relative weight)
--cpus decimal Number of CPUs
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
-d, --detach Run container in background and print container ID
--detach-keys string Override the key sequence for detaching a container
--device list Add a host device to the container
--device-cgroup-rule list Add a rule to the cgroup allowed devices list
--device-read-bps list Limit read rate (bytes per second) from a device (default [])
--device-read-iops list Limit read rate (IO per second) from a device (default [])
--device-write-bps list Limit write rate (bytes per second) to a device (default [])
--device-write-iops list Limit write rate (IO per second) to a device (default [])
--disable-content-trust Skip image verification (default true)
--dns list Set custom DNS servers
--dns-option list Set DNS options
--dns-search list Set custom DNS search domains
--domainname string Container NIS domain name
--entrypoint string Overwrite the default ENTRYPOINT of the image
-e, --env list Set environment variables
--env-file list Read in a file of environment variables
--expose list Expose a port or a range of ports
--gpus gpu-request GPU devices to add to the container ('all' to pass all GPUs)
--group-add list Add additional groups to join
--health-cmd string Command to run to check health
--health-interval duration Time between running the check (ms|s|m|h) (default 0s)
--health-retries int Consecutive failures needed to report unhealthy
--health-start-period duration Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)
--health-timeout duration Maximum time to allow one check to run (ms|s|m|h) (default 0s)
--help Print usage
-h, --hostname string Container host name
--init Run an init inside the container that forwards signals and reaps processes
-i, --interactive Keep STDIN open even if not attached
--ip string IPv4 address (e.g., 172.30.100.104)
--ip6 string IPv6 address (e.g., 2001:db8::33)
--ipc string IPC mode to use
--isolation string Container isolation technology
--kernel-memory bytes Kernel memory limit
-l, --label list Set meta data on a container
--label-file list Read in a line delimited file of labels
--link list Add link to another container
--link-local-ip list Container IPv4/IPv6 link-local addresses
--log-driver string Logging driver for the container
--log-opt list Log driver options
--mac-address string Container MAC address (e.g., 92:d0:c6:0a:29:33)
-m, --memory bytes Memory limit
--memory-reservation bytes Memory soft limit
--memory-swap bytes Swap limit equal to memory plus swap: '-1' to enable unlimited swap
--memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
--mount mount Attach a filesystem mount to the container
--name string Assign a name to the container
--network network Connect a container to a network
--network-alias list Add network-scoped alias for the container
--no-healthcheck Disable any container-specified HEALTHCHECK
--oom-kill-disable Disable OOM Killer
--oom-score-adj int Tune host's OOM preferences (-1000 to 1000)
--pid string PID namespace to use
--pids-limit int Tune container pids limit (set -1 for unlimited)
--privileged Give extended privileges to this container
-p, --publish list Publish a container's port(s) to the host
-P, --publish-all Publish all exposed ports to random ports
--read-only Mount the container's root filesystem as read only
--restart string Restart policy to apply when a container exits (default "no")
--rm Automatically remove the container when it exits
--runtime string Runtime to use for this container
--security-opt list Security Options
--shm-size bytes Size of /dev/shm
--sig-proxy Proxy received signals to the process (default true)
--stop-signal string Signal to stop a container (default "SIGTERM")
--stop-timeout int Timeout (in seconds) to stop a container
--storage-opt list Storage driver options for the container
--sysctl map Sysctl options (default map[])
--tmpfs list Mount a tmpfs directory
-t, --tty Allocate a pseudo-TTY
--ulimit ulimit Ulimit options (default [])
-u, --user string Username or UID (format: <name|uid>[:<group|gid>])
--userns string User namespace to use
--uts string UTS namespace to use
-v, --volume list Bind mount a volume
--volume-driver string Optional volume driver for the container
--volumes-from list Mount volumes from the specified container(s)
-w, --workdir string Working directory inside the container
Referencia:
7.1. Hello World!
En Docker Hub existe una imagen oficial que contiene un ejemplo de "Hello World!". Este contenedor lo único que hace es mostrar un mensaje de bienvenida.
El contenido del archivo Dockerfile de la imagen hello-world es el siguiente:
FROM scratch (1)
COPY hello / (2)
CMD ["/hello"] (3)
| 1 | Esta instrucción indica que está utilizando la imagen scratch como imagen base. Esta imagen es una imagen especial que se corresponde con una imagen vacía. |
| 2 | Copia el archivo hello al directorio raiz del sistema de archivos de la imagen. El archivo hello es un archivo binario que podemos ver en el mismo repositorio de GitHub donde está alojado el Dockerfile. |
| 3 | Indica que el contenedor ejecutará esta instrucción cuando se inicie. |
Vamos a ejecutar nuestro primer contenedor:
$ docker run hello-world
Veamos con detalle qué es lo que ha ocurrido.
-
En primer lugar busca si la imagen hello-world existe en el repositorio local de imágenes de nuestro equipo. Como no la ha encontrado, la ha descargado automáticamente de Docker Hub. Por lo tanto, hemos visto que no es necesario descargar la imagen previamente con
docker pull. -
En segundo lugar se crea un contenedor a partir de la imagen hello-world y se inicia.
-
Se ejecuta el archivo binario hello y cuando finaliza la ejecución el contenedor se detiene.
Listar los contenedores que están en ejecución
Para consultar los contenedores que están en ejecución actualmente utilizamos el siguiente comando:
$ docker ps
Podemos ver que actualmente no hay ningún contenedor en ejecución. El comando anterior nos devuelve la siguiente salida.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Listar todos los contenedores (los que están en ejecución y los que están detenidos)
Para poder ver los contenedores que están detenidos utilizamos la opción -a.
$ docker ps -a
Ahora obtendremos la siguiente salida.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c48138039ade hello-world "/hello" 12 seconds ago Exited (0) 12 seconds ago docha_can
-
CONTAINER ID (
c48138039ade): Es el identificador único del contenedor. -
IMAGE (
hello-world): Es el nombre de la imagen utilizada para instanciar el contenedor. -
COMMAND (
/hello): Instrucción que ejecuta el contenedor. En este caso es la que aparece en el archivo Dockerfile. -
CREATED (
12 seconds ago): Cuando fue creado el contenedor. -
STATUS (
Exited (0) 12 seconds ago): El estado actual del contenedor. En este caso el contenedor se ha ejecutado y se ha detenido, finalizando su ejecución con el código 0 que indica que ha finalizado sin errores. -
PORTS: Indica los puertos expuestos por el contenedor. En este caso está vacío porque no exponen ningún puerto.
-
NAMES (
docha_can): Nombre del contenedor. Si no le asignamos un nombre durante la creación del contenedor, Docker creará un nombre de forma automática.
Referencia:
7.2. Creación de un contenedor para ejecutar un comando
En este ejemplo vamos a utilizar Alpine Linux, una distribución Linux muy ligera. La imagen para Docker ocupa menos de 6 MB.
$ docker pull alpine
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 965ea09ff2eb 2 weeks ago 5.55MB
El contenido del archivo Dockerfile de la imagen alpine es el siguiente:
FROM scratch (1)
ADD alpine-minirootfs-3.10.3-x86_64.tar.gz / (2)
CMD ["/bin/sh"] (3)
| 1 | Esta instrucción indica que está utilizando la imagen scratch como imagen base. Esta imagen es una imagen especial que se corresponde con una imagen vacía. |
| 2 | La instrucción ADD opia el archivo alpine-minirootfs-3.10.3-x86_64.tar.gz al directorio raiz del sistema de archivos de la imagen y lo descomprime. El archivo alpine-minirootfs-3.10.3-x86_64.tar.gz es un archivo que podemos ver en el mismo repositorio de GitHub donde está alojado el Dockerfile. |
| 3 | Indica que el contenedor ejecutará esta instrucción cuando se inicie. |
Es posible ejecutar comandos dentro del contenedor indicando el comando después
del nombre de la imagen. Veamos la sintaxis de docker run.
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Por lo tanto, si queremos ejecutar el comando cat /etc/os-release dentro de un contenedor basado en la imagen alpine ejecutaríamos el siguiente comando.
$ docker run alpine cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.3
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
|
Cuando indicamos un comando a la hora de ejecutar un comando con |
En este caso el contenedor ejecutará el comando que le hemos indicado (cat
/etc/os-release) y cuando el comando termina su ejecución el contenedor se
detiene.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2d250ff407ef alpine "cat /etc/os-release" 7 seconds ago Exited (0) 7 s ago gifted_me
Mostrar la salida estándar (STDOUT) de un contenedor
Aunque el contenedor esté detenido podemos consultar sus registros de salida (STDOUT) con el comando docker logs.
La sintaxis es la siguiente:
$ docker logs [OPTIONS] CONTAINER
Donde CONTAINER puede ser:
-
el identificador largo del contenedor (64 caracteres).
-
el identificador corto del contenedor (16 caracteres).
-
el nombre del contenedor.
|
También es posible utilizar solamente los primeros caracteres del identificador |
Por ejemplo, para mostrar la salida estándar (STDOUT) del contenedor del ejemplo anterior podría haber utilizado:
$ docker logs 2d250ff407ef201f496e4e2f66d3e8fcdd8b91d828de60e70a29f6613161fc8a
$ docker logs 2d250ff407ef
$ docker logs gifted_me
$ docker logs 2d
7.3. Creación de un contenedor en modo interactivo
Para que un contenedor no se detenga al ejecutarse debemos indicarle que queremos iniciarlo en modo interactivo.
Ejemplo de creación de un contenedor con Alpine Linux
$ docker run -it --name alpinec alpine
/ #
-
docker runes el comando que nos permite crear un contenedor a partir de una imagen Docker. -
El parámetro
-inos permite interaccionar con el contenedor a través de la entrada estándar STDIN. -
El parámetro
-tnos asigna un terminal dentro del contenedor. -
Los dos parámetros
-itnos permiten usar un contenedor como si fuese una máquina virtual tradicional. -
El parámetro
--namenos permite asignarle un nombre a nuestro contenedor. Si no le asignamos un mombre Docker nos asignará un nombre automáticamente. -
alpinees el nombre de la imagen. En primer lugar buscará la imagen en local y si no está disponible la buscará en el repositorio oficial Docker Hub.
Una vez ejecutado el comando anterior nos aparece un prompt para poder interaccionar con el contenedor que acabamos de crear.
/ #
Podemos probar a escribir algunos comandos.
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.3
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
Gestión de paquetes en Alpine Linux
El gestor de paquetes de Alpine Linux es apk. En la documentación oficial podemos encontrar más detalles sobre cómo usarlo.
Si quisiéramos instalar nano en el contenedor tendríamos que hacer lo siguiente.
1) Actualizar el índice de paquetes disponibles
apk update
2) Añadir el nuevo paquete al sistema.
apk add nano
Para salir del contenedor escribimos el comando exit.
exit
Como no hemos iniciado el contenedor con el parámetro --rm, cuando el contenedor se detiene no se elimina y ocupa espacio en nuestro disco. Podemos comprobarlo con el siguiente comando.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e9af6c8dbffb alpine "/bin/sh" 9 seconds ago Exited (0) 9 s ago dazz_bra
Eliminar un contenedor
Para eliminar el contenedor que está detenido y está ocupando espacio en nuestro disco ejecutaremos el comando docker rm. Este comando nos permite eliminar un contenedor indicando su ID su nombre.
Para el ejemplo anterior podríamos utilizar cualquiera de estas dos opciones.
$ doker rm alpinec
o
$ doker rm e9af6c8dbffb
|
A partir de este momento siempre que vayamos a crear un nuevo contenedor añadiremos el parámetro |
|
Para salir del contenedor y detenerlo:
Para salir del contenedor SIN detenerlo:
|
7.4. attach y exec
7.4.1. attach
$ docker attach --help
Usage: docker attach [OPTIONS] CONTAINER
Attach local standard input, output, and error streams to a running container
Options:
--detach-keys string Override the key sequence for detaching a container
--no-stdin Do not attach STDIN
--sig-proxy Proxy all received signals to the process (default true)
Nos permite acceder al terminal de un contenedor que está en ejecución indicando su nombre o su ID. Tenga en cuenta que no crea un nuevo terminal (tty), sino que usa el terminal original que está en ejecución de modo que si salimos del terminal con exit el contenedor se detendrá.
Ejemplo
Ejecutamos un contenedor en modo detached (-d) y le añadimos la opción (-it) para poder interaccionar con él a través de un terminal.
Vamos a ejecutar en el contendor el comando /usr/bin/top en modo batch (-b) para que siga ejecutándose en segundo plano. Si no utilizásemos el modo batch el contenedor se dentendría una vez que finaliza la ejecución del comando.
$ docker run -dit \
--rm \
--name topdemo \
ubuntu /usr/bin/top -b
Comprobamos que el contenedor está ejecutándose en segundo plano.
$ docker ps
Ahora podríamos acceder al terminal el contenedor que está en ejecución con attach.
$ docker attach topdemo
|
7.4.2. exec
docker exec --help
Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
Run a command in a running container
Options:
-d, --detach Detached mode: run command in the background
--detach-keys string Override the key sequence for detaching a container
-e, --env list Set environment variables
-i, --interactive Keep STDIN open even if not attached
--privileged Give extended privileges to the command
-t, --tty Allocate a pseudo-TTY
-u, --user string Username or UID (format: <name|uid>[:<group|gid>])
-w, --workdir string Working directory inside the container
Nos permite ejecutar un comando en un contenedor que está en ejecución indicando su nombre o su ID. exec ejecuta el comado en un proceso nuevo, asignándonos un nuevo terminal.
Esto significa que si salimos del contenedor con exit, el contenedor no detendrá su ejecución.
Cuando queramos conectarnos a un contenedor que está en ejecución podemos abrir un nuevo terminal con exec.
$ docker exec -it container_name /bin/sh
Ejemplo
Vamos a utilizar el ejemplo anterior.
Ejecutaremos el comando /usr/bin/top en modo batch (-b) para que siga ejecutándose en segundo plano. Si no utilizásemos el modo batch el contenedor se dentendría una vez que finaliza la ejecución del comando.
$ docker run -dit \
--rm \
--name topdemo \
ubuntu /usr/bin/top -b
Comprobamos que el contenedor está ejecutándose en segundo plano.
$ docker ps
Ahora vamos a ejecutar el comando /bin/bash para crear un nuevo terminal sobre el contenedor.
$ docker exec -it topdemo /bin/sh
Una vez ejecutado el comando anterior nos aparece un prompt para poder interaccionar con el contenedor que acabamos de crear.
/ #
Si ejecutamos ps aux para ver los procesos que están en ejecución dentro del contenedor veremos lo siguiente.
# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 36480 3032 pts/0 Ss+ 10:25 0:00 /usr/bin/top -b
root 16 0.6 0.0 4624 816 pts/1 Ss 10:28 0:00 /bin/sh
root 23 0.0 0.1 34396 2816 pts/1 R+ 10:28 0:00 ps aux
Vemos como el proceso con PID 1 es el que está ejecutando el comando /usr/bin/top -b y el proceso con PID 16 es el del comando /bin/sh que es el que hemos ejecutado don docker exec.
Referencia:
7.5. Eliminar contenedores
7.5.1. rm
$ docker rm --help
Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
Remove one or more containers
Options:
-f, --force Force the removal of a running container (uses SIGKILL)
-l, --link Remove the specified link
-v, --volumes Remove the volumes associated with the container
Eliminar un contenedor que está detenido
$ docker rm httpdc
Eliminar un contenedor que está en ejecución
$ docker rm -f httpdc
Al utilizar el parámetro -f puedo eliminar un contenedor que está en ejecución. De otro modo, tendría que detener el contenedor y luego eliminarlo.
Eliminar todos los contenedores que están detenidos
$ docker rm $(docker ps -aq)
Eliminar todos los contenedores (en ejecución y detenidos)
$ docker rm -f $(docker ps -aq)
Referencia:
7.6. stop y start
7.6.1. stop
docker start --help
Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
Start one or more stopped containers
Options:
-a, --attach Attach STDOUT/STDERR and forward signals
--detach-keys string Override the key sequence for detaching a container
-i, --interactive Attach container's STDIN
7.6.2. start
docker stop --help
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
Stop one or more running containers
Options:
-t, --time int Seconds to wait for stop before killing it (default 10)
7.7. Creación de un contenedor en segundo plano
Hasta este momento hemos visto dos formas usar los contenedores:
-
Creamos un contenedor para ejecutar un comando dentro de él, esperamos a que finalice el comando y cuando el comando finaliza el contenedor se detiene.
-
Creamos un contenedor en modo interactivo, donde podemos acceder a un terminal y ejecutar comandos en él.
Existe otra posibilidad, que además es la más utilizada, consiste en la ejecución de un contenedor en segundo plano mientras que éste ejecuta una aplicación en primer plano.
Para ejecutar un contenedor en segundo plano se utiliza la opción -d (detach).
Ejemplo
Vamos a ejecutar un contenedor que ejecuta un servidor web en primer plano. Mientras que el servidor web esté en ejecución el contenedor también lo estará.
Utilizaremos la imagen oficial httpd, que es de Apache HTTP Server.
$ docker run -d \
--rm \
--name httpdc \
httpd
Comprobamos que el contenedor está en ejecución:
$ docker ps
Para ver los registros de salida (STDOUT) del contenedor podemos utilizar el comando docker logs. Si utilizamos la opción -f la salida se irá actualizando automáticamente.
$ docker logs -f httpdc
Con el comando docker inspect podemos obtener información sobre el contenedor.
$ docker inspect httpdc
|
El comando |
Si buscamos la dirección IP del contenedor vemos que es una dirección privada del tipo 172.17.0.x.
$ docker inspect httpdc| grep IPAddress
"IPAddress": "172.17.0.2
El contenedor está en la red bridge y por lo tanto sólo es accesible desde el servidor que está ejecutando el servicio de Docker o desde otro contenedor de la misma red (En Linux, en macOS tiene otro comportamiento).
# curl http://172.17.0.2
<html><body><h1>It works!</h1></body></html>
Por lo tanto, para acceder al servidor web desde nuestra red y no sólo desde el servidor que está ejecutando el servicio de Docker tendremos que exponer los puertos.
Eliminamos el contenedor.
$ docker rm -f httpdc
7.8. Exponer los puertos
Consiste en reservar un puerto del servidor de Docker con el objetivo de redirigir las peticiones a un puerto específico de un contenedor.
Existen dos opciones:
-
-p
Con esta opción tenemos que indicar qué puerto local de nuestra máquina vamos a redireccionar con el puerto del contenedor. Si el puerto local que indicamos ya está en uso, obtendremos un error y el contenedor no se creará.
La sintaxis para indicar los puertos será puerto_local:puerto_contenedor
En el siguiente ejemplo vamos a redireccionar el puerto 81 de nuestra máquina con el puerto 80 del contenedor.
$ docker run -d \
--rm \
--name httpdc \
-p 81:80 \
httpd
Comprobamos que el contenedor está ejecutándose.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3ac8d4400ab2 httpd "httpd-foreground" 6 seconds ago Up 5 seconds 0.0.0.0:81->80/tcp httpdc
Comprobamos que podemos acceder al contenido del contenedor desde un navegador web accediendo a la URL http://localhost:81, o desde el terminal con el comando curl.
$ curl http://localhost:81
<html><body><h1>It works!</h1></body></html>
-
-P
Con esta opción no tenemos que indicar el puerto local, será seleccionado aleatoriamente entre los puertos que estén libres. El puerto del contenedor con el que hacemos la redirección estará definido en el archivo Dockerfile con el que se ha creado la imagen del contentedor. Tenga en cuenta que en el archivo Dockerfile se pueden exponer varios puertos a la vez.
En el siguiente ejemplo se seleccionará un puerto aleaotrio de nuestra máquina y se redirigirá al puerto 80 del contenedor.
Podemos consultar el archivo Dockerfile de la imagen httpd en Docker Hub y comprobar que el puerto que expone esta imagen es el 80. Los puertos aparecen definidos con la instrucción EXPOSE.
$ docker run -d \
--rm \
--name httpdc \
-P \
httpd
Para conocer cuál es el puerto aleatorio de nuestra máquina que se ha utilizado ejecutamos el siguiente comando.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96922862a483 httpd "httpd-foreground" 3 seconds ago Up 1 second 0.0.0.0:32770->80/tcp httpdc
Aquí podemos ver que el puerto aleatorio que se ha seleccionado es el puerto 32770.
Comprobamos que podemos acceder al contenido del contenedor desde un navegador web accediendo a la URL http://localhost:32770, o desde el terminal con el comando curl.
$ curl http://localhost:32770
<html><body><h1>It works!</h1></body></html>
7.9. Copiar archivos/carpetas
7.9.1. cp
$ docker cp --help
Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
Copy files/folders between a container and the local filesystem
Use '-' as the source to read a tar archive from stdin
and extract it to a directory destination in a container.
Use '-' as the destination to stream a tar archive of a
container source to stdout.
Options:
-a, --archive Archive mode (copy all uid/gid information)
-L, --follow-link Always follow symbol link in SRC_PATH
7.10. Crear un contenedor con un volumen (de tipo bind mount)
Para conocer más detalles sobre la gestión de volúmenes se recomienda ver la sección almacenamiento en Docker.
En este ejemplo vamos a utilizar un volumen de tipo bind mount, que será un directorio de nuestra máquina host que vamos a montar en un directorio dentro del contenedor. El directorio que vamos a indicar dentro del contenedor no tiene por qué existir previamente.
Con bind mount podemos montar archivos o directorios.
¿Cuándo sería apropiado utilizar un volumen de tipo bind mount?
-
Para compartir archivos de configuración entre la máquina host y el contenedor.
-
Para compartir el código de las aplicaciones entre la máquina host y el contenedor en un entorno de desarrollo.
Para montar un directorio podemos utilizar los flags -v o --mount. En nuestros ejemplos utilizaremos -v.
En el caso de los bind mounts tendremos tres campos separados por dos puntos (:) y tendrán el siguiente orden:
-
En primer lugar se indica el archivo o directorio de la máquina host.
-
En segundo lugar se indica en qué archivo o directorio lo vamos a montar dentro del contenedor.
-
El tercer parámetro es opcional, y puede ser una lista separada por comas con las siguientes opciones:
ro,consistent,delegated,cached,zyZ.
Por lo tanto, la sintaxis para las dos formas de crear un bind_mount serán:
-
path_directorio_host:path_directorio_contenedor
-
path_directorio_host:path_directorio_contenedor:ro
Ejemplos de cómo ejecutar un contenedor con un bind_mount
En los siguientes ejemplos vamos a montar el directorio /home/josejuan/target de nuestra máquina local en el directorio /app del contenedor.
Podemos hacerlo de tres formas:
1) Indicar el path completo del directorio que queremos montar en el contenedor.
$ docker run -d \
--rm \
--name devtest \
-v /home/josejuan/target:/app \
nginx
2) Utilizar la salida del comando pwd para construir el path.
$ docker run -d \
--rm \
--name devtest \
-v "$(pwd)"/target:/app \
nginx
3) Utilizar la variable $PWD para construir el path.
$ docker run -d \
--rm \
--name devtest \
-v "$PWD"/target:/app \
nginx
Podemos inspeccionar el contenido del contenedor para verificar que el directorio se ha creado correctamente.
$ docker inspect devtest
"Mounts": [
{
"Type": "bind",
"Source": "/home/josejuan/target",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
|
Cuando realizamos un bind mount sobre un directorio del contenedor que no está vacío, el contenido de este directorio será reemplazado por el contenido del directorio del host. Hay que tener cuidado con esto porque puede provocar comportamientos no esperados. En el siguiente ejemplo estamos reemplazando el directorio
|
Crear bind_mounts de sólo lectura
Es posible indicar que el contenido del directorio que estamos montando en el contenedor sea de sólo lectura, añadiendo el flag ro a la lista de parámetros de creación del volumen.
$ docker run -d \
--rm \
--name devtest \
-v "$PWD"/target:/app:ro \
nginx
Inspeccionamos el contenido del contenedor para verificar que el directorio se ha creado correctamente.
$ docker inspect devtest
"Mounts": [
{
"Type": "bind",
"Source": "/home/josejuan/target",
"Destination": "/app",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
],
Referencias:
7.11. Creación de un contendedor con Apache y PHP 7.2 (en segundo plano)
En este caso vamos a utilizar la imagen oficial php:7.2-apache.
Esta imagen está configurada para servir el contenido que se encuentre dentro
del directorio /var/www/html.
$ docker run -d \
--rm \
--name apache_php \
-p 80:80 \
-v "$PWD":/var/www/html \
php:7.2-apache
Comprobamos que el contenedor está en ejecución:
$ docker ps
Creamos el archivo info.php en nuestro directorio de trabajo actual con el
siguiente contenido:
<?php
phpinfo();
?>
Ahora vamos a conectarnos a un terminal del contenedor para comprobar que el volumen se ha montado correctamente.
$ docker exec -it apache_php /bin/bash
Abrimos un navegador y accedemos a la URL
http://localhost/info.php para comprobar que la
página info.php se sirve correctamente.
También podemos hacer la comprobación desde la línea de comandos con curl.
$ curl http://localhost/info.php
7.12. Creación de un contenedor con MySQL sin persistencia de datos (en segundo plano)
Vamos a utilizar la imagen oficial mysql.
$ docker run -d \
--rm \
--name mysqlc \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
mysql:5.7.28
Abrimos un terminal en el contenedor para interaccionar con él.
$ docker exec -it mysqlc /bin/bash
Una vez que estamos dentro del contenedor nos conectamos desde la consola de MySQL.
# mysql -u root -p
Creamos una nueva base de datos con los siguientes datos.
DROP DATABASE IF EXISTS tienda;
CREATE DATABASE tienda CHARSET utf8mb4;
USE tienda;
CREATE TABLE fabricante (
codigo INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL
);
CREATE TABLE producto (
codigo INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
precio DOUBLE NOT NULL,
codigo_fabricante INT UNSIGNED NOT NULL,
FOREIGN KEY (codigo_fabricante) REFERENCES fabricante(codigo)
);
INSERT INTO fabricante VALUES(1, 'Asus');
INSERT INTO fabricante VALUES(2, 'Lenovo');
INSERT INTO fabricante VALUES(3, 'Hewlett-Packard');
INSERT INTO fabricante VALUES(4, 'Samsung');
INSERT INTO fabricante VALUES(5, 'Seagate');
INSERT INTO fabricante VALUES(6, 'Crucial');
INSERT INTO fabricante VALUES(7, 'Gigabyte');
INSERT INTO fabricante VALUES(8, 'Huawei');
INSERT INTO fabricante VALUES(9, 'Xiaomi');
INSERT INTO producto VALUES(1, 'Disco duro SATA3 1TB', 86.99, 5);
INSERT INTO producto VALUES(2, 'Memoria RAM DDR4 8GB', 120, 6);
INSERT INTO producto VALUES(3, 'Disco SSD 1 TB', 150.99, 4);
INSERT INTO producto VALUES(4, 'GeForce GTX 1050Ti', 185, 7);
INSERT INTO producto VALUES(5, 'GeForce GTX 1080 Xtreme', 755, 6);
INSERT INTO producto VALUES(6, 'Monitor 24 LED Full HD', 202, 1);
INSERT INTO producto VALUES(7, 'Monitor 27 LED Full HD', 245.99, 1);
INSERT INTO producto VALUES(8, 'Portátil Yoga 520', 559, 2);
INSERT INTO producto VALUES(9, 'Portátil Ideapd 320', 444, 2);
INSERT INTO producto VALUES(10, 'Impresora HP Deskjet 3720', 59.99, 3);
INSERT INTO producto VALUES(11, 'Impresora HP Laserjet Pro M26nw', 180, 3);
Comprobamos que la base de datos se ha creado correctamente.
mysql> SHOW TABLES;
+------------------+
| Tables_in_tienda |
+------------------+
| fabricante |
| producto |
+------------------+
2 rows in set (0.00 sec)
Comprobamos que las tablas tienen datos.
mysql> SELECT * FROM fabricante;
+--------+-----------------+
| codigo | nombre |
+--------+-----------------+
| 1 | Asus |
| 2 | Lenovo |
| 3 | Hewlett-Packard |
| 4 | Samsung |
| 5 | Seagate |
| 6 | Crucial |
| 7 | Gigabyte |
| 8 | Huawei |
| 9 | Xiaomi |
+--------+-----------------+
9 rows in set (0.00 sec)
mysql> SELECT * FROM producto;
+--------+---------------------------------+--------+-------------------+
| codigo | nombre | precio | codigo_fabricante |
+--------+---------------------------------+--------+-------------------+
| 1 | Disco duro SATA3 1TB | 86.99 | 5 |
| 2 | Memoria RAM DDR4 8GB | 120 | 6 |
| 3 | Disco SSD 1 TB | 150.99 | 4 |
| 4 | GeForce GTX 1050Ti | 185 | 7 |
| 5 | GeForce GTX 1080 Xtreme | 755 | 6 |
| 6 | Monitor 24 LED Full HD | 202 | 1 |
| 7 | Monitor 27 LED Full HD | 245.99 | 1 |
| 8 | Porttil Yoga 520 | 559 | 2 |
| 9 | Porttil Ideapd 320 | 444 | 2 |
| 10 | Impresora HP Deskjet 3720 | 59.99 | 3 |
| 11 | Impresora HP Laserjet Pro M26nw | 180 | 3 |
+--------+---------------------------------+--------+-------------------+
11 rows in set (0.00 sec)
Una vez que hemos llegado a este punto tendríamos la base de datos almacenada dentro del sistema de archivos del contenedor, de modo que si elimimanos el contenedor y volvemos a crear uno nuevo con el mismo comando, no tendríamos acceso a la base de datos. Vamos a comprobarlo.
Comprobamos que el contenedor está en ejecución.
$ docker ps
Detenemos el contenedor. Como hemos iniciado el contenedor con la opción --rm al detenerlo se eliminará automáticamente.
$ docker stop mysqlc
Comprobamos que el contenedor se ha detenido y se ha eliminado correctamente.
$ docker ps -a
7.13. Creación de un contenedor con MySQL con persistencia de datos (en segundo plano)
Vamos a utilizar la imagen oficial mysql.
Existen dos formas de añadir persistencia de datos:
-
Crear un volumen interno gestionado por Docker.
-
Crear un volumen de tipo bind mount donde montamos un directorio de nuestra máquina local en un directorio dentro del contenedor.
Solución 1. Crear un volumen interno gestionado por Docker
$ docker volume create mysql_data
$ docker run -d \
--rm \
--name mysqlc \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Abrimos un terminal en el contenedor para interaccionar con él.
$ docker exec -it mysqlc /bin/bash
Una vez que estamos dentro del contenedor nos conectamos desde la consola de MySQL.
# mysql -u root -p
Creamos una nueva base de datos con los siguientes datos.
DROP DATABASE IF EXISTS tienda;
CREATE DATABASE tienda CHARSET utf8mb4;
USE tienda;
CREATE TABLE fabricante (
codigo INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL
);
CREATE TABLE producto (
codigo INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
precio DOUBLE NOT NULL,
codigo_fabricante INT UNSIGNED NOT NULL,
FOREIGN KEY (codigo_fabricante) REFERENCES fabricante(codigo)
);
INSERT INTO fabricante VALUES(1, 'Asus');
INSERT INTO fabricante VALUES(2, 'Lenovo');
INSERT INTO fabricante VALUES(3, 'Hewlett-Packard');
INSERT INTO fabricante VALUES(4, 'Samsung');
INSERT INTO fabricante VALUES(5, 'Seagate');
INSERT INTO fabricante VALUES(6, 'Crucial');
INSERT INTO fabricante VALUES(7, 'Gigabyte');
INSERT INTO fabricante VALUES(8, 'Huawei');
INSERT INTO fabricante VALUES(9, 'Xiaomi');
INSERT INTO producto VALUES(1, 'Disco duro SATA3 1TB', 86.99, 5);
INSERT INTO producto VALUES(2, 'Memoria RAM DDR4 8GB', 120, 6);
INSERT INTO producto VALUES(3, 'Disco SSD 1 TB', 150.99, 4);
INSERT INTO producto VALUES(4, 'GeForce GTX 1050Ti', 185, 7);
INSERT INTO producto VALUES(5, 'GeForce GTX 1080 Xtreme', 755, 6);
INSERT INTO producto VALUES(6, 'Monitor 24 LED Full HD', 202, 1);
INSERT INTO producto VALUES(7, 'Monitor 27 LED Full HD', 245.99, 1);
INSERT INTO producto VALUES(8, 'Portátil Yoga 520', 559, 2);
INSERT INTO producto VALUES(9, 'Portátil Ideapd 320', 444, 2);
INSERT INTO producto VALUES(10, 'Impresora HP Deskjet 3720', 59.99, 3);
INSERT INTO producto VALUES(11, 'Impresora HP Laserjet Pro M26nw', 180, 3);
Comprobamos que la base de datos se ha creado correctamente.
mysql> SHOW TABLES;
+------------------+
| Tables_in_tienda |
+------------------+
| fabricante |
| producto |
+------------------+
2 rows in set (0.00 sec)
Comprobamos que las tablas tienen datos.
mysql> SELECT * FROM fabricante;
+--------+-----------------+
| codigo | nombre |
+--------+-----------------+
| 1 | Asus |
| 2 | Lenovo |
| 3 | Hewlett-Packard |
| 4 | Samsung |
| 5 | Seagate |
| 6 | Crucial |
| 7 | Gigabyte |
| 8 | Huawei |
| 9 | Xiaomi |
+--------+-----------------+
9 rows in set (0.00 sec)
mysql> SELECT * FROM producto;
+--------+---------------------------------+--------+-------------------+
| codigo | nombre | precio | codigo_fabricante |
+--------+---------------------------------+--------+-------------------+
| 1 | Disco duro SATA3 1TB | 86.99 | 5 |
| 2 | Memoria RAM DDR4 8GB | 120 | 6 |
| 3 | Disco SSD 1 TB | 150.99 | 4 |
| 4 | GeForce GTX 1050Ti | 185 | 7 |
| 5 | GeForce GTX 1080 Xtreme | 755 | 6 |
| 6 | Monitor 24 LED Full HD | 202 | 1 |
| 7 | Monitor 27 LED Full HD | 245.99 | 1 |
| 8 | Porttil Yoga 520 | 559 | 2 |
| 9 | Porttil Ideapd 320 | 444 | 2 |
| 10 | Impresora HP Deskjet 3720 | 59.99 | 3 |
| 11 | Impresora HP Laserjet Pro M26nw | 180 | 3 |
+--------+---------------------------------+--------+-------------------+
11 rows in set (0.00 sec)
Una vez que hemos llegado a este punto tendríamos la base de datos almacenada en el volumen mysql_data, de modo que si elimimanos el contenedor y volvemos a crear uno nuevo que haga uso del mismo volumen, tendríamos acceso a la misma base de datos. Vamos a comprobarlo.
Comprobamos que el contenedor está en ejecución.
$ docker ps
Detenemos el contenedor. Como hemos iniciado el contenedor con la opción --rm al detenerlo se eliminará automáticamente.
$ docker stop mysqlc
Comprobamos que el contenedor se ha detenido y se ha eliminado correctamente.
$ docker ps -a
7.14. Inicializar un contenedor de MySQL con una Base de Datos
La imagen oficial de mysql, ejecuta los archivos con extensión .sh, .sql y .sql.gz que se encuentren en el directorio /docker-entrypoint-initdb.d. Estos archivos serán ejecutados por orden alfabético.
Gracias a esta funcionalidad es muy sencillo importar una base de datos en nuestro contenedor de forma automática con un solo comando.
Lo único que necesitamos es crear un nuevo directorio en nuestro directorio de
trabajo que contenga los scripts SQL que queremos importar en el contenedor.
Este directorio local con los scripts SQL tendremos que montarlo sobre el
directorio /docker-entrypoint-initdb.d del sistema de ficheros del contenedor.
Los scripts SQL se importarán por defecto en la base de datos que se haya
indicado en la variable de entorno MYSQL_DATABASE.
Ejemplo
El siguiente ejemplo ejectuaría en el contenedor todos los scripts SQL que se encuentren en el directorio sql de nuestro directorio local de trabajo.
$ docker run -d \
--rm \
--name mysqlc \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
-v mysql_data:/var/lib/mysql \
-v "$PWD/sql":/docker-entrypoint-initdb.d \
mysql:5.7.28
7.15. Conectar un contenedor con Adminer con MySQL
Adminer es una herramienta que permite administrar contenido de bases de datos MySQL desde un sitio web. Se distribuye en un solo archivo PHP.
Para este ejemplo usaremos la imagen oficial de adminer.
Para conectar dos contenedores podemos hacerlo de dos formas:
-
Utilizando
legacy container linkscon el flag--link, en labridge network. -
Utilizando una
user-defined bridge network.
Solución 1. Legacy container links con el flag --link, en la bridge network
Los enlaces permiten que los contenedores se descubran entre sí y transfieran de manera segura información sobre un contenedor a otro contenedor. Para crear un enlace se utiliza el flag --link.
En primer lugar debe existir un contenedor con MySQL Server.
$ docker run -d \
--rm \
--name mysqlc \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Una vez que la instancia de MySQL está en ejecución podemos crear el contenedor con Adminer.
$ docker run -d \
--rm \
--link mysqlc \
-p 8080:8080 \
adminer
Con el flag --link mysqlc hemos creado un enlace entre el contenedor mysql y adminer.
En el archivo /etc/hosts del contenedor adminer se ha añadido una nueva línea que permite resolver la dirección IP del contenedor de MySQL a partir de su nombre (mysqlc) o su ID (8411f6064e44).
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 mysqlc 8411f6064e44
172.17.0.4 c25ca9a48fb3
Comprobamos que el contendedor adminer puede conectar con el contenedor mysql abriendo un navegador web y accediendo a la URL: http://localhost:8080.
|
Tenga en cuenta que el nombre del servidor al que queremos conectarnos no es db, sino que es mysqlc, que es el nombre del contenedor de MySQL. |
Solución 2. Utilizando una user-defined bridge network
En primer lugar creamos una user-defined bridge network.
$ docker network create my-net
Creamos un contenedor con MySQL indicando que queremos que esté en la red --network my-net.
$ docker run -d \
--rm \
--name mysqlc \
--network my-net \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Creamos un contenedor con Adminer indicando que queremos que esté en la red --network my-net.
$ docker run -d \
--rm \
--network my-net \
-p 8080:8080 \
adminer
Comprobamos que el contendedor adminer puede conectar con el contenedor mysql abriendo un navegador web y accediendo a la URL: http://localhost:8080.
|
Tenga en cuenta que el nombre del servidor al que queremos conectarnos no es db, sino que es mysqlc, que es el nombre del contenedor de MySQL. |
Para eliminar la red que hemos creado ejecutamos lo siguiente.
$ docker network rm my-net
Referencias:
7.16. Conectar un contenedor phpMyAdmin con MySQL
Para este ejemplo usaremos la imagen oficial de phpmyadmin.
Para conectar dos contenedores podemos hacerlo de dos formas:
-
Utilizando
legacy container linkscon el flag--link, en labridge network. -
Utilizando una
user-defined bridge network.
Solución 1. Legacy container links con el flag --link, en la bridge network
$ docker run -d \
--rm \
--name mysqlc \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Le pasamos la variable de entorno -e PMA_ARBITRARY=1 para que en la página principal de phpMyAdmin me aparezca un campo en el formulario donde pueda indicar el servidor al que quiero conectarme.
$ docker run -d \
--rm \
--link mysqlc \
-e PMA_ARBITRARY=1 \
-p 8080:80 \
phpmyadmin/phpmyadmin
Solución 2. Utilizando una user-defined bridge network
En primer lugar creamos una user-defined bridge network.
$ docker network create my-net
Creamos un contenedor con MySQL indicando que queremos que esté en la red --network my-net.
$ docker run -d \
--rm \
--name mysqlc \
--network my-net \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Creamos un contenedor con phpMyAdmin indicando que queremos que esté en la red --network my-net.
$ docker run -d \
--rm \
--network my-net \
-e PMA_ARBITRARY=1 \
-p 8080:80 \
phpmyadmin/phpmyadmin
Comprobamos que el contendedor phpMyAdmin puede conectar con el contenedor mysql abriendo un navegador web y accediendo a la URL: http://localhost:8080.
Para eliminar la red que hemos creado ejecutamos lo siguiente.
$ docker network rm my-net
7.17. Docker restart policies (--restart)
Docker nos permite establecer políticas de reinicio para controlar si los contenedores deben reiniciarse automáticamente cuando finalizan por algún motivo o cuando el servicio de Docker se reinicia.
Para configurar una política de renicio en un contenedor se utiliza el flag --restart.
Las diferentes políticas de reinicio que podemos configurar son:
| Flag | Descripción |
|---|---|
|
El contenedor no se reinicia. Es la opción por defecto. |
|
El contenedor se reinicia si finaliza por un error, es decir, cuando el exit code es distinto de 0. |
|
El contenedor se reinicia cada vez que se detiene. Si se detiene manualmente, sólo se reinicia cuando el servicio de Docker se reinicia o cuando se reinicia manualmente. |
|
Es similar a |
|
Si iniciamos un contenedor con alguna política de reinicio no podremos utilizar el flag |
Ejemplo
En este ejemplo vamos a intentar ejecutar un contenedor con el servicio de MySQL con una política de reinicio de tipo always, pero no vamos a poder hacerlo porque la opción --rm es icompatible con las políticas de reinicio.
$ docker run -d \
--rm \
--restart always \
--name mysqlc \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Para poder aplicar la política de reinicio always deberemos eliminar el flag --rm.
$ docker run -d \
--restart always \
--name mysqlc \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Referencias:
8. Portainer
Portainer es una herramienta web open-source que permite gestionar contenedores Docker de forma local o remota.
Portainer nos permite realizar las siguientes tareas:
-
Gestionar contenedores de Docker
-
Acceder a la consola del contenedor
-
Gestionar imágenes de Docker
-
Etiquetar y subir imágenes Docker
-
Gestionar redes de Docker
-
Gestionar volúmenes de Docker
-
Navegar por los eventos de Docker
-
Preconfigurar templates de contenedores
-
Vista de clúster con Docker Swarm
Fuente: Wikipedia
8.1. Gestión de un servidor local
Portainer está compuesto por dos elementos, un servidor y un agente. En nuestro caso sólo vamos a gestionar un servidor local, por lo tanto no será necesario utilizar el agente.
Aquí vamos a diferenciar dos escenarios:
1. Gestión de un servidor local Linux, Mac, o Windows 10 ejecutándose en modo "Linux containers".
Los comandos Docker para ejecutar Portainer son los siguientes:
$ docker volume create portainer_data
$ docker run -d \
--rm \
-p 9000:9000 \
--name portainerc \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer-data:/data \
portainer/portainer
-
-dnos permite ejecutar el contenedor en modo detached, es decir, ejecutándose en segundo plano. -
--rmnos permite eliminar el contenedor cuando finaliza su ejecución. -
-p 9000:9000nos permite para mapear el puerto 9000 del host (nuestra máquina) con el puerto 9000 expuesto en el contenedor. -
--name portainernos permite asignarle un nombre al contenedor -
-v /var/run/docker.sock:/var/run/docker.socknos permite montar el sock UNIX del Docker daemon en el contenedor de portainer. -
-v portainer-data:/datanos permite crear un volumen para persistir la configuración de Portainer fuera del contenedor.
Para gestionar un servidor local de Docker donde se está ejecutando un contenedor de Portainer es necesario montar el socket UNIX del Docker daemon (/var/run/docker.sock).
|
El Docker daemon puede recibir peticiones de la API del Docker Engine utilizando tres tipos de sockets: Puede encontrar más información sobre los sockets del daemon de Docker en la documentación oficial. |
Una vez hecho esto podemos acceder con un navegador web al puerto 9000 de nuestra máquina.
Al acceder tendremos que configurar una contraseña para el usuario admin.
En el siguiente paso tendremos que indicar que queremos administrar un Servidor Local.
2. Gestión de un servidor local Windows ejecutándose en modo "Windows containers".
Los comandos Docker para ejecutar Portainer son los siguientes:
$ docker volume create portainer_data
$ docker run -d \
--rm \
-p 9000:9000 \
--name portainerc \
-v \\.\pipe\docker_engine:\\.\pipe\docker_engine \
-v portainer_data:C:\data \
portainer/portainer portainer/portainer
Referencias:
8.2. Gestión de un servidor remoto
Para configurar el acceso remoto a través del API de Docker, hay que modificar cómo arranca el Docker daemon.
Tenga en cuenta que habililtar el acceso remoto a través de la API de Docker puede suponer un riesgo de seguridad si no se realiza correctamente, por lo que se recomienda revisar la documentación oficial: Protect the Docker daemon socket.
Los puertos que se utilizan para el acceso remoto son el 2375 para comunicaciones no encriptadas y el 2376 para comunicaciones encriptadas.
Habilitar el acceso remoto a la API de Docker
En primer lugar habrá que habilitar el acceso remoto a la API de Docker. Puede encontrar información de cómo hacerlo en la siguiente referencia de la documentación oficial: How do I enable the remote API for dockerd.
Ejecutar Portainer indicando la IP del servidor remoto
El comando Docker para ejecutar Portainer es el siguiente:
$ docker run -d \
-p 9000:9000 \
--name portainerc \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer-data:/data \
portainer/portainer -H tcp://192.168.21.100:2375
Donde 192.168.21.100 se corresponde con la IP del servidor remoto de Docker y 2375 el puerto donde el Docker daemon está escuchando las peticiones.
|
Tenga en cuenta que tendrá que reemplazar la dirección IP |
Referencia:
9. Redes en Docker
Cuando instalamos Docker Engine se crean tres redes:
-
bridge: Es la red que usarán por defecto todos los contenedores que se ejecutan en el mismo host. También se les conoce como las
default bridge network. En Linux durante la instalación se crea una nueva interfaz de red virtual llamada docker0. Cuando ejecutamos un contenedor, esta es la red que utilizará por defecto a no ser que indiquemos lo contrario. -
none: En esta red el contenedor no tendrá asociada ninguna interfaz de red, sólo tendrá la de loopback (lo).
-
host: En esta red el contenedor tendrá la misma configuración que el servidor Docker Engine donde se esté ejecutando.
Además de estas redes, también es posible crear las user-defined bridge
network.
|
En la documentación oficial de Docker aseguran que las redes |
9.1. Diferencias entre las redes default bridge y user-defined bridge
-
Los contenedores que pertenecen a la misma red
user-defined bridgeexponen todos los puertos entre ellos y ninguno al exterior. -
Las redes
user-defined bridgeofrecen un DNS automático entre los contenedores de la misma red, mientras que los contenedores de una reddefault bridgesólo pueden acceder a los otros contenedores a través de su IP o haciendo uso de la opción--linkque está consideradalegacy. -
Las redes
user-defined bridgepermiten que un contenedor en ejecución pueda ser añadido o eliminado de la red, mientras que la redesdefault bridgeno lo permiten.
Puede encontrar más diferencias en la documentación oficial.
Ejemplo
La siguiente imagen muestra un ejemplo de tres contenedores que están dentro del
mismo host. El contenedor c1 está conectado a la red default bridge y los
contenedores c2 y c3 están conectados a una red de tipo user-defined
bridge que se llama my_bridge.
El comando para gestionar las redes es docker network.
$ docker network --help
Usage: docker network COMMAND
Manage networks
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks
Run 'docker network COMMAND --help' for more information on a command.
Ver la lista de redes disponibles
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
f3ecd102593d bridge bridge local
851c1a03b334 host host local
878629111af9 none null local
Inspeccionar una red
$ docker inspect bridge
Referencias:
10. Almacenamiento en Docker
Por defecto, todos los archivos que se crean dentra de un contenedor se almacenan en la última capa del sistema de archivos (la capa de lectura/escritura), esto quiere decir que los datos que tenemos en esta capa se perderán cuando el contenedor se elimine y no podremos compartirlos con otros contenedores.
Docker nos ofrece dos posibilidades para implementar persistencia de datos en los contenedores:
-
Bind mounts
-
Volumes
10.1. Bind mounts
Los _ bind mounts_ pueden estar almacenados en cualquier directorio del sistema de archivos de la máquina host. Estos archivos pueden ser consultados o modificados por otros procesos de la máquina host o incluso por otros contenedores Docker.
10.2. Volumes
Los volumes se almacenan en la máquina host dentro del área del sistema de
archivos que gestiona Docker. Por ejemplo, en Linux será el directorio
/var/lib/docker/volumes.
Otros procesos de la máquina host no deberían modificar estos archivos, sólo deberían ser modificados por contenedores Docker.
Desde la documentación oficial de Docker nos aseguran que esta es la mejor forma de implementar persistencia de datos en los contenedores Docker.
El comando para gestionar volúmenes en Docker es docker volume.
$ docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
Referencias:
11. Docker system
$ docker system --help
Usage: docker system COMMAND
Manage Docker
Commands:
df Show docker disk usage
events Get real time events from the server
info Display system-wide information
prune Remove unused data
Mostrar el espacio de disco utilizado por Docker
$ docker system df
Mostrar información detallada del sistema
$ docker system info
Eliminar datos que no están siendo utilizados
$ docker system prune
Con el flag -a también se eliminarán todas las imágenes que no están siendo utilizadas.
$ docker system prune -a
12. Limpieza del equipo
Después de ejecutar los siguientes comandos sólo tendremos en nuestra máquina los contenedores que estén en ejecución y sus imágenes correspondientes.
$ docker system df
$ docker system prune -a
$ docker volume prune
13. Plugin de Docker y Docker Compose para Vistual Studio
Vamos a instalar el plugin de Docker que ha desarrollado Microsoft para Visual Studio.
En la documentación oficial del plugin podemos encontrar todas las funcionalidades que ofrece.
Referencia:
14. Creación de imágenes a partir de un archivo Dockerfile
14.1. Dockerfiles
Algunas de las instrucciones que podemos incluir en un Dockerfile son las siguientes:
-
FROM:
-
Indica el nombre de la imagen base de la que partimos.
-
-
ARG:
-
Podemos recibir argumentos en el proceso de creación de la imagen.
-
-
LABEL:
-
Nos permite incluir metainformación en la imagen, como el nombre del autor, versión, etc.
-
-
RUN:
-
Son los comandos que quiero ejecutar en la imagen.
-
-
VOLUME:
-
Indica el volumen donde vamos a guardar datos persistentes fuera del contenedor. Para que sigan estando disponibles cuando el contenedor no esté en ejecución.
-
-
COPY:
-
Para poder copiar archivos de nuestra máquina a la imagen.
-
-
EXPOSE:
-
Para indicar los puertos que se quieren exponer cuando se ejecute un contenedor con el parámetro
-P. Es informativo.
-
-
ENTRYPOINT:
-
Una vez que se crea la instancia del contenedor e instanciado, es lo primero que ejecuta.
-
El objetivo es dejar el contenedor en el estado inicial deseado.
-
Cada vez que iniciamos un contenedor debe estar en este estado.
-
Lo que hagamos en el entrypoint se ecuenta tantas veces como instancias realice.
-
-
CMD:
-
Puede tener los parámetros que le vamos a pasar al ENTRYPOINT para que se ejecuten después del ENTRYPOINT.
-
Si no hay ENTRYPOINT, ejecuta los comandos que tengamos aquí.
-
Si ENTRYPOINT y CMD están presentes en el Dockerfile, primero se ejecuta el ENTRYPOINT y el CMD se le pasa como parámetro.
-
Si sólo está el ENTRYPOINT sólo se ejecuta éste.
-
Si sólo está el CMD sólo se ejecuta este.
-
|
Si tenemos varios CMD sólo se ejecuta el último. |
Referencia:
14.2. build
Para crear imágenes a partir de un archivo Dockerfile utilizamos el comando docker build.
docker build --help
Usage: docker build [OPTIONS] PATH | URL | -
Build an image from a Dockerfile
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
--build-arg list Set build-time variables
--cache-from strings Images to consider as cache sources
--cgroup-parent string Optional parent cgroup for the container
--compress Compress the build context using gzip
--cpu-period int Limit the CPU CFS (Completely Fair Scheduler) period
--cpu-quota int Limit the CPU CFS (Completely Fair Scheduler) quota
-c, --cpu-shares int CPU shares (relative weight)
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
--disable-content-trust Skip image verification (default true)
-f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile')
--force-rm Always remove intermediate containers
--iidfile string Write the image ID to the file
--isolation string Container isolation technology
--label list Set metadata for an image
-m, --memory bytes Memory limit
--memory-swap bytes Swap limit equal to memory plus swap: '-1' to enable unlimited swap
--network string Set the networking mode for the RUN instructions during build (default "default")
--no-cache Do not use cache when building the image
--pull Always attempt to pull a newer version of the image
-q, --quiet Suppress the build output and print image ID on success
--rm Remove intermediate containers after a successful build (default true)
--security-opt strings Security options
--shm-size bytes Size of /dev/shm
-t, --tag list Name and optionally a tag in the 'name:tag' format
--target string Set the target build stage to build.
--ulimit ulimit Ulimit options (default [])
Referencias:
15. Docker Hub
15.1. Cómo publicar una imagen en Docker Hub
En primer lugar debemos de tener un usuario en Docker Hub.
Una vez que nos hemos creado un usuario, hacemos login desde nuestro terminal.
$ docker login
Ahora vamos a crear la imagen a partir de un archivo Dockerfile. Para poder subir una imagen a Docker Hub el nombre de la imagen tiene que incluir nuestro nombre de usuario y el nombre del respositorio donde se almacenará en Docker Hub.
Opcionalmente podemos indicar un tag con la versión de la imagen.
Ejemplo
Vamos a crear una imagen donde el nombre de usuario es josejuansanchez, el
nombre del repositorio es hola-mundo y el tag es 1.0.
$ docker build -t josejuansanchez/hola-mundo:1.0 .
Una vez hecho esto podemos hacer un push de la imagen para subirla a Docker Hub.
$ docker push josejuansanchez/hola-mundo:1.0
Después del paso anterior Docker Hub creará un repositorio en nuestra cuenta con la imagen que acabamos de subir.
|
Es posible cambiar el nombre y el tag de una imagen que tenemos ya creada en nuestro equipo local sin necesidad de volver a crearla.
|
Referencia:
16. Docker Compose
16.1. Instalación de Docker Compose
17. Autor
Este material ha sido desarrollado por José Juan Sánchez.
18. Licencia
El contenido de esta web está bajo una licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional.
19. Referencias
-
Docker. Guía práctica. Alberto González. RC Libros.
-
Docker. Primeros pasos y puesta en práctica de una arquitectura basada en micro-servicios. Jean-Philippe. Ediciones ENI.
-
Taller de Docker. Aula de Software Libre de la Universidad de Córdoba.
-
Docker. Una nueva forma de ejecutar y desarrollar aplicaciones. Manolo Torres.