Como crear un sitio web con Docker

Como forma de introducción al uso de Docker he decidido implementar este sitio web utilizando contenedores. Para ello he utilizado varios de los componentes y funcionalidades de Docker que me han permitido implementar el sitio web de una forma que creo que es interesante compartir como ejemplo del uso de contenedores.

Objetivo

El objetivo principal de esta entrada es el conocer con un detalle avanzado el funcionamiento de docker, no nos vamos a quedar en sólo descargar y ejecutar una imagen. Para ello vamos a tomar como ejemplo la creación de un sitio web con una infraestructura que tiene las siguientes características:

  • Como plataforma base voy a utilizar un servidor VPS basado en CENTOS 7 con 2GB de RAM y 10GB de disco duro
  • Para la página web voy a utilizar WordPress con MariaDB como base de datos
  • Un sistema de monitorización del estado de la propia web basado en InfluxDB+Telegraf+Grafana
  • Un Proxy inverso basado en Nginx que me permita acceder a los diferentes «servicios» del sitio web
  • Publicación con http y https, utilizando certificados de Let’s Encrypt

Cuando empecé a escribir esta entrada, comencé con la idea de mostrar de forma sencilla como usar Docker para crear una página web, pero ha ido creciendo hasta convertirse un manual de qué es Docker y como funciona.

 

Introducción a Docker

¿Qué es Docker?

Docker es un proyecto de código abierto que permite el uso de aplicaciones dentro contenedores con el objetivo de proporcionar una capa de aislamiento en la ejecución de las mismas. Al iniciarse dentro de un entorno Linux, esta capa de aislamiento es proporcionada por componentes del kernel como cgroups o namespaces, aunque debido a su auge y aceptación por la industria y la comunidad también ha sido adoptado por Microsoft de forma que podemos también utilizar Docker en entornos Windows (a partir de Windows 10 y Windows Server 2016)

El objetivo de un contenedor, es conseguir un paquete de software ligero que incluye todos los componentes y dependencias necesarios para ejecutar una aplicación de forma que siempre se ejecuta de la misma forma independientemente de su entorno o infraestructura donde se ejecuta.

Características de Docker

A continuación voy a enumerar algunas de las principales de Docker:

  • Menor uso de recursos: los contenedores comparten recursos con el kernel de la máquina donde se ejecutan. Además pueden compartir recursos (principalmente almacenamiento) de forma que se reduce en gran medida el tamaño de los contenedores.
  • Rapidez de ejecución: el inicio y ejecución de las instacias de los contenedores se produce de forma casi instantánea
  • Estándar: utiliza componentes estándar por lo que se puede utilizar en la mayoría de las distribuciones Linux y como hemos comentado antes, también en entornos Windows.
  • Aislamiento: con Docker conseguimos un nivel de aislamiento entre las aplicaciones que permiten aumentar la seguridad de la infraestructura
  • Distribución y compartición: permite compartir de una forma muy sencilla y rápida las aplicaciones creadas en contenedores.
  • Escalado: permite automatizar y escalar miles de una forma rápida y sencilla

 

Docker vs Máquinas virtuales

Aunque Dockers y Máquinas virtuales puedan ser un concepto similar y proporcionen características similares (ambos proporcionan aislamiento en la ejecución, permiten mover las aplicaciones entre hosts …) es importante saber que estamos trabajando con sistemas y arquitecturas diferentes.

Para entender las diferencias entre dockers y máquinas virtuales me ha gustado la analogía que he visto en un vídeo de la última DockerCon 2017:

  • Una máquina virtual es una casa individual o chalet que proporciona una infraestructura completa a quién vive en ella. Posee recursos individuales en cuanto a calefacción, electricidad, aislamiento…
  • Un docker es un apartamento o piso, proporciona una infraestructura a  quién vive en él, pero esa infraestructura es compartida con otros apartamentos. En el edificio de apartamentos hay una infraestructura común de la que se sirven los apartamentos y pisos, calefacción, electricidad… Además en un edificio pueden existir pisos de diferentes tamaños para diferentes necesidades.

Dejando de lado el tema inmobiliario, podríamos decir que las máquinas virtuales que se ejecutan en un host comparten los recursos físicos pero no comparten ningún recurso software. En el caso de Docker, además de compartir los recursos hardware también comparten recursos a nivel software. El objetivo de Docker es centrarse en la aplicación, dejando de lado el sistema operativo donde se ejecuta.

Máquina Virtual Docker

Este cambio de modelo implica también un cambio en la mentalidad de los administradores de sistemas:

  • ¿Cómo hago el backup de un contenedor? Directamente no lo hacemos. Uno de los objetivos de los contenedores es que no incluyan datos de la aplicación y se puedan ejecutar y reproducir su ejecución de forma muy sencilla.
  • Pero el contenedor tiene un sistema operativo, aunque sea una imagen mínima y optimizada: ¿cómo mantengo actualizado el contenedor? ¿cómo lo parcheo? Pues no lo parcheamos. Como los contenedores están basados en imágenes y no contienen datos, lo que hacemos es actualizar la imagen y parar y arrancar el contenedor.

Aunque son conceptos diferentes, no son excluyentes. Podemos perfectamente ejecutar Docker sobre una máquina virtual o incluso una aplicación que se ejecuta en un contenedor puede utilizar una base de datos que se ejecuta en una máquina virtual. Los dockers no van a reemplazar de forma competa a las máquinas virtuales. Tendremos aplicaciones donde la ejecución en contenedores nos proporcione ventajas sobre la ejecución de las mismas en máquinas virtuales (agilidad de despliegue, consumo de recursos…) pero habrá otras donde sigamos prefiriendo que se ejecuten en máquinas virtuales (por ejemplo bases de datos)

De esta forma tendremos centros de datos con máquinas virtuales, dockers que se ejecutan en servidores físicos, dockers que se ejecutan en máquinas virtuales…

 

 

