Curso Introductorio a Docker
Curso Introductorio a Docker
2.3. Ponentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3. Conceptos básicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4. Instalación de Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
6. Imágenes Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
7.4.1. attach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.4.2. exec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.5.1. rm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.6.1. stop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.6.2. start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.9.1. cp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7.11. Creación de un contendedor con Apache y PHP 7.2 (en segundo plano) . . . . . . . . . . . . . . . . . . 53
7.12. Creación de un contenedor con MySQL sin persistencia de datos (en segundo plano) . . . . . 54
7.13. Creación de un contenedor con MySQL con persistencia de datos (en segundo plano) . . . . . 58
8. Portainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
9. Redes en Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
10.2. Volumes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
14.1. Dockerfiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
14.2. build. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
16.4. version. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
16.6. volumes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
1
Chapter 2. Contenido, sesiones y ponentes
2.1. Bloque I: Docker (10 horas)
• 19 de Noviembre, de 16:30 a 20:30
2.3. Ponentes
• Manolo Torres (Profesor titular de la UAL)
2
Chapter 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 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.
Referencia:
• De Docker a Kubernetes: entendiendo qué son los contenedores y por qué es una de las mayores
revoluciones de la industria del desarrollo
3
3.2. Analogía con el transporte marítimo de
contenedores
Los contenedores de transporte marítimo:
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
4
Referencia: Docker 101 Tutorial
• 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.
• 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.
Referencia:
5
3.6. Arquitectura de Docker
• Docker Daemon
• Docker Client
• Docker Registries
• Docker Objects
◦ Images
◦ Containers
◦ Volumes
◦ Networks
Referencias:
• [Link]
• [Link]
• Docker daemon
• Docker CLI
6
Figure 5. Docker Engine. Imagen de [Link]
Referencia:
• [Link]
Una imagen se crea 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.
7
3.9. ¿Qué tecnología hay detrás de Docker?
Docker está escrito en Go.
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.
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.
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.
Figure 7. AUFS storage-driver demonstrating whiteout file. Imagen de AUFS and Docker Deployment
Referencias:
8
• [Link]
• Docker Internals. A Deep Dive Into Docker For Engineers Interested In The Gritty Details.
• Docker Community Edition (CE): Es la versión de uso gratuito, open source y se puede usar en
Windows, Mac y Linux.
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.
• 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:
9
Referencia: [Link]
10
Chapter 4. Instalación de Docker
1. Instalación para Ubuntu (Linux).
Got permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/[Link]: Get
[Link] dial unix
/var/run/[Link]: connect: permission denied
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.
Para activar los cambios en los grupos sin tener que cerrar la sesión podemos ejecutar lo siguiente.
$ newgrp docker
11
4.3. Comprobamos si docker está instalado
correctamente
$ docker version
12
Chapter 5. Administración básica de
contenedores Docker
5.1. Ciclo de vida de un contenedor Docker
13
Figure 10. Docker client query verbs. Imagen de edX.
14
5.4. Comandos de Docker CLI
$ docker --help
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/[Link]")
--tlscert string Path to TLS certificate file (default "/Users/josejuansanchez/.docker/[Link]")
--tlskey string Path to TLS key file (default "/Users/josejuansanchez/.docker/[Link]")
--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
15
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
16
Chapter 6. Imágenes Docker
6.1. Buscar imágenes en Docker Hub
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 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 AUTOMATED son imágenes que Docker
Hub ha generado automáticamente a partir del código fuente de un repositorio
externo y se han enviado a los repositorios de Docker.
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
17
de cada una de las imágenes.
Ejemplo:
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:
Referencia:
• [Link]
• stars=(number)
• is-automated=(true|false)
• is-official=(true|false)
18
$ docker search --filter is-automated=true alpine
Referencia:
• [Link]
Ejercicios
1. Utiliza docker search para buscar imágenes de sistemas operativos que conozcas y de
aplicaciones web que te puedan ser de utilidad en los módulos que impartas.
2. Hemos visto que docker search sólo nos permite realizar tres tipos de filtros, sin embargo
en la web oficial de Docker Hub tenemos la posibilidad de utilizar otros filtros diferentes.
Utiliza la web oficial para conocer cuáles son las imágenes de Docker más populares y
realiza algunas búsquedas.
3. ¿Existe alguna imagen que permita ejecutar Docker dentro de un contenedor Docker?
4. Busca qué imágenes de sistemas operativos base existen para crear contenedores con
Windows.
• ubuntu: Ubuntu
• node: [Link]
• php, haproxy, wordpress, rabbitmq, python, openjdk, tomcat, jenkins, redmine, elasticsearch…
19
$ docker pull --help
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.
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:
Referencia:
• [Link]
Ejercicios
1. Descarga las imágenes de alpine, ubuntu, httpd, nginx y mysql de Docker Hub.
Ejemplo:
20
El siguiente comando descargará la imagen curso-docker/test-image de un registry local que
estará escuchando en el puerto 5000 ([Link]):
Referencia:
• [Link]
Cada capa representa cada una de las instrucciones del archivo Dockerfile con el que se ha
creado la imagen.
En un contenedor todas las capas son de lectura, excepto la última capa que
será de lectura/escritura.
Ejemplo
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/[Link]
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.
21
Figure 12. Imagen de las capas de lectura y escritura de un contenedor ubuntu. Imagen de Docker
La 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.
22
Figure 13. Varios contenedores comparten la misma imagen de ubuntu. Imagen de Docker
Ejemplo
Cuando descargamos una imagen obtenemos una salida similar a la del siguiente ejemplo, donde
descargamos la imagen de alpine:
En este caso la imagen de alpine está formada por una única capa.
FROM scratch
ADD alpine-minirootfs-3.10.3-x86_64.[Link] /
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
23
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 alpine.
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
① Se descarga la 1ª capa
② Se descarga la 2ª capa
③ Se descarga la 3ª capa
④ Se descarga la 4ª capa
En este ejemplo podemos ver como ha sido necesario descargar cuatro capas para la imagen de
ubuntu.
24
FROM scratch
ADD [Link] /
# verify that the APT lists files do not exist
RUN [ -z "$(apt-get indextargets)" ]
# (see [Link]
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.
25
$ 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
[Link]/library/php:latest
① Esta capa no es necesario descargarla porque ya se descargó previamente para otra imagen.
Ocurre lo mismo para 2, 3 y 4.
Referencia:
26
Usage: docker history [OPTIONS] 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
Ejemplo
Referencia:
• [Link]
27
$ docker images --help
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
$ docker images -a
28
• Las imágenes que aparecen como <none>:<none> con el comando docker
images son dangling images (imágenes colgadas). Estas imágenes habrá que
borrarlas para liberar espacio porque ya no se usarán.
Para conocer más detalles sobre este tema se recomienda la lectura del siguiente
artículo: What are Docker <none>:<none> images?
$ 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.
Referencia:
• [Link]
Options:
-f, --force Force removal of the image
--no-prune Do not delete untagged parents
Referencia:
• [Link]
29
6.12. Eliminar una imagen por su nombre
(REPOSITORY)
Ejemplo:
Untagged: ubuntu:latest
Untagged: ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d
Deleted: sha256:775349758637aff77bf85e2ff0597e86e3e859183ef0baba8b3e8fc8d3cba51c
Deleted: sha256:4fc26b0b0c6903db3b4fe96856034a1bd9411ed963a96c1bc8f03f18ee92ac2a
Deleted: sha256:b53837dafdd21f67e607ae642ce49d326b0c30b39734b6710c682a50a9f932bf
Deleted: sha256:565879c6effe6a013e0b2e492f182b40049f1c083fc582ef61e49a98dca23f7e
Deleted: sha256:cc967c529ced563b7746b663d98248bc571afdb3c012019d7f54d6c092793b8b
Referencia:
• [Link]
• 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.
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.
30
$ docker rmi 6d1ef012b567 965ea09ff2eb 4c9d84aeed9f
Referencia:
• [Link]
Recuerda que la opción docker images -aq muestra el ID de todas las imágenes del
host, incluyendo las imágenes intermedias.
4c9d84aeed9f
965ea09ff2eb
6b7ac1689533
a9e108e8ee8a
1d774b717f24
37b651a98b60
31
Chapter 7. Creación y ejecución de
contenedores Docker
docker run --help
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., [Link])
--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
32
--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:
• [Link]
FROM scratch ①
COPY hello / ②
CMD ["/hello"] ③
33
① 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.
② Copia el archivo hello al directorio raíz 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.
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.
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
34
• CONTAINER ID (c48138039ade): Es el identificador único del contenedor.
• COMMAND (/hello): Instrucción que ejecuta el contenedor. En este caso es la que aparece en el
archivo Dockerfile.
• 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.
Referencia:
• [Link]
• [Link]
FROM scratch ①
ADD alpine-minirootfs-3.10.3-x86_64.[Link] / ②
CMD ["/bin/sh"] ③
① 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.
35
Es posible ejecutar comandos dentro del contenedor indicando el comando después del nombre de
la imagen. Veamos la sintaxis de docker run.
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.
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.3
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="[Link]
BUG_REPORT_URL="[Link]
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
Aunque el contenedor esté detenido podemos consultar sus registros de salida (STDOUT) con el
comando docker logs.
La sintaxis es la siguiente:
36
Por ejemplo, para mostrar la salida estándar (STDOUT) del contenedor del ejemplo anterior podría
haber utilizado:
$ docker logs 2d
Ejercicios
• docker run es el comando que nos permite crear un contenedor a partir de una imagen Docker.
• Los dos parámetros -it nos permiten usar un contenedor como si fuese una máquina virtual
tradicional.
37
un nombre Docker nos asignará un nombre automáticamente.
Una vez ejecutado el comando anterior nos aparece un prompt para poder interaccionar con el
contenedor que acabamos de crear.
/ #
/ # 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="[Link]
BUG_REPORT_URL="[Link]
El gestor de paquetes de Alpine Linux es apk. En la documentación oficial podemos encontrar más
detalles sobre cómo usarlo.
apk update
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.
38
$ docker ps -a
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.
$ doker rm alpinec
$ doker rm e9af6c8dbffb
• Escribir exit
• Pulsar CTRL + D
• Pulsar CTRL + P + Q
39
Ejercicios
2. Una vez que has creado el contenedor finaliza la sesión con exit o CTRL + D para salir y
detenerlo.
5. Una vez que has creado el contenedor sal del contenedor sin detenerlo con CTRL + P + Q.
7. ¿Cómo puedo volver a conectarme al terminal del contenedor que está en ejecución?
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 contenedor 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 detendría una
vez que finaliza la ejecución del comando.
40
$ docker run -dit \
--rm \
--name topdemo \
ubuntu /usr/bin/top -b
$ docker ps
Ahora podríamos acceder al terminal el contenedor que está en ejecución con attach.
Ejercicios
1. Accede al terminal del contenedor topdemo1 con attach y una vez dentro finaliza la sesión
pulsando CTRL + C.
3. Accede al terminal del contenedor topdemo2 con attach y una vez dentro finaliza la sesión
pulsando CTRL + P + Q.
41
7.4.2. exec
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 comando 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.
Ejemplo
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 ps
Ahora vamos a ejecutar el comando /bin/bash para crear un nuevo terminal sobre el contenedor.
42
$ 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:
• [Link]
• [Link]
$ docker rm --help
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
43
$ docker rm httpdc
$ 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.
Referencia:
• [Link]
Ejercicios
1. Elimina todos los contenedores que tengas en ejecución y los que estén detenidos.
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
44
7.6.2. start
Options:
-t, --time int Seconds to wait for stop before killing it (default 10)
Ejercicios
2. Comprueba los contenedores que están detenidos. ¿Existe alguno? ¿Por qué?
5. Elimina todos los contenedores que tengas en ejecución y los que estén detenidos.
1. 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.
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.
45
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á.
$ docker run -d \
--rm \
--name httpdc \
httpd
$ 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.
Si buscamos la dirección IP del contenedor vemos que es una dirección privada del tipo 172.17.0.x.
"IPAddress": "[Link]
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 [Link]
<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á
46
ejecutando el servicio de Docker tendremos que exponer los puertos.
Eliminamos el contenedor.
$ docker rm -f httpdc
• -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á.
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
$ docker ps
Comprobamos que podemos acceder al contenido del contenedor desde un navegador web
accediendo a la URL [Link] o desde el terminal con el comando curl.
$ curl [Link]
<html><body><h1>It works!</h1></body></html>
• -P
47
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 contenedor. Tenga en
cuenta que en el archivo Dockerfile se pueden exponer varios puertos a la vez.
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
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 [Link] o desde el terminal con el comando curl.
$ curl [Link]
<html><body><h1>It works!</h1></body></html>
Ejercicios
1. Crea un contenedor con la imagen oficial de nginx y haz una redirección del puerto 8080
de tu máquina local con el puerto 80 del contenedor.
2. ¿Cómo podría sustituir la página de bienvenida de Nginx por una página que está en
nuestro equipo local?
48
7.9.1. cp
$ docker cp --help
Options:
-a, --archive Archive mode (copy all uid/gid information)
-L, --follow-link Always follow symbol link in SRC_PATH
Ejercicios
49
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.
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 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, z y Z.
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
$ docker run -d \
--rm \
--name devtest \
-v /home/josejuan/target:/app \
nginx
50
$ docker run -d \
--rm \
--name devtest \
-v "$(pwd)"/target:/app \
nginx
$ 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.
"Mounts": [
{
"Type": "bind",
"Source": "/home/josejuan/target",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
51
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.
$ docker run -d \
--rm \
--name broken-container \
-v /tmp:/usr \
nginx
docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting
container process caused "exec: \"nginx\": executable file not found in $PATH": unknown.
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
"Mounts": [
{
"Type": "bind",
"Source": "/home/josejuan/target",
"Destination": "/app",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
],
Referencias:
• [Link]
52
• [Link]
• [Link]
Ejercicio
4. Crea un contenedor con el servidor web nginx que cumpla los siguientes requisitos:
Ejercicio
[Link]
Crea un contenedor con el servidor web nginx que cumpla los siguientes requisitos:
• Crea un volumen de tipo bind mount con el directorio site de tu host y el directorio
/usr/share/nginx/html.
Esta imagen está configurada para servir el contenido que se encuentre dentro del directorio
53
/var/www/html.
$ docker run -d \
--rm \
--name apache_php \
-p 80:80 \
-v "$PWD":/var/www/html \
php:7.2-apache
$ docker ps
Creamos el archivo [Link] 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.
$ curl [Link]
1. Busca una imagen oficial que te permita servir una web PHP con el servidor Nginx. En
caso de no encontrar ninguna, ¿qué podríamos hacer?
54
$ docker run -d \
--rm \
--name mysqlc \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
mysql:5.7.28
Una vez que estamos dentro del contenedor nos conectamos desde la consola de MySQL.
# mysql -u root -p
55
DROP DATABASE IF EXISTS tienda;
CREATE DATABASE tienda CHARSET utf8mb4;
USE tienda;
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);
56
mysql> SHOW TABLES;
+------------------+
| Tables_in_tienda |
+------------------+
| fabricante |
| producto |
+------------------+
2 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 eliminamos el contenedor y volvemos a crear
57
uno nuevo con el mismo comando, no tendríamos acceso a la base de datos. Vamos a
comprobarlo.
$ docker ps
Detenemos el contenedor. Como hemos iniciado el contenedor con la opción --rm al detenerlo se
eliminará automáticamente.
$ docker ps -a
Ejercicios
1. Instancia un nuevo contenedor que tenga el mismo nombre y los mismos parámetros que
el contenedor que hemos eliminado (utiliza el mismo comando).
2. Abre un terminal en el contenedor que acabas de crear para interaccionar con él.
3. Una vez dentro del contenedor inicia una conexión a la consola de MySQL.
2. Crear un volumen de tipo bind mount donde montamos un directorio de nuestra máquina local
en un directorio dentro del contenedor.
58
$ docker run -d \
--rm \
--name mysqlc \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Una vez que estamos dentro del contenedor nos conectamos desde la consola de MySQL.
# mysql -u root -p
59
DROP DATABASE IF EXISTS tienda;
CREATE DATABASE tienda CHARSET utf8mb4;
USE tienda;
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);
60
mysql> SHOW TABLES;
+------------------+
| Tables_in_tienda |
+------------------+
| fabricante |
| producto |
+------------------+
2 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
61
del mismo volumen, tendríamos acceso a la misma base de datos. Vamos a comprobarlo.
$ docker ps
Detenemos el contenedor. Como hemos iniciado el contenedor con la opción --rm al detenerlo se
eliminará automáticamente.
$ docker ps -a
Ejercicios
1. Instancia un nuevo contenedor con el nombre mysql_container_2, que haga uso del
volumen mysql_data donde está almacenada la base de datos tienda.
3. Una vez dentro del contenedor inicia una conexión a la consola de MySQL.
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 ejecutaría en el contenedor todos los scripts SQL que se encuentren en el
62
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
Ejercicio
2. Crear un archivo con el nombre [Link] que contenga todas las sentencias SQL del
ejercicio anterior y guárdalo en el directorio sql.
4. Abre un terminal en el contenedor que acabas de crear para interaccionar con él.
5. Una vez dentro del contenedor inicia una conexión a la consola de MySQL.
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.
63
$ 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).
[Link] localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
[Link] mysqlc 8411f6064e44
[Link] c25ca9a48fb3
Comprobamos que el contendedor adminer puede conectar con el contenedor mysql abriendo un
navegador web y accediendo a la URL: [Link]
Creamos un contenedor con MySQL indicando que queremos que esté en la red --network my-net.
64
$ 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: [Link]
Referencias:
65
$ docker run -d \
--rm \
--name mysqlc \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
$ docker run -d \
--rm \
--link mysqlc \
-e PMA_ARBITRARY=1 \
-p 8080:80 \
phpmyadmin/phpmyadmin
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
66
Comprobamos que el contendedor phpMyAdmin puede conectar con el contenedor mysql abriendo un
navegador web y accediendo a la URL: [Link]
Ejercicio
1. Busca en Docker Hub las imágenes del sistema gestor de bases de datos PostgreSQL y
phpPgAdmin.
2. Crea una instancia de PostreSQL y phpPgAdmin, de modo que desde phpPgAdmin pueda
conectarme a PostgreSQL.
Necesitarás consultar en Docker Hub cuáles son las variables de entorno que
necesitas para ambos casos.
67
Ejercicio con múltiples contenedores: WordPress + MySQL +
phpMyAdmin
Utiliza las imágenes oficiales que hay en Docker Hub para WordPress, MySQL y
phpMyAdmin.
1) Crea una user-defined bridge network para todos los contenedores. Por ejemplo, esta red se
puede llamar wordpress-net.
2) Crea un volumen nuevo para almacenar los datos de MySQL. Por ejemplo, este volumen se
puede llamar wordpress_mysql_data.
3) Crea una instancia de un contenedor con MySQL con las siguientes características:
68
$ docker run -d \
--rm \
--name mysqlc \
--network wordpress-net \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=wp_database \
-e MYSQL_USER=wp_user \
-e MYSQL_PASSWORD=wp_password \
-v wordpress_mysql_data:/var/lib/mysql \
mysql:5.7.28
4) Crea una instancia de un contenedor con phpMyAdmin con las siguientes características:
• Crea la variable de entorno PMA_ARBITRARY y asígnale el valor 1, para que nos permita
indicar el nombre del servidor de base de datos al que queremos conectarnos.
$ docker run -d \
--rm \
--network wordpress-net \
-e PMA_ARBITRARY=1 \
-p 8080:80 \
phpmyadmin/phpmyadmin
5) Crea una instancia de un contenedor con WordPress con las siguientes características:
69
$ docker run -d \
--rm \
--name wordpressc \
--network wordpress-net \
-p 80:80 \
-e WORDPRESS_DB_HOST=mysqlc \
-e WORDPRESS_DB_NAME=wp_database \
-e WORDPRESS_DB_USER=wp_user \
-e WORDPRESS_DB_PASSWORD=wp_password \
-v wordpress_data:/var/www/html \
wordpress
Utiliza las imágenes oficiales que hay en Docker Hub para Moodle, MySQL y phpMyAdmin,
para crear un sitio web con Moodle.
Flag Descripción
no El contenedor no se reinicia. Es la opción por
defecto.
on-failure[:max-retries] El contenedor se reinicia si finaliza por un error,
es decir, cuando el exit code es distinto de 0.
always 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.
unless-stopped Es similar a always, excepto que cuando el
contenedor se detiene (manualmente o de otro
modo), éste no se reiniciará cuando se reinicia el
servicio de Docker.
70
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
incompatible 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:
• [Link]
71
Chapter 8. Portainer
Portainer es una herramienta web open-source que permite gestionar contenedores Docker de
forma local o remota.
Fuente: Wikipedia
$ docker run -d \
--rm \
-p 9000:9000 \
--name portainerc \
-v /var/run/[Link]:/var/run/[Link] \
-v portainer-data:/data \
portainer/portainer
72
• --rm nos permite eliminar el contenedor cuando finaliza su ejecución.
• -p 9000:9000 nos permite para mapear el puerto 9000 del host (nuestra máquina) con el puerto
9000 expuesto en el 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/[Link]).
El Docker daemon puede recibir peticiones de la API del Docker Engine utilizando
tres tipos de sockets: unix, tcp y fd.
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.
En el siguiente paso tendremos que indicar que queremos administrar un Servidor Local.
$ 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:
• [Link]
• [Link]
73
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 habilitar 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.
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.
$ docker run -d \
-p 9000:9000 \
--name portainerc \
-v /var/run/[Link]:/var/run/[Link] \
-v portainer-data:/data \
portainer/portainer -H tcp://[Link]:2375
Donde [Link] se corresponde con la IP del servidor remoto de Docker y 2375 el puerto
donde el Docker daemon está escuchando las peticiones.
Referencia:
74
Chapter 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.
• Las redes user-defined bridge ofrecen un DNS automático entre los contenedores de la misma
red, mientras que los contenedores de una red default bridge sólo pueden acceder a los otros
contenedores a través de su IP o haciendo uso de la opción --link que está considerada legacy.
• Las redes user-defined bridge permiten que un contenedor en ejecución pueda ser añadido
o eliminado de la red, mientras que la redes default bridge no lo permiten.
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.
75
Figure 15. Ejemplo de un host con tres contenedores y dos redes. Imagen de Docker Education Team
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
76
$ docker network ls
Referencias:
• [Link]
• [Link]
• [Link]
Tutorial
[Link]
77
Chapter 10. Almacenamiento en Docker
Por defecto, todos los archivos que se crean dentro 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
78
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.
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
Referencias:
• [Link]
• [Link]
• [Link]
79
Chapter 11. Docker system
$ docker system --help
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
$ docker system df
Con el flag -a también se eliminarán todas las imágenes que no están siendo utilizadas.
80
Chapter 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
81
Chapter 13. Plugin de Docker y Docker
Compose para Visual 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:
82
Chapter 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:
• ARG:
• LABEL:
◦ Nos permite incluir metainformación en la imagen, como el nombre del autor, versión, etc.
• RUN:
• 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:
• 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.
◦ Lo que hagamos en el entrypoint se ejecuta en cada una de las instancias que se realicen.
• CMD:
◦ Puede tener los parámetros que le vamos a pasar al ENTRYPOINT para que se ejecuten
después del ENTRYPOINT.
83
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.
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:
84
Ejercicio 1
Ejercicio 2
Ejercicio 3
Crea una imagen que sirva la web estática que está en el directorio site del siguiente
repositorio:
[Link]
Ejercicio 4
Crea una imagen que ejecute la siguiente aplicación web desarrollada con Python y Flask.
[Link]
Ejercicio 5
Crea una imagen que ejecute la siguiente aplicación web desarrollada en [Link].
[Link]
85
Ejercicio 6
Crea una imagen que utilice como base la imagen de nginx e instala/configura los paquetes
necesarios para que pueda servir páginas PHP.
<?php
phpinfo();
?>
En la siguiente referencia puede encontrar los pasos necesarios para instalar y configurar
PHP-FPM (PHP FastCGI Process Manager) en el servidor web Nginx.
[Link]
86
Chapter 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
repositorio donde se almacenará en Docker Hub.
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.
Una vez hecho esto podemos hacer un push de la imagen para subirla a Docker Hub.
Después del paso anterior Docker Hub creará un repositorio en nuestra cuenta con la imagen que
acabamos de subir.
$ docker tag <local-image>[:tag] <new-repo>[:tag]
Referencia:
• [Link]
• [Link]
87
Chapter 16. Docker Compose
Docker Compose es una herramienta para definir y ejecutar aplicaciones multi-contenedor con
Docker. Utiliza un archivo YAML para definir y configurar los servicios, los volúmenes y las redes
que utilizará nuestra aplicación. El nombre del archivo que se utiliza por defecto es docker-
[Link], aunque es posible asignarle otro nombre. Una vez definidos todos los servicios de
nuestra aplicación, podemos crearlos e iniciarlos con un solo comando (docker-compose).
• Entornos de desarrollo.
Para instalarla en Linux se recomienda seguir los pasos que se describen en la documentación
oficial.
Referencia:
• [Link]
$ docker-compose
Usage:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help
Options:
-f, --file FILE Specify an alternate compose file
(default: [Link])
-p, --project-name NAME Specify an alternate project name
(default: directory name)
88
--verbose Show more output
--log-level LEVEL Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
--no-ansi Do not print ANSI control characters
-v, --version Print version and exit
-H, --host HOST Daemon socket to connect to
Commands:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information
89
16.3. El archivo de configuración [Link]
El archivo [Link] está escrito en formato YAML y está formado por las siguientes
secciones:
• version (Obligatorio. Si no se indica una version se trataría de la versión 1 que está en desuso)
• volumes (Opcional)
• networks (Opcional)
Ejemplo:
version: '3'
services:
...
volumes:
...
networks:
...
Ejemplo
El siguiente ejemplo muestra el contenido del directorio raíz de un proyecto llamado proyecto-lamp.
90
proyecto-lamp
|
├── apache
│ └── Dockerfile
├── [Link] ①
├── sql
│ └── [Link]
└── src
├── [Link]
├── [Link]
├── [Link]
├── [Link]
├── [Link]
└── [Link]
A continuación vamos a estudiar cada una de las secciones que pueden aparecer en un archivo
[Link].
16.4. version
La etiqueta version debe estar definida al inicio del documento YAML. El número de versión es
una cadena y por lo tanto debe ir encerrado entre comillas simples o dobles.
Ejemplo:
version: '3'
• Versión 2.x
• Versión 3.x: Es la última versión y la recomendada por Docker. Ha sido diseñada para ser
compatible con Docker Compose y Docker Swarm (Cluster de Docker Engine).
Referencia:
• [Link]
91
16.5. services
Esta sección del archivo hace referencia a la configuración que tendrá cada uno de los
contenedores de nuestra aplicación.
Ejemplo:
Suponga que tenemos una aplicación PHP que hace uso de la pila LAMP. En este caso podríamos
tener dos contenedores, que se corresponderían con dos servicios dentro de nuestro archivo
[Link]
• Servicio 1: apache-php
• Servicio 2: mysql
services:
apache-php:
image: php:7.2-apache
...
mysql:
image: mysql:5.7.28
...
92
Nombre de servicio vs Nombre del contenedor
services:
apache-php: ①
image: php:7.2-apache
container_name: apachec ②
...
mysql: ①
image: mysql:5.7.28
container_name: mysqlc ②
...
16.6. volumes
En la sección global de volumes debemos incluir todos los volúmenes que hayamos definido en los
servicios, excepto los volúmenes de tipo bind_mount.
Si hemos declarado volumes en los servicios pero no los hemos declarado en la sección global del
archivo, obtendremos un mensaje de error al intentar crear y ejecutar los contenedores con docker-
compose up.
Los volúmenes que aparezcan en la sección global serán visibles por todos los contenedores.
Ejemplo
93
version: '3.7'
services:
web:
image: nginx
...
volumes:
- ./src:/usr/share/nginx/html ①
db:
image: mysql:5.7.28
...
volumes:
- mysql_data:/var/lib/mysql ②
volumes: ③
mysql_data:
① Declara un volumen de tipo bind_mount dentro del servicio web. Este volumen no se declarará en
la sección global volumes.
② Declara un volume dentro del servicio db. Este volumen hay que declararlo en la sección global
volumes.
③ Esta es la sección glogal donde se declaran todos los volúmenes a nivel global. Este volumen será
visible por todos los contenedores
16.7. networks
En la sección global de networks debemos incluir todas las redes que hayamos definido en los
servicios.
Si hemos declarado volumes en los servicios pero no los hemos declarado en la sección global del
archivo, obtendremos un mensaje de error al intentar crear y ejecutar los contenedores con docker-
compose up.
Si no definimos ninguna network entonces todos los servicios que se definen en el archivo docker-
[Link] se ejecutarán por defecto en una red de tipo user-defined bridge network, que se
llamará igual que el directorio que contiene el archivo [Link], con el sufijo _default.
Ejemplo
94
version: '3'
services:
apache:
image: php:7.2-apache
ports:
- 80:80
volumes:
- ./src:/var/www/html
networks: ①
- frontend-network
- backend-network
mysql:
image: mysql:5.7.28
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=database
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
networks: ②
- backend-network
volumes:
mysql_data:
networks: ③
frontend-network:
backend-network:
① Declaramos que el servicio apache está incluido en dos redes: frontend-network y backend-network.
③ Esta es la sección global donde se declaran todas las networks que existen en nuestro archivo a
nivel global.
• Imagen: httpd.
95
$ docker run -d \
--rm \
-p 80:80 \
-v "$PWD"/src:/usr/local/apache2/htdocs/ \
httpd
Con Docker Compose es posible definir las características del contenedor en un archivo docker-
[Link], de modo que para el ejemplo anterior quedaría así:
version: '3'
services:
httpd:
image: httpd
ports:
- 80:80
volumes:
- ./src:/usr/local/apache2/htdocs/
Como el volumen que estamos utilizando en este caso es de tipo bind mount, no es necesario
declararlo en la sección volumes del archivo.
ports
La documentación oficial nos avisa que cuando mapeamos los puertos utilizando
el formato HOST:CONTAINER podemos obtener resultados erróneos cuando
utilizamos un número de puerto del contenedor menor de 60, debido a que
YAML parsea los números con formato xx:yy como un valor en base-60.
Por este motivo, recomienda siempre indicar los puertos como una cadena
encerrada entre comillas simples o dobles.
Referencia:
• [Link]
Para crear y ejecutar los servicios en segundo plano (-d detach) con Docker Compose usamos la
opción up -d:
$ docker-compose up -d
① Crea una red por defecto de tipo user-defined bridge con el nombre del directorio donde
96
estamos ejecutando docker-compose y el sufijo _default.
② Crea un contenedor para el servicio httpd y le asigna el nombre httpd_httpd_1, que está formado
por el nombre del directorio, el nombre del servicio y un número.
Para consultar la lista de contenedores que están definidos en el archivo [Link] que
están en ejecución utilizamos la opción ps.
$ docker-compose ps
docker ps vs docker-compose ps
Para consultar la salida estándar (STDOUT) de los contenedores que están definidos en el archivo
[Link] utilizamos la opción logs.
Esta opción sólo nos mostrará las últimas líneas de la salida estándar.
$ docker-compose logs
Si utilizamos la opción logs -f el comando se queda mostrando la salida estándar hasta que
pulsemos CTRL + C.
$ docker-compose logs -f
$ docker-compose down
① Detiene el contenedor
97
② Elimina el contenedor
③ Elimina la red
• Imagen: mysql:5.7.28.
• Variables de entorno:
◦ MYSQL_ROOT_PASSWORD=root
◦ MYSQL_DATABASE=database
◦ MYSQL_USER=user
◦ MYSQL_PASSWORD=password
$ docker run -d \
--rm \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=database \
-e MYSQL_USER=user \
-e MYSQL_PASSWORD=password \
-p 3306:3306 \
-v mysql_data:/var/lib/mysql \
mysql:5.7.28
Con Docker Compose es posible definir las características del contenedor en un archivo docker-
[Link], de modo que para el ejemplo anterior quedaría así:
98
version: '3'
services:
mysql:
image: mysql:5.7.28
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=database
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Como el servicio mysql está haciendo uso del volumen mysql_data, es necesario
incluirlo en la sección global de volumes.
environment
1) Como un array
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=database
- MYSQL_USER=user
- MYSQL_PASSWORD=password
2) Como un diccionario
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: database
MYSQL_USER: user
MYSQL_PASSWORD: password
Después de los dos puntos (:) tiene que existir un espacio en blanco.
Para crear y ejecutar los servicios en segundo plano (-d detach) con Docker Compose usamos la
opción up -d:
99
$ docker-compose up -d
① Crea una red por defecto de tipo user-defined bridge con el nombre del directorio donde
estamos ejecutando docker-compose y el sufijo _default.
② Crea el volumen que hemos declarado añadiéndole como prefijo el nombre del directorio donde
estamos ejecutando docker-compose.
③ Crea un contenedor para el servicio mysql y le asigna el nombre mysql_mysql_1, que está formado
por el nombre del directorio, el nombre del servicio y un número.
Para consultar la lista de contenedores que están definidos en el archivo [Link] que
están en ejecución utilizamos la opción ps.
$ docker-compose ps
Para consultar la salida estándar (STDOUT) de los contenedores que están definidos en el archivo
[Link] utilizamos la opción logs.
Esta opción sólo nos mostrará las últimas líneas de la salida estándar.
$ docker-compose logs
Si utilizamos la opción logs -f el comando se queda mostrando la salida estándar hasta que
pulsemos CTRL + C.
$ docker-compose logs -f
100
$ docker-compose down
① Detiene el contenedor
② Elimina el contenedor
③ Elimina la red
Si ejecutamos el comando docker volume ls podemos ver que el volumen mysql_mysql_data todavía
existe.
$ docker volume ls
Para detener y eliminar los servicios, con las redes y los volúmenes incluidos tenemos que
indicar la opción down -v.
$ docker-compose down -v
① Detiene el contenedor
② Elimina el contenedor
③ Elimina la red
④ Elimina el volumen
101
version: '3'
services:
mysql:
image: mysql:5.7.28
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=database
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- 8080:80
environment:
- PMA_HOST=mysql ①
volumes:
mysql_data:
① La variable de entorno PMA_HOST nos permite indicar el nombre del servicio con el que quiero
conectar el servicio de phpmyadmin.
102
Figure 20. Así se vería phpmyadmin cuando configuramos la variable PMA_HOST
103
Figure 21. Así se vería phpmyadmin cuando configuramos la variable PMA_ARBITRARY=1
16.10.1. depends on
Para solucionar este problema y controlar el orden en el que se deben iniciar los servicios
utilizamos la opción depends on.
La opción depends on sólo nos asegura que un servicio se inicia antes que otro,
pero no nos asegura que el servicio esté "preparado" para conectarnos a él.
Por ejemplo, para indicar que el servicio de phpmyadmin tiene que iniciarse después del servicio de
mysql pondríamos la opción depends on en la declaración de phpmyadmin de la siguiente manera.
104
version: '3'
services:
mysql:
image: mysql:5.7.28
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=database
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- 8080:80
environment:
- PMA_HOST=mysql
depends_on: ①
- mysql
volumes:
mysql_data:
① Indicamos que este servicio depende del servicio mysql y que no podrá iniciarse hasta que el
servicio de mysql se haya iniciado.
Referencia:
105
Ejercicio
Crea un archivo [Link] para desplegar los servicios de mysql y phpmyadmin, pero
deberá utilizar la última versión de la imagen de mysql.
Nota: Es posible que cuando quiera conectarse desde phpmyadmin a mysql obtenga errores
similares a estos
Para solucionar este problema deberá sobrescribir el comando con el que se inicia el
servicio de mysql para indicar que el plugin de autenticación por defecto es de tipo
mysql_native_password.
En la sección de definición del servicio de mysql deberá añadir la opción command con el
siguiente valor.
...
mysql:
image: mysql
command: --default-authentication-plugin=mysql_native_password
...
16.10.2. restart
Con la opción restart podemos definir una política de reinicio para cada uno de los servicios de
nuestro archivo [Link]. Estas políticas de reinicio nos permiten controlar si los
contenedores deben reiniciarse automáticamente cuando finalizan por algún motivo o cuando el
servicio de Docker se reinicia.
Flag Descripción
restart: no El contenedor no se reinicia. Es la opción por
defecto.
restart: on-failure El contenedor se reinicia si finaliza por un error,
es decir, cuando el exit code es distinto de 0.
restart: always 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.
106
Flag Descripción
restart: unless-stopped Es similar a always, excepto que cuando el
contenedor se detiene (manualmente o de otro
modo), éste no se reinciará cuando se reinicia el
servicio de Docker.
Si quisiéramos utilizar Docker Compose para realizar despliegues en un único servidor, deberíamos
utilizar la opción restart: always para evitar los tiempos de inactividad de los servicios.
Ejemplo
version: '3'
services:
mysql:
image: mysql
command: --default-authentication-plugin=mysql_native_password
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=database
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
restart: always ①
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- 8080:80
environment:
- PMA_HOST=mysql
depends_on:
- mysql
restart: always ①
volumes:
mysql_data:
① Indicamos que la política de renicio de los dos servicios es de tipo restart: always.
Referencia:
107
16.11. Ejemplo de una pila LAMP
En este ejemplo vamos a utilizar un repositorio con el siguiente contenido:
├── apache
│ └── Dockerfile
├── [Link]
├── sql
│ └── [Link]
└── src
• apache
• mysql
• phpmyadmin
Servicio: apache
Para el servicio apache vamos a crear nuestra propia imagen a partir de un archivo Dockerfile con
el siguiente contenido.
FROM ubuntu
LABEL title="apache-lamp" \
author="José Juan Sánchez"
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Madrid
EXPOSE 80
Se trata una imagen que parte de la imagen base de la última versión de ubuntu donde instalamos el
servidor web apache y los paquetes necesarios para servir código PHP y poder conectarnos con
un sistema gestor de bases de datos MySQL.
108
Servicio: mysql
Este servicio utilizará la última versión de la imagen oficial de mysql de Docker Hub.
Servicio: phpmyadmin
Este servicio utilizará la última versión de la imagen oficial de phpmyadmin de Docker Hub.
[Link]
109
version: '3'
services:
apache:
build: ./apache ①
ports:
- 80:80 ②
volumes:
- ./src:/var/www/html ③
depends_on:
- mysql ④
mysql:
image: mysql ⑤
command: --default-authentication-plugin=mysql_native_password ⑥
ports:
- 3306:3306 ⑦
environment: ⑧
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=lamp_db
- MYSQL_USER=lamp_user
- MYSQL_PASSWORD=lamp_password
volumes:
- mysql_data:/var/lib/mysql ⑨
- ./sql:/docker-entrypoint-initdb.d ⑩
phpmyadmin:
image: phpmyadmin/phpmyadmin ⑪
ports:
- 8080:80 ⑫
environment:
- PMA_HOST=mysql ⑬
depends_on:
- mysql ⑭
volumes: ⑮
mysql_data:
① Con la opción build indicamos el directorio donde está el archivo Dockerfile que vamos a
utilizar para crear la imagen.
③ Creamos un volumen de tipo bind mount donde estará el código de la aplicación que queremos
servir.
④ Indicamos que este servicio depende del servicio mysql y se iniciará después de mysql.
⑥ Sobrescribimos el comando que inicia el servicio para indicar que queremos usar el plugin de
autenticación mysql_native_password.
110
⑦ Indicamos los puertos del servicio de mysql.
⑬ La variable de entorno PMA_HOST=mysql nos permite indicar el nombre del host de la base de
datos.
⑭ Indicamos que este servicio depende del servicio mysql y se iniciará después de mysql.
⑮ En la sección global de volumes indicamos los volúmenes que utilizarán los servicios.
Ejercicio 1
[Link]
111
Ejercicio 2
Modifica el archivo [Link] del ejercicio anterior para que los servicios usen dos
redes:
• frontend-network
• backend-network
• apache
• phpmyadmin
• mysql
Sólo los servicios que están en la red frontend-network expondrán sus puertos en nuestro
host. Por lo tanto, el servicio de mysql no deberá estar accesible desde nuestro host.
Referencia:
• Networking in Compose
112
Ejercicio 3
Referencia:
• [Link]
• Cada línea del archivo tiene que tener una variable de entorno con el formato VARIABLE=valor.
• Las líneas que empiezan con el carácter # son interpretadas como un comentario y son
ignoradas.
• No hay un tratamiento especial para las comillas. Esto significa que son parte del valor de la
variable.
Ejemplo
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=database
MYSQL_USER=user
MYSQL_PASSWORD=password
113
version: '3'
services:
mysql:
image: mysql:5.7.28
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD={MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- 8080:80
environment:
- PMA_HOST=mysql
depends_on:
- mysql
volumes:
mysql_data:
Es posible utilizar archivos con variables de entorno que no se llamen .env, en este
caso tendremos que utilizar la opción env_file para indicar el nombre del archivo.
Ejemplo:
web:
env_file:
- [Link]
Referencias:
• [Link]
Ejercicio
Modifica el archivo [Link] del ejercicio anterior para poder desplegar los
servicios de WordPress, MySQL y phpMyAdmin haciendo uso de variables de entorno en un
archivo .env.
114
16.13. Ejemplo de una pila LEMP
En este ejemplo vamos a utilizar un repositorio con el siguiente contenido:
├── [Link]
├── nginx
│ └── [Link]
├── php-fpm
│ └── Dockerfile
├── sql
│ └── [Link]
└── src
• nginx
• php-fpm
• mysql
• phpmyadmin
Servicio: nginx
El servicio nginx utiliza la última versión de la imagen oficial de ngingx de Docker Hub. En este
servicio vamos a crear dos volúmenes:
115
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
location / {
index [Link] [Link] [Link];
}
location ~ \.php$ {
try_files $uri $uri/ /[Link]$is_args$query_string;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-fpm:9000; ①
fastcgi_index [Link];
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
① En esta línea estamos indicando que todas las peticiones que reciba el servidor con extensión
.php serán enviadas al puerto 9000 del servicio php-fpm.
Servicio: php-fpm
Este servicio será el encargado de interpretar el código PHP de las peticiones que recibe del servicio
nginx. En este servicio tendremos que crear un volumen de tipo bind mount con el código fuente de
la aplicación.
Para el servicio php-fpm vamos a crear nuestra propia imagen a partir de un archivo Dockerfile con
el siguiente contenido.
FROM php:7.2-fpm
Utilizamos como imagen base la imagen php:7.2-fpm y le añadimos una nueva capa con las librerías
que me permite conectar con MySQL.
Servicio: mysql
116
• Un volumen de tipo bind mount sobre el directorio /docker-entrypoint-initdb.d del contenedor
para ejecutar los scripts de SQL que tenemos en el directorio local sql y así inicializar las bases
de datos.
Servicio: phpmyadmin
[Link]
117
version: '3'
services:
nginx:
image: nginx ①
ports:
- 80:80 ②
volumes:
- ./nginx:/etc/nginx/conf.d ③
- ./src:/usr/share/nginx/html ④
depends_on:
- php-fpm ⑤
php-fpm:
build: ./php-fpm ⑥
volumes:
- ./src:/usr/share/nginx/html ⑦
depends_on:
- mysql ⑧
mysql:
image: mysql ⑨
command: --default-authentication-plugin=mysql_native_password ⑩
ports:
- 3306:3306 ⑪
environment: ⑫
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=lamp_db
- MYSQL_USER=lamp_user
- MYSQL_PASSWORD=lamp_password
volumes:
- mysql_data:/var/lib/mysql ⑬
- ./sql:/docker-entrypoint-initdb.d ⑭
phpmyadmin:
image: phpmyadmin/phpmyadmin ⑮
ports:
- 8080:80 ⑯
environment:
- PMA_HOST=mysql ⑰
depends_on:
- mysql ⑱
volumes: ⑲
mysql_data:
118
④ Volumen de tipo bind mount para el código fuente de la aplicación
⑤ Indicamos que este servicio depende del servicio php-fpm y se iniciará después de éste.
⑥ Con la opción build indicamos el directorio donde está el archivo Dockerfile que vamos a
utilizar para crear la imagen.
⑧ Indicamos que este servicio depende del servicio mysql y se iniciará después de éste.
⑩ Sobrescribimos el comando que inicia el servicio para indicar que queremos usar el plugin de
autenticación mysql_native_password.
⑰ La variable de entorno PMA_HOST=mysql nos permite indicar el nombre del host de la base de
datos.
⑱ Indicamos que este servicio depende del servicio mysql y se iniciará después de mysql.
⑲ En la sección global de volumes indicamos los volúmenes que utilizarán los servicios.
Ejercicio 1
[Link]
119
16.14. Ejemplos interesantes
Laradock
• [Link]
• [Link]
• [Link]
Los ejemplos que hemos utilizado durante el curso están disponibles en el siguiente repositorio.
• [Link]
• [Link]
120
Chapter 17. Autor
Este material ha sido desarrollado por José Juan Sánchez.
121
Chapter 18. Licencia
El contenido de esta web está bajo una licencia de Creative Commons Reconocimiento-
NoComercial-CompartirIgual 4.0 Internacional.
122
Chapter 19. Referencias
• Docker. Guía práctica. Alberto González. RC Libros.
123