Componentes principales de Docker

  • Docker Engine: es la aplicación cliente-servidor que contiene los siguientes elementos
    • Un servidor que se ejecuta como un demonio en el sistema (dockerd)
    • Un interfaz REST API que sirve de acceso para ejecutar comandos en el servidor
    • Un cliente que utiliza el usuario para interactuar con el servidor(docker)
  • Image: es el paquete de software ejecutable que contiene todo lo necesario para ejecutar una aplicación
  • Container: es una instancia en ejecución de una imagen.
  • Network: los contenedores se comunican con otros dockers o con recursos externos a través de la red:
  • Data Volume: permiten almacenar datos de forma persistente.

 

Ediciones de Docker

A principios de este año 2017 se han producido varios anuncios relacionados con las ediciones disponibles de Docker ya que de alguna forma, se está reorganizando su alcance y disponibilidad.

En estos momentos disponemos de las siguientes ediciones:

  • Docker Enterprise Edition (Docker EE): es la versión orientada a utilizarse en empresas y negocios.
  • Docker Community Edition (Docker CE): es la versión gratuita orientada a utilizarse en pequeños proyectos o desarrollos personales:
  • Docker Cloud: es la orientada a servirse en plataformas cloud como AWS o Azure

Nosotros vamos a utilizar Docker CE, y en este caso la versión Docker CE Stable (se publica una versión cada 4 meses) en lugar de la versión Docker CE Edge (se publica una versión nueva cada mes)

 

 

Instalación de Docker

Habilitamos el repositorio para CentOS

  • Instalamos el paquete yum-utils para utilizar la herramienta yum-config-manager
yum install -y yum-utils
  • Añadimos el repositorio (es el mismo repositorio para la versión Stable como para la versión Edge)
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
  • (Opcional) Si quisiesemos utilizar la versión Edge es necesario hablitar el repositorio
yum-config-manager --enable docker-ce-edge

 

Instalamos Docker

  • Actualizamos el índice de paquetes de yum
yum makecache fast
  • Instalamos Docker-CE
yum install docker-ce
  • Comprobamos el estado del demonio docker y lo iniciamos si no está arrancado
systemctl start docker
  • Hablitamos el demonio para que arranque con el sistema
systemctl enable docker
  • Ejecutamos el comando para obtener la versión de docker
docker version

Y obtenemos un mensaje en el que se nos indica las versiones del cliente como del servidor (en este caso los dos instalados en la misma máquina) como el siguiente:

Client:
 Version:      17.03.1-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:05:44 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.1-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:05:44 2017
 OS/Arch:      linux/amd64
 Experimental: false

 

Creación del sitio Web

Imágenes y versiones de Dockers a utilizar en el sitio web

Vamos a utilizar las versiones oficiales de los diferentes productos, y como en la mayoría de los casos tenemos diferentes versiones (o tags en el lenguaje docker) vamos a especificar la versión concreta a utilizar (en lugar de utilizar la última versión disponible)

 

Voy a ir describiendo como vamos creando el sitio web desde una primera versión con una configuración mínima y paso a paso ver como evoluciona añadiendo más servicios y configuraciones de forma que podamos ver el uso y las diferentes opciones de docker.

 

Ejecutando el primer docker

Como para gestionar el sitio web vamos a utilizar WordPress, lo primer que vamos a hacer es descargarnos la imagen de wordpress oficial.

Accedemos al repositorio (https://hub.docker.com/_/wordpress/) y lo primero que vemos es que tenemos decenas de versiones de imágenes disponibles para utilizar. Además de diferentes versiones y subversiones de wordpress (4.7, 4.7.1, 4.7.2…) tenemos disponibles imágenes basadas en:

  • apache
  • fpm
  • fpm-alpine

Vamos a comenzar eligiendo la imagen: wordpress:4.7.2-apache

  • Descargamos la imagen con el comando «docker pull» y el nombre de la imagen a descargar
# docker pull wordpress:4.7.2-apache

4.7.2-apache: Pulling from library/wordpress
693502eb7dfb: Downloading [==============>                                    ] 14.68 MB/51.36 MB
16328c296404: Downloading [=========>                                         ]  14.6 MB/77.61 MB
8b3c97761df6: Download complete
5e1d4f4f29eb: Download complete
530750fc5019: Download complete
39e9c6c72db7: Download complete
de476ce7ac87: Download complete
4ad13cbbc7d8: Download complete
74c28aa07dc7: Downloading [======================>                            ] 5.545 MB/12.58 MB
a07a242e36fb: Waiting
3d491d166e88: Waiting
cb6c232330f0: Waiting

 

  • Creamos y ejecutamos un docker de nombre «wordpress01» con la imagen anterior con el comando «docker run» y los situientes parámetros:
    • –name: nombre que le vamos a dar al contenedor
    • -d: el contenedor s e ejecutará en segundo plano
# docker run --name wordpress01 -d wordpress:4.7.2-apache

0b255bad8202cb48e43af418e627d92767111edcd2dc03504df7f3d2c3f7e115

 

  • Si todo es correcto, el comando nos devuelve el identificador asignado al docker

 

  • Comprobamos que el contenedor se está ejecutando
# docker ps

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS               NAMES
0b255bad8202        wordpress:4.7.2-apache   "docker-entrypoint..."   35 seconds ago      Up 34 seconds       80/tcp              wordpress01

 

  • En este punto hemos creado el contenedor con la imagen de wordpress, pero no tiene ninguna utilidad ya que no podemos acceder a él, no hemos configurado la conectividad desde el exterior del host.

 

Cuando se instala y ejecuta el servicio docker, se crean diferentes redes que pueden ser utilizadas por los contenedores

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9c435956de8f        bridge              bridge              local
98d7b2d36ca4        host                host                local
77b2fad06ea6        none                null                local
  • bridge: conecta el contenedor con el interfaz docker0 del host, utilizando una red privada y un servicio DHCP que asigna las direcciones IP a los contenedores. Es la red a la que se conectan por defecto los contenedores si no se indica una red de forma explícita.
  • host: conecta el contenedor con la red del host, de forma que no hay aislamiento entre el host y el contenedor.
  • none: en este caso el contenedor no tiene conectividad

La siguiente imagen podría mostrar una representación gráfica de cada una de las 3 redes

 

En la instalación por defecto de Docker, se crea el interfaz docker0 en el host, como podemos ver en el siguiente ejemplo:

 docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
    link/ether 02:42:59:c3:7f:c5 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever

 

En nuestro caso, hemos creado un contenedor conectado a la red «bridge»

Cuando arrancamos el contenedor, en el host se crea un interfaz por cada red a la que está conectado el contenedor:

veth97047f1@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP
    link/ether b2:fe:ac:72:52:62 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::b0fe:acff:fe72:5262/64 scope link tentative
       valid_lft forever preferred_lft forever

Pero aunque parece que tiene conectividad con el exterior, realmente el contenedor está aislado y tenemos que «publicar» el puerto correspondiente, en el caso del contenedor wordpress, el puerto tcp-80.

  • Paramos y borramos el contenedor creado
# docker stop wordpress01

wordpress01


# docker rm wordpress01

wordpress01

 

  • Volvemos a ejecutar «docker run» pero esta vez vamos a utilizar el parámetro «-p». Con este parámetro podemos «enlazar» uno de los puertos del host donde se está ejecutando el contenedor, con el puerto por el que está dando servicio el contenedor, en el caso de wordpress, el puerto 80.
    • -p: definimos los puertos en el formato puerto(s) del host:puerto(s) del contenedor (podemos definir un rango de puertos y también indicar una ip específica del host)
# docker run --name wordpress01 -d -p 8080:80 wordpress:4.7.2-apache

45e7fdef188bcb798cb25e3beb30da1e69db2728d227aa7cc518dcbdb6a54472
  • Ahora en la columna Ports vemos el enlace de los puertos entre el puerto del host (8080) y el puerto del contenedor (80)
# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                  NAMES
45e7fdef188b        wordpress:4.7.2-apache   "docker-entrypoint..."   17 seconds ago      Up 16 seconds       0.0.0.0:8080->80/tcp   wordpress01

  • Si accedemos con un navegador a la url http://host:8080 podremos ver la página donde se inicia el asistente de configuración inicial de wordpress.

Ya temos el sitio de wordpress listo para configurar, pero tenemos un problema, no tenemos una base de datos donde guardar la configuración de wordpress. Para ello vamos a utilizar MariaDB como gestor de base de datos y con la idea de utilizar dockers como microservicios, crearemos un contenedor con la instancia correspondiente.

 

 

Primera versión del sitio web (WordPress + MariaDB)

De forma similar a como hemos hecho con la imagen de wordpress vamos a proceder con la imagen de MariaDB

  • Descargamos la imagen
# docker pull mariadb:10.0.29

10.0.29: Pulling from library/mariadb
693502eb7dfb: Already exists
08d0e9d74b1b: Pull complete
e700ebfbe6bc: Pull complete
f718f1976629: Extracting [==================================================>]    114 B/114 B
b73d942a76fd: Downloading [>                                                  ] 67.16 kB/6.468 MB
6b34f02138e1: Download complete
b07f47800e46: Waiting
fb3499ae0cd2: Waiting

 

  • Creamos y ejecutamos un contenedor
# docker run --name mariadb01 -d mariadb:10.0.29

2754d6410c164a3d23f33f1b15d8f8740c04d9a5bdf4d87abf3c8881a83c132e

 

  • Comprobamos los contenedores que tenemos ejecutando
# docker ps

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                  NAMES
45e7fdef188b        wordpress:4.7.2-apache   "docker-entrypoint..."   7 minutes ago       Up 7 minutes        0.0.0.0:8080->80/tcp   wordpress01

 

  • Vemos que hay un problema, ¿dónde está el contenedor de mariadb01?  Vamos a volver a ejecutar el comando «docker ps» pero esta vez con el parámetro «-a»
# docker ps -a

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                      PORTS                  NAMES
2754d6410c16        mariadb:10.0.29          "docker-entrypoint..."   15 seconds ago      Exited (1) 14 seconds ago                          mariadb01
45e7fdef188b        wordpress:4.7.2-apache   "docker-entrypoint..."   7 minutes ago       Up 7 minutes                0.0.0.0:8080->80/tcp   wordpress01

 

  • Vemos que el contenedor mariadb01 se ha creado pero se ha parado, vemos que en la columna STATUS tenemos el mensaje «Exited …» Para poder investigar un poco más que ha pasado vamos a utilizar el comando «docker logs»
# docker logs mariadb01

error: database is uninitialized and password option is not specified
  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD

 

  • El mensaje nos indica claramente que no hemos indicado la contraseña del usuario root (SIEMPRE tenemos que leer la documntación primero) Hay imagenes que requieren o permiten utilizar variables de entorno para poder personalizar diferentes valores del contenedor. Para poder establecer los valores de las variables utilizamos el parámetro «-e» al ejecutar «docker run»

 

  • En el caso de la imagen de MariaDB podemos utilizar las siguientes variables:
    • MYSQL_ROOT_PASSWORD: [obligatoria] permite establecer la contraseña del usuario root.
    • MYSQL_ALLOW_EMPTY_PASSWORD: podemos utilizar esta variable para dejar la contraseña del usuario root en blanco.
    • MYSQL_RANDOM_ROOT_PASSWORD: se genera una contraseña para el usuario root de forma aleatoria y se muestra por pantalla.
    • MYSQL_DATABASE: permite crear una base de datos, y si se indica usuario y contraseña (siguiente variable) se le asignan los permisos de superusuario (GRANT ALL)
    • MYSQL_USER, MYSQL_PASSWORD: estas dos variables permiten crear un usuario y su contraseña sobre el que se aplicarán los permisos de superusuario para la base de datos creada con la variable anterior
  • Tras ver las variables disponibles, vamos a volver a ejecutar el contenedor de MariaDB, pero esta vez vamos a utilizar las variables para definir la contraseña del usuario root y ya definimos el usuario y la base de datos que vamos a utilizar con wordpress:
    • MYSQL_ROOT_PASSWORD: root_P@ssw0rd
    • MYSQL_DATABASE: wordpress01
    • MYSQL_USER: user_wordpress01
    • MYSQL_PASSWORD: user_P@ssw0rd
# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd mariadb:10.0.29

3f33340e0623f51aea8b2d4a8eecca64801f18d0f9c4d21a8894987f39453f44

 

  • Volvemos a comprobar los contenedores y ahora si que tenemos los dos contenedores ejecutándose:
# docker ps

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                  NAMES
3f33340e0623        mariadb:10.0.29          "docker-entrypoint..."   18 seconds ago      Up 18 seconds       3306/tcp               mariadb01
45e7fdef188b        wordpress:4.7.2-apache   "docker-entrypoint..."   28 minutes ago      Up 28 minutes       0.0.0.0:8080->80/tcp   wordpress01

 

  • Para comprobar que el servicio de mysql está funcionando y la base de datos creada, vamos a conectarnos al contenedor con el comando «docker exec» y los parámetros:
    • -i: modo interactivo
    • -t: pseudo-TTY
    • Nombre del contenedor
    • comando
# docker exec -i -t mariadb01 bash

root@3f33340e0623:/#

 

  • Ahora estamos en la shell del contenedor y podemos conectarnos con el cliente de mysql y comprobar la existencia de la base de datos
root@3f33340e0623:/# mysql -u user_wordpress01  -p
Enter password:

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 5
Server version: 10.0.29-MariaDB-1~jessie mariadb.org binary distribution

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

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

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| wordpress01        |
+--------------------+
2 rows in set (0.00 sec)


 

  • Hemos avanzado, pero ahora tenemos un problema: ¿cómo le indico al contenedor de wordpress que utilice la base de datos que hemos creado en el contenedor mariadb01?

En el punto anterior, para dar acceso al contenedor de wordpress, lo que hemos hecho es «mapear» un puerto del host al puerto 80 del contenedor. En este caso podríamos hacer lo mismo y mapear un puerto del host para el contenedor de MariaDB, pero no tiene sentido, ya que a este contenedor no tenemos que acceder desde el exterior, sólo es necesario que otro contenedor acceda a él.

Tenemos los dos contenedores conectados a la misma red (la red bridge) por lo que tienen conectividad entre ellos, pero únicamente utilizando sus direcciones IP. Además estas direcciones IP no son estáticas, por lo que no es recomendable utilizarlas para establecer la conectividad entre los contenedores ya que en el futuro, si cambiamos el orden de ejecución de los contenedores o creamos más, pueden cambiar.

Para permiter la conexión entre los contenedores vamos a «enlazarlos». Cuando enlazamos los contenedores con el parámetro «–link», se crea un «tunel» que enlaza los dos contenedores, sin la necesidad de exponer puertos. Al enlazar los contenedores, también hacemos que las variables de entorno de un contenedor, sean accesibles desde el otro. También se actualiza el archivo /etc/hosts para poder utilizar los nombres de los contenedores como nombres de red.

Creamos en enlace del siguiente modo:

--link <nombre del contenedor o id>:alias

Conociendo esto vamos a volver a rehacer los contenedores

  • El contenedor de Mariadb lo podemos dejar ejecutando pero paramos y borramos el contenedor de wordpress para volver a crearlo
# docker stop wordpress01

wordpress01


# docker rm wordpress01

wordpress01

 

  • Volvemos a ejecutar el contenedor de wordpress pero esta vez enlazándolo al contenedor de mysql
# docker run --name wordpress01 -d -p 8080:80 --link mariadb01:db wordpress:4.7.2-apache

428f50e26dd69f705f383d0b9603b31450536aef7016c96b4a8b325a44453069

 

  • Si accedemos al docker de wordpress01 podemos comprobar que ahora si hay conectividad entre los contenedores ya que se ha creado una entrada en el archivo /etc/hosts del contenedor wordpress01
# docker exec -it wordpress01 bash

root@428f50e26dd6:/var/www/html# cat /etc/hosts
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      db 3f33340e0623 mariadb01
172.17.0.2      428f50e26dd6

root@428f50e26dd6:/var/www/html# ping mariadb01
PING db (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: icmp_seq=0 ttl=64 time=0.200 ms
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.097 ms
^C--- db ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.097/0.149/0.200/0.052 ms

 

  • Ahora podemos volver al asistente de configuración de wordpress y configurar la base de datos correspondiente

 

En este punto ya tendríamos un sitio web totalmente funcional con WordPress y MariaDB

 

Para llegar a este punto, en el que tenemos:

  • 1 contenedor con MariaDB y una base de datos
  • 1 contenedor con WordPress siendo accesible en el puerto 80 del host donde se ejecutan los contenedores

ha sido necesario ejecutar los siguientes comandos de docker para crear 2 contenedores:

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd mariadb:10.0.29

# docker run --name wordpress01 -d -p 80:80 --link mariadb01:db wordpress:4.7.2-apache

 

Revisión de la configuración de red

Aunque ya tenemos los dockers funcionando, el utilizar la red «bridge» por defecto no es una práctica recomendada. Para seguir con las buenas prácticas vamos a crear una nueva red, también de tipo bridge, ya que de esta forma tenemos un mayor control de la conectividad de los contenedores y nos aprovechamos de la funcionalidad que proporciona una resolución automática DNS de forma que no tenemos que utilizar los enlaces que hemos visto anteriormente para permitir la conexión entre contenedores.

Vamos a crear un diseño como el siguiente:

Para ello vamos a realizar los siguientes pasos:

  • Crear 2 redes de tipo bridge
    • FrontEnd
    • BackEnd
  • Conectar el contenedor mariadb01 a la red BackEnd
  • Conectar el contenedor wordpress01 a las dos redes, a FrontEnd y a BackEnd

Seguimos los siguientes pasos:

# docker network create --driver bridge FrontEnd
f3c2e0bde8a5545a4cca4437f176c5c7fb4a85195cbac674d068267ac5641a3c

# docker network create --driver bridge BackEnd
f73c8718a52d665c32171e7de7dc9a54c3eeba220b1ce32a895faf04a681ae37

# docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
f73c8718a52d        BackEnd             bridge              local
f3c2e0bde8a5        FrontEnd            bridge              local
03774c55fe4e        bridge              bridge              local
98d7b2d36ca4        host                host                local
77b2fad06ea6        none                null                local

Podemos inspeccionar las redes creadas y ver el direccionamiento asignado a cada una

# docker inspect FrontEnd
[
    {
        "Name": "FrontEnd",
        "Id": "f3c2e0bde8a5545a4cca4437f176c5c7fb4a85195cbac674d068267ac5641a3c",
        "Created": "2017-05-10T21:01:45.487193712+02:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]


# docker inspect BackEnd
[
    {
        "Name": "BackEnd",
        "Id": "f73c8718a52d665c32171e7de7dc9a54c3eeba220b1ce32a895faf04a681ae37",
        "Created": "2017-05-10T21:01:49.700385188+02:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

Al crear las redes, vemos también que se han creado las tarjetas de red asociadas en el host:

br-e104556ea223: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ef:3a:59:57 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 scope global br-e104556ea223
       valid_lft forever preferred_lft forever
    inet6 fe80::42:efff:fe3a:5957/64 scope link
       valid_lft forever preferred_lft forever

br-7259dde2f075: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:e5:54:be:ad brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 scope global br-7259dde2f075
       valid_lft forever preferred_lft forever
    inet6 fe80::42:e5ff:fe54:bead/64 scope link
       valid_lft forever preferred_lft forever

Ejecutamos los contenedores, pero esta vez indicando a que red conectamos cada contenedor

El contenedor de mariadb01

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd --network=BackEnd mariadb:10.0.29

Y el contenedor de wordpress01

# docker run --name wordpress01 -d -p 80:80 --network=FrontEnd wordpress:4.7.2-apache

# docker network connect BackEnd wordpress01

 

 

Configuración del almacenamiento de los contenedores

Contenedores y capas

El siguiente punto que tenemos que revisar es la configuración del almacenamiento para tener claro donde se están guardando los contenedores y la información generada en el sitio web.

Como hemos hecho antes con la red, vamos ver de forma rápida como funciona el almacenamiento en los contenedores de Docker.

El almacenamiento de los archivos y carpetas de las imágenes y contenedores está organizado por capas. Una imagen está formada por diferentes capas de sólo lectura, que a través del driver de almacenamiento (la implementación correspondiente de Union File System) es mostrada de forma única.

Por ejemplo, la imagen de ubuntu:15:04 está formada por 4 capas, cada una de ellas con diferente información.

El driver es el encargado de mostrar esas 4 capas como una vista única de la imagen. Cada vez que se añade una modificación a una imagen, se le crea una nueva capa con los cambios introducidos, quedando el resto sin tocar.

Al crear un contenedor de esa imagen, lo que hacemos es añadir una nueva capa, en este caso de lectura/escritura. De esta forma, el contenedor está formado por las 4 capas de sólo lectura que proporciona la imagen y la capa de lectura/escritura propia del contenedor.

Los identificadores de cada una de las capas de la imagen corresponden con un hash del contenido. El identificador de la capa del contenedor es generado de forma aleatoria.

Comentábamos que una de las características de Docker es que los contenedores ocupan poco espacio en disco. Esto es así, porque diferentes contenedores creados a partir de una misma imagen, comparten todas las capas de sólo lectura de la imagen y cada uno de ellos tiene su propia capa de escritura.

Si alguno de los contenedores necesita modificar alguno de los archivos que se ubican en las capas de la imagen, se crea una copia (Copy-On-Write) de forma que sólo ese contenedor accede a esa copia modificada y el resto de contenedores siguen accediendo a la imagen original.

Si el contenedor es eliminado, esa capa propia de lectura/escritura también es eliminada.

Docker permite utilizar diferentes drivers para gestionar el almacenamiento. Algunos de los drivers que nos podemos encontrar o utilizar son: OverlayFS, AUFS, Btrfs, Device mapper, VFS, ZFS …

Para ver que driver estamos utilizando podemos ejecutar docker info

# docker info

Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 1
Server Version: 17.03.1-ce
Storage Driver: overlay
 Backing Filesystem: xfs
 Supports d_type: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
Swarm: inactive

Aunque hay diferentes drivers y cada uno tiene diferentes características que se pueden adaptar de una mejor forma a diferentes entornos y necesidades, vamos a utilizar el driver por defecto que proporciona la instalación de Docker en CentOS: overlay

 

 

Data Volumes

Como hemos visto, los datos que modificar un contenedor respecto a los datos de la imagen a partir del cual se crean se gestionan en una capa de lectura/escritura que tiene la vida del mismo contenedor, se elimina cuando se borra el contenedor. Pero podemos tener casos en los que queramos mantener esa información o queramos compartir la información entre dos o más contenedores de forma que accedan a los mismos datos. Para esto tenemos los Data Volumes

Los Data Volumes, son directorios específicos que no utilizan los drivers de almacenamiento y que permiten disponer de un recurso de datos permanente y con la opción de compartirlo entre varios contenedores.

Para poder añadir un Data Volume a un contenedor, utilizamos la opción -v al crearlo, por ejemplo:

# docker run --name ubuntu01 -it -v /volume01 ubuntu:17.04 /bin/bash

Podemos ver que en el contenedor tenemos el directorio /volume01

root@26ee79b0ea44:/# ls -la /volume01

/volume01:
total 0
drwxr-xr-x. 2 root root  6 May 14 08:05 .
drwxr-xr-x. 1 root root 58 May 14 08:05 ..

Si inspeccionamos el contenedor vemos en el apartado Mount, los volúmenes a los que accede el contenedor:

 "Mounts": [
            {
                "Type": "volume",
                "Name": "5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f",
                "Source": "/var/lib/docker/volumes/5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f/_data",
                "Destination": "/volume01",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

Si ejecutamos el comando docker volume ls, podemos ver el listado de volúmenes del host, donde se encuentra el creado para el contenedor anterior:

# docker volume ls

DRIVER              VOLUME NAME
local               5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f

Todo lo que el contenedor escriba o modifique en la carpeta local /volume01, se estará escribiendo en el siguiente directorio del host.

/var/lib/docker/volumes/5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f/_data

Acceso a directorios y archivos del host

También tenemos la opción de mapear un directorio o archivo concreto del host (sin crear un volumen gestionado por docker) de forma que sea compartido entre el host y el contenedor. También utilizamos la opción -v pero en este caso en el formato dir-host:dir-contenedor, siendo dir-contenedor una ruta absoluta dentro del contenedor y dir-host puede ser una ruta absoluta o relativa del host donde se ejecuta el contenedor

# docker run --name ubuntu02 -it  -v /mnt/volume02:/volume02 ubuntu:17.04 /bin/bash

#docker inspect ubuntu02
...
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/mnt/volume01",
                "Destination": "/volume01",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

De igual forma que hemos indicado un directorio tanto en el host como en el contenedor podemos indicar un archivo en concreto.

Por defecto, cuando se monta un volumen en el contendor, se hace con permisos de lectura y escritura, pero podemos indicar que sea de sólo lectura

# docker run --name ubuntu03 -it  -v /mnt/volume03:/volume03:ro ubuntu:17.04 /bin/bash

 

Almacenamiento persistente para nuestro sitio web

Retomamos nuestro ejemplo de crear un sitio web y volvemos a la situación en la que teníamos creados ya los dos contenedores de wordpress y mariadb

# docker network create --driver bridge FrontEnd
# docker network create --driver bridge BackEnd

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd --network=BackEnd mariadb:10.0.29

# docker run --name wordpress01 -d -p 80:80 --network=FrontEnd wordpress:4.7.2-apache
# docker network connect BackEnd wordpress01

Podemos ver que al crear los contenedores, tenemos dos volúmenes creados:

# docker volume ls
DRIVER              VOLUME NAME
local               aeaaf3890d571f4189d0abe08036101db31c5cf1aeb17b2c8f64522b7cbe7721
local               f560914d5536100c648a2cbaca874c47e3be2bd2f583a01bd309013188cdc76e

Si inspeccionamos los contenedores podemos ver los volúmenes asociados a cada uno de ellos:

# docker inspect wordpress01

"Mounts": [
            {
                "Type": "volume",
                "Name": "aeaaf3890d571f4189d0abe08036101db31c5cf1aeb17b2c8f64522b7cbe7721",
                "Source": "/var/lib/docker/volumes/aeaaf3890d571f4189d0abe08036101db31c5cf1aeb17b2c8f64522b7cbe7721/_data",
                "Destination": "/var/www/html",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],



# docker inspect mariadb01

"Mounts": [
            {
                "Type": "volume",
                "Name": "f560914d5536100c648a2cbaca874c47e3be2bd2f583a01bd309013188cdc76e",
                "Source": "/var/lib/docker/volumes/f560914d5536100c648a2cbaca874c47e3be2bd2f583a01bd309013188cdc76e/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

Estos volúmenes se han creado, aunque nosotros no se lo hemos indicado, porque están así definidos en la imagen de la que partimos:

  • La imagen de wordpress tiene definido el volumen
    • VOLUME /var/www/html
  • La imagen de mariadb tiene definido el volumen
    • VOLUME /var/lib/mysql

De esta forma tenemos ya creados los volúmenes persistentes, pero no los estamos gestionando nosotros, por lo que vamos a modificar la forma en la que creamos los contenedores para ajustarlo a nuestras necesidades y tener un mayor control de los datos, ya que si borramos estos contenedores y volvemos a crear unos nuevos (por un cambio en la configuración o por un actualización) se crearán nuevos volúmenes y los antiguos se quedarán sin utilizar.

Podemos crear un volumen y asignarle un nombre al mismo para poder identificarlo

# docker volume create VL_mariadb01

# docker volume create VL_wordpress01

y volvemos a crear los contenedores, pero ahora con la opción -v

# docker run --name mariadb01 -d -v VL_mariadb01:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd --network=BackEnd mariadb:10.0.29

# docker run --name wordpress01 -d -v VL_wordpress01:/var/www/html -p 80:80 --network=FrontEnd wordpress:4.7.2-apache

Podemos comprobar el contenido de los volúmenes:

# docker volume inspect VL_wordpress01

[
    {
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/VL_wordpress01/_data",
        "Name": "VL_wordpress01",
        "Options": {},
        "Scope": "local"
    }
]

# ls /var/lib/docker/volumes/VL_wordpress01/_data
index.php    wp-activate.php     wp-comments-post.php  wp-cron.php        wp-load.php   wp-settings.php   xmlrpc.php
license.txt  wp-admin            wp-config-sample.php  wp-includes        wp-login.php  wp-signup.php
readme.html  wp-blog-header.php  wp-content            wp-links-opml.php  wp-mail.php   wp-trackback.php

Vemos que el directorio «/var/lib/docker/volumes/VL_wordpress01/_data» contiene los archivos y directorios de la aplicación wordpress

El esquema del sitio web en este punto es el siguiente:

 

Vamos a trabajar con servicios

Si habéis llegado hasta aquí y habéis seguido todo el proceso, habréis comprobado que es bastante tedioso el crear un contenedor, pararlo, borrarlo, crear una nueva versión …

Esto es así porque estamos trabajando con cada uno de los contenedores, redes y volúmenes de forma individual, sin tener la visión en conjunto de lo que es la aplicación (que está formada por todos los componentes) Para facilitar esta gestión tenemos la herramienta Docker-Compose, que con un único comando nos va a permitir gestionar todos los contenedores (servicios) que forman parte de la aplicación.

 

Características de Docker-Compose

  • Permite aislar en un host múltiples proyectos utilizando un nombre de proyecto. Por defecto el nombre del proyecto es el nombre del directorio de trabajo aunque se puede modificar.
  • Mantiene la información de los volúmenes de datos
  • Sólo crea los contenedores que han tenido cambios desde la última ejecución

 

Instalación de Docker-Compose

Docker-Compose no se incluye en los paquetes por defecto de Docker, por lo que vamos a descargar el binario directamente de la página de Git-Hub

Vemos la última versión disponible: https://github.com/docker/compose/releases

En estos momentos, vemos que la última versión es la 1.13.0, por lo que ejecutamos

curl -L https://github.com/docker/compose/releases/download/1.13.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

Asignamos permisos de ejecución al archivo descargado

chmod +x /usr/local/bin/docker-compose

Comprobamos que tenemos Docker-Compose disponible:

# docker-compose version
docker-compose version 1.13.0, build 1719ceb
docker-py version: 2.2.1
CPython version: 2.7.13
OpenSSL version: OpenSSL 1.0.1t  3 May 2016

 

Creación del archivo docker-compose.yml

El primer paso es crear una carpeta en nuestro directorio de trabajo con el nombre que le vamos a dar a nuestro proyecto, por ejemplo SitioWeb01

mkdir -p SitioWeb01

Accedemos al directorio y creamos el archivo docker-compose.yml (también se acepta la extensión .yaml)

El archivo docker-compose.yml utiliza lenguaje YAML para definir los servicios (contenedores), redes y volúmenes.

Vamos a comenzar con un ejemplo simple, que va a replicar el sitio web que habíamos creado:

version: '3'

services:
   mariadb01:
     image: mariadb:10.0.29
     volumes:
       - VL_mariadb01:/var/lib/mysql
     restart: always
     environment:
       - MYSQL_ROOT_PASSWORD=root_P@ssw0rd
       - MYSQL_DATABASE=wordpress01
       - MYSQL_USER=user_wordpress01
       - MYSQL_PASSWORD=user_P@ssw0rd
     networks:
       - BackEnd

   wordpress01:
     depends_on:
       - mariadb01
     image: wordpress:4.7.2-apache
     ports:
       - "80:80"
     restart: always
     volumes:
           - VL_wordpress01:/var/www/html
     environment:
       - WORDPRESS_DB_HOST=mariadb01
       - WORDPRESS_DB_NAME=wordpress01
       - WORDPRESS_DB_USER=user_wordpress01
       - WORDPRESS_DB_PASSWORD=user_P@ssw0rd
     networks:
       - FrontEnd
       - BackEnd
volumes:
    VL_mariadb01:
    VL_wordpress01:

networks:
    FrontEnd:
      driver: bridge
    BackEnd:
      driver: bridge

Una vez creado el archivo docker-compose.yml con el contenido indicado, ejecutamos el comando:

docker-compose up

También podemos ejecutarlo con la opción -d, para que se ejecute en segundo plano y sin mostrarnos los logs de ejecución de los contenedores

# docker-compose up -d
Creating network "sitioweb01_FrontEnd" with driver "bridge"
Creating network "sitioweb01_BackEnd" with driver "bridge"
Creating sitioweb01_mariadb01_1 ...
Creating sitioweb01_mariadb01_1 ... done
Creating sitioweb01_wordpress01_1 ...
Creating sitioweb01_wordpress01_1 ... done

Tras levantar los servicios comprobamos que elementos se han creado:

  • Redes
    • sitioweb01_BackEnd
    • sitioweb01_FrontEnd
# docker network ls
NETWORK ID          NAME                  DRIVER              SCOPE
bb9e7f4cc904        bridge                bridge              local
98d7b2d36ca4        host                  host                local
77b2fad06ea6        none                  null                local
67abdb08ebe8        sitioweb01_BackEnd    bridge              local
63b6b2ed7641        sitioweb01_FrontEnd   bridge              local
  • Volúmenes
    • sitioweb01_VL_mariadb01
    • sitioweb01_VL_wordpress01
# docker volume ls
DRIVER              VOLUME NAME
local               sitioweb01_VL_mariadb01
local               sitioweb01_VL_wordpress01
  • Contenedores:
    • sitioeweb01_wordpress01_1
    • sitioeweb01_mariadb01_1
# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                NAMES
545063308b87        wordpress:4.7.2-apache   "docker-entrypoint..."   7 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp   sitioweb01_wordpress01_1
27fd925ad553        mariadb:10.0.29          "docker-entrypoint..."   8 minutes ago       Up 4 minutes        3306/tcp             sitioweb01_mariadb01_1

Y podemos comprobar que el sitio web sigue funcionando.

Si queremos reiniciar los servicios

# docker-compose restart
Restarting sitioweb01_wordpress01_1 ... done
Restarting sitioweb01_mariadb01_1 ... done

Si queremos parar la aplicación:

# docker-compose down
Stopping sitioweb01_wordpress01_1 ... done
Stopping sitioweb01_mariadb01_1 ... done
Removing sitioweb01_wordpress01_1 ... done
Removing sitioweb01_mariadb01_1 ... done
Removing network sitioweb01_FrontEnd
Removing network sitioweb01_BackEnd

Si queremos borrar los servicios parados

# docker-compose rm
Going to remove sitioweb01_wordpress01_1, sitioweb01_mariadb01_1
Are you sure? [yN] y
Removing sitioweb01_wordpress01_1 ... done
Removing sitioweb01_mariadb01_1 ... done

De momento llegamos hasta aquí. Más adelante continuaremos añadiendo contenedores que nos proporcionen una monitorización de la infraestructura (Grafana + Influxdb + Telegraf) un publicador (Nginx) y certificados (Let’s encypt)

14 comentarios en “Como crear un sitio web con Docker

  • el 16 septiembre, 2017 a las 7:39 am
    Permalink

    Hola que tal
    excelente post
    solo queria saber cuando le daras continuidad a esto
    esta muy interesante y es muy buena la manera en la que explicas
    saludos desde panama

    Respuesta
    • el 18 septiembre, 2017 a las 4:19 pm
      Permalink

      Gracias Francisco,

      Mi intención es darle continuidad, pero no soy todo lo constante que me gustaría en cuanto a publicar en el blog.

      Gracias y un saludo

      Respuesta
      • el 3 noviembre, 2017 a las 10:30 am
        Permalink

        hola que tal
        uff que lastima.. que en verdad e aprendido bastante con lo que has mostrado
        en este blog…
        espero pronto puedas seguir dandole continuidad al asinto
        saludos 🙂

        Respuesta
  • el 14 noviembre, 2017 a las 8:58 pm
    Permalink

    hola que tal hermano soy nuevo en el ambito de los contenedores, mira estuve siguiendo todos los paso pero al momento de llegar a configurar el wordpress , asignarle la base de datos desde la interfaz web me envía el siguiente error «error al establecer un conexión con la base de datos», me puedes dar una mano con esto.
    ley todo el post muy bueno la verdad pero mi inconveniente es solo que no se pega a bd desde la interfaz web.
    al momento de realizar la pruebas desde la consola le hago ping y responde hacia la base de datos de mariadb tambien realize la configuracion de red del backend y fronted y aun asi nada.
    estoy usando CentOS 7 virtualizado.

    te agradezco tu ayuda.

    Respuesta
    • el 18 noviembre, 2017 a las 11:03 am
      Permalink

      Hola Orlando,

      Por lo que entiendo, puede ser un mensaje de conectividad entre los dos contenedores. Revisa que estás utilizando correctamente el nombre del contenedor con la base de datos y si has utilizado la opción «-link» al crear los contenedores para «enlazarlos». Hay que tener en cuenta que aunque es el método que utilizo en la entrada, es un método que docker va a retirar y sería necesario recrear redes personalizadas. (https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/)

      Gracias y un saludo,

      Respuesta
  • el 10 enero, 2018 a las 3:58 am
    Permalink

    Solo 2 cosas:
    1) EXCELENTE POST!!!!!!
    2) Por favor haz la 2° Parte

    PD: excelente

    Respuesta
  • el 22 marzo, 2018 a las 11:57 pm
    Permalink

    Excelente entrada. No he encontrado nada igual. No sabia nada de docker y con tu ayuda he conseguido trabajar con wordpress en local utilizando esta herramienta. Hasta ahora usaba Vagrant con VVV, pero tarda en iniciarse, consume mas ram, etc. Seguiré profundizando con docker porque aunque el concepto es un poco complicado de entender, con post como el que te has currado es mas sencillo. Te animo encarecidamente que continúes con la segunda parte.
    Enhorabuena!
    Un saludo

    Respuesta
  • el 17 abril, 2018 a las 8:26 pm
    Permalink

    Excelente post ROBERTO. Yo tambien soy nuevo en docker y la verdad me quedo bastante claro. Te felicito y espero la 2da parte !

    Gracias !

    Respuesta
  • el 21 abril, 2018 a las 10:22 pm
    Permalink

    No puedo estar más de acuerdo con los comentarios que señalan como excelente este artículo.
    Me he llevado muchísimo aprendido. Gracias por tu tiempo y dedicación.

    Saludos

    Respuesta
  • el 28 mayo, 2018 a las 12:52 pm
    Permalink

    Muy bueno el blog y gracias ya que me esta ayudando muchísimo!
    Pro me gustaría saber si es posible ejecutar la aplicación de docker-compose.yml en un cluster swarm y como hacerlo.
    Gracias

    Respuesta
  • el 25 octubre, 2018 a las 10:53 pm
    Permalink

    MMUUUUYY BUEN APORTE ROBERTO.
    Mas que un buen inicio, como dices al inicio de tu post se convirtio en un manual de que es docker y como usarlo practicamente… lo mejor que he encontrado en la red.
    Estoy de acuerdo con los demas diciendo que esperamos la segunda parte.

    Respuesta
  • el 9 abril, 2020 a las 1:56 pm
    Permalink

    Hola, Roberto.
    Genial tu artículo.
    Tengo problema para instalar wordpress.
    Hay visibilidad de la BBDD desde el sevidor web, la base de datos está accesible, pero cuando desde el navegador intento instalar wordpress me dice que hay un error al establecer la conexión a la base de datos.
    He revisado nombres de variables para el wordpress y están bien.
    ¿Ha cambiado algo la configuración del –link?

    Saludos y enhorabuena por tu blog.

    Respuesta
  • el 2 mayo, 2020 a las 7:49 pm
    Permalink

    Muy pero muy bueno ! Consulta si quiero acceder pero por https ? Como debo proceder ? Me podes orientar Gracias

    Respuesta

Responder a Roberto Orayen Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.