---
weight: 4
title: "Apuntes de Docker"
date: 2021-01-23T16:01:50+0100
draft: false
summary: "Apuntes de docker"
categories:
  - notes
tags:
  - docker
  - traefik
---

{{< image src="/images/docker_logo_wide.jpg" >}}

{{< admonition type=warning title="Work in progress" open=true >}}
Estos apuntes no están completos, (ni de lejos)
{{< /admonition >}}

## Conceptos

### El software

El software de Docker implementa el estándar OCI (_Open Container
Initiative_) tan fielmente como les es posible.

La OCI especifica:

* Imágenes
* _Runtime containers_

runc

:    Es la implementación para _Docker_ de la especificación de
     _runtime container_ de la OCI.

     La misión de _runc_ es crear contenedores, puede funcionar por si
     solo via CLI (es un binario) pero funciona a muy bajo nivel.

containerd

:    Es un demonio. Se encarga del ciclo de vida de los contenedores:
     arrancarlos, pararlos, pausarlos, borrarlos.

     Se encarga también de otras cosas como _pulls_, _volumes_ y
     _networks_. Esta funcionalidad adicional es modular y opcional y
     se introdujo para facilitar el uso de _containerd_ en otros
     proyectos como por ejemplo _Kubernetes_.

shim

:    _shim_ se encarga de dejar conectados los containers con
     _containerd_ una vez que _runc_ termina su misión y muere.

### _images_

Puede pensarse que una imagen es como un contenedor parado. De hecho
puedes parar un contenedor y construir una imagen a partir de el. Se
dice que las imágenes son objetos de _build-time_ y los contenedores
son objetos de _run-time_

Las imágenes se pueden almacenar centralizadas en los _image
registries_. Por ejemplo _dockerhub_ es un _image registry_,
seguramente el más popular, pero tu podrías tener tu propio _image
registry_.

{{< admonition type=warning title="Seguridad e Imágenes" state=open >}}

Mucho ojo con esto, por que en el [Docker Store](https://hub.docker.com/) nos podemos encontrar todo tipo de imágenes, al fin y al cabo cualquiera puede publicar sus imagenes. No debemos confiar automáticamente en cualquier imagen. Ten cuidado con tus fuentes.

__¡No te fies de las etiquetas!__ Son solo eso, etiquetas. Por ejemplo _latest_ se supone que apunta a la última versión de un contenedor pero no hay ningún mecanismo que lo asegure, _alguien_ tiene que ocuparse de que la etiqueta apunte al contenedor correcto.

{{< /admonition >}}

Las __imágenes oficiales__ suelen residir en un repo de primer nivel dentro de _dockerhub_ y tendrán una url como esta, por ejemplo: <https://hub.docker.com/_/nginx/>


### _containers_

Como dijimos arriba, un _container_ es una instancia _runtime_ de una
imagen. A partir de una imagen podemos crear varios _containers_.

Un contenedor es muy parecido a una máquina virtual clásica (como las
de _Virtual Box_ por ejemplo). La principal diferencia es que son
mucho más ligeros y rápidos, ya que comparten _kernel_ con la máquina
_host_. Además suelen basarse en imágenes lo más ligeras posible que
contienen solo lo imprescindible para que el contenedor cumpla su
función.

### _volumes_

### _networks_


## cheatsheet

### Imágenes

| Comando             | Efectos                                |
|:--------------------|:---------------------------------------|
| docker images       | Lista las imágenes disponibles         |
| docker ps           | Lista los container en ejecución       |
| docker ps -a        | Lista todos los container              |
| docker build        | construye imagen desde dockerfile      |
| docker history      | Muestra la hist. es decir como se hizo |
| docker inspect      | Detalles de la imagen (p.ej. ip)       |
| docker images       | Lista todas las imágenes               |
| docker images -a    | lista tb las imágenes intermedias      |
| docker images -q    | lista solo identificativos             |
| docker system prune | Borra todas las imágenes no usadas     |
| docker pull         | descarga una imagen                    |
| docker push         | sube una imagen a la nube              |
| docker image rm     | Borra imágenes, alias `docker rmi`     |

### Contenedores

| Comando                 | Efectos                               |
|:------------------------|:--------------------------------------|
| docker run              | Ejecuta un contenedor                 |
| docker run -d           | detached                              |
| docker run -name        | para pasarlo por nombre               |
| docker container rename | para renombrar un contenedor          |
| docker ps               | Para ver cont. ejecutandose           |
| docker ps -a            | Para verlos todos                     |
| docker stop             | Para parar uno, `SIGTERM` + `SIGKILL` |
| docker start            | reiniciar un cont.                    |
| docker restart          | stop + start                          |
| docker kill             | `SIGKILL`                             |
| docker pause            | lo pone en pausa                      |
| docker unpause          | lo saca de la pausa                   |
| docker cp               | copiar ficheros a y desde cont.       |
| docker exec -it         | ejecutar y abrir terminal             |
| docker top              | ver estadísticas del cont.            |
| docker stats            | para ver más stats. `--no-stream`     |
| docker --rm             | borrar cont. al sali                  |
| docker container prune  | borrar todos los cont. parados        |

__Ejemplos__:

- Lanzar un alpine y abrir un terminal contra el:

  ```bash
  docker run -it --rm alpine /bin/ash
  ```

- Abrir un terminal contra un contenedor que ya está corriendo:

  ```bash
  docker exec -it <containerId> bash
  ```

### Volumes

| Comando                 | Efectos                               |
|:------------------------|:--------------------------------------|
| docker volume prune     | Borrar todos los `volume` no usados   |

### Dockerhub

| Comando              | Efectos                  |
|:---------------------|:-------------------------|
| docker commit        | crea imagen desde cont.  |
| docker login         |                          |
| docker push          |                          |
| docker search ubuntu | busca imágenes en el hub |


### docker inspect ###

```shell
docker inspect --format '{{ .NetworkSettings.IPAddress }}' CONT_ID
```

** TODO ** Investigar mas el `format`


### docker build ###

Como construir una imagen

`docker build https://github.com/salvari/repo.git#rama:directorio`

Tambien se le puede pasar una url o un directorio en nuestro pc.


## Dockerfile

{{< admonition type=info title="Referencias" open=true >}}

- [Dockerfile Best Practices with Examples](https://takacsmark.com/dockerfile-tutorial-by-example-dockerfile-best-practices-2018/#overview)

{{< /admonition >}}

Un _dockerfile_ es un fichero de texto que define una imagen de Docker. Con esto podemos crear nuestras imágenes a medida, ya sea para "dockerizar" una aplicación, para construir un entorno a medida, para implementar un servicio etc. etc.

En el [dockerhub](https://hub.docker.com/) tenemos imágenes para hacer prácticamente cualquier cosa, pero en la práctica habrá muchas ocasiones donde queramos cambiar algún detalle o ajustar la imagen a nuestro gusto y necesidades (o simplemente aprender los detalles exactos de la imagen), para todo ello necesitamos saber como funcionan los _dockerfiles_.

Un _dockerfile_ de ejemplo:

```dockerfile
FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [ "python", "./your-daemon-or-script.py" ]
```

Un _dockerfile_ funciona por capas (_layers_) que son los bloques de construcción de _docker_. La primera _layer_ siempre es `FROM image_name` que define en que imagen pre-construida se basará nuestra imagen. Podemos definir muchas cosas en un _dockerfile_ desde permisos de usuario hasta _scripts_ de arranque.

Asi que:

1. Un _dockerfile_ es un fichero de texto que contien las instrucciones que se ejecutarán con `docker build` para construir una imagen Docker
2. Es un conjunto de instrucciones paso a paso
3. Se compone de un conjunto de instrucciones estándar (como `FROM`, `ADD`, etc. etc.)
4. Docker contruirá automáticamente una imagen a partir de esas instrucciones

Nota: En _docker_ un contenedor es una imagen con una capa de lectura-escritura en la cima de muchas capas de solo-lectura. Esas capas inferiores se denominan "imágenes intermedias" y se generan cuando se ejecuta el _dockerfile_ durante la etapa de construcción de la imagen.

Evidentemente para construir un _dockerfile_ no basta con conocer Docker. Si quiero construir una imagen para proveer un servicio de base de datos, necesito conocer como funciona ese servicio y el proceso de instalación del mismo para "dockerizarlo"


`ADD`
: Copia un fichero del host al contenedor

`CMD`
: el argumento que pasas por defecto

`ENTRYPOINT`
: el comando que se ejecuta por defecto al arrancar el contenedor

`ENV`
: permite declarar una variable de entorno en el contenedor

`EXPOSE`
: abre un puerto del contenedor

`FROM`
: indica la imagen base que utilizarás para construir tu imagen
  personalizada. Esta opción __es obligatoria__, y además __debe ser
  la primera instrucción__ del Dockerfile.

`MAINTAINER`
: es una valor opcional que te permite indicar quien es el que se encarga de mantener el Dockerfile

`ONBUILD`
: te permite indicar un comando que se ejecutará cuando tu imagen sea utilizada para crear otra imagen.

`RUN`
: ejecuta un comando y guarda el resultado como una nueva capa.

`USER`
: define el usuario por defecto del contenedor

`VOLUME`
: crea un volumen que es compartido por los diferentes contenedores o con el host
`WORKDIR define el directorio de trabajo para el contenedor.

### Ejemplos de dockerfiles

#### Simple
Un *Dockerfile* muy simple para un script de python

```dockerfile
FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [ "python", "./your-daemon-or-script.py" ]
```

#### nginx

Este es un ejemplo de *Dockerfile* `multi-stage`. En un *Dockerfile* de tipo `multi-stage` cada `FROM` (puede haber varios) usa la base que quieras para abrir una nueva etapa (*stage*) Se pueden copiar artefactos de una etapa anterior de forma selectiva.

- En la primera etapa, que llamaremos `builder` partimos de la imagen `alpine:3.20.1`
- Instalamos:
    - `pcre`, una biblioteca de expresiones regulares
    - `libxml2`
    - `libxslt`
    - `gcc`, el compilador gnu
    - `make`, la herramienta
    - `libc-dev`
    - `pcre-dev`
    - `zlib-dev`
    - `libxml2-dev`
    - `libxslt-dev`
- Descargamos el código de *nginx* en el directorio `/tmp` y lo descomprimimos
- Ejecutamos `autoconfigure` especificando que se instale en `/opt`
- Compilamos con `make` e instalamos con `make install`
- Desinstalamos la bibliotecas y borramos la caché (aunque no creo que esto valga para nada, hasta donde yo entiendo la etapa `builder` se descarta)

``` dockerfile
FROM alpine:3.20.1 AS builder
RUN apk add --update \
            --no-cache \
            pcre~=8.45-r3 \
            libxml2~=2.12.7-r0 \
            libxslt~=1.1.39-r1 \
            gcc~=13.2 \
            make~=4.4 \
            musl-dev~=1.2.5 \
            pcre-dev~=8.45 \
            zlib-dev~=1.3.1 \
            libxml2-dev~=2.12.7 \
            libxslt-dev~=1.1.39 && \
    cd /tmp && \
    wget -q https://github.com/nginx/nginx/archive/master.zip -O nginx.zip && \
    unzip nginx.zip && \
    cd nginx-master && \
    ./auto/configure --prefix=/opt/nginx && \
    make && \
    make install && \
    apk del gcc make libc-dev pcre-dev zlib-dev libxml2-dev libxslt-dev && \
    rm -rf /var/cache/apk

FROM alpine:3.20.1

ARG UID=${UID:-1000}
ARG GID=${GID:-1000}

RUN apk add --update \
            --no-cache \
            pcre~=8.45 \
            libxml2~=2.12.7 \
            libxslt~=1.1.39 \
            tini~=0.19 \
            shadow~=4.15 &&\
    rm -rf /var/cache/apk && \
    groupmod -g $GID www-data && \
    adduser -u $UID -S www-data -G www-data && \
    mkdir /html

COPY --from=builder /opt /opt

COPY nginx.conf /opt/nginx/conf/nginx.conf
COPY entrypoint.sh /

EXPOSE 8080
VOLUME /html

RUN chown -R www-data:www-data /html && \
    chown -R www-data:www-data /opt/nginx

USER www-data


ENTRYPOINT ["tini", "--"]
CMD ["/bin/sh", "/entrypoint.sh"]
```

La segunda etapa empieza en la linea 24 definiendo un par de variables para establecer en `UID` y el `GID` del usuario `www-data` que usaremos dentro del contenedor `nginx` para ejecutar el servidor.

- Instalamos las bibliotecas:
    - `pcre`
    - `libxml2`
    - `libxslt`
    - `tini`, un micro `init` ideal para implementar contenedores ligeros
    - `shadow`, el PAM de Alpine
- Limpiamos la caché de instalación de paquetes
- Cambiamos el `GID`para el grupo `www-data`
- Creamos el usuario `www-data` con el `UID` especificado
- Creamos el directorio `/html`
- Copiamos los ejecutables de *nginx* de la etapa anterior
- Copiamos el `nginx.conf` y el `entrypoint.sh` desde el directorio de trabajo del *Dockerfile*
- Declaramos el puerto de acceso y el volumen correspondiente a `/html`
- Ejecutamos como usuario `www-data` el *entrypoint* del contenedor.

##### Ficheros auxiliares

**entrypoint**

El fichero `entrypoint` que hemos usado es muy simple:

```bash
#!/bin/bash
mediaowner=$(ls -ld /html | awk '{print $3}')
echo "Current /html owner is $mediaowner"
/opt/nginx/sbin/nginx -g "daemon off;"
```

El fichero `nginx.conf` tampoco es muy complicado. Dejamos secciones comentadas para tener algunas referencias.

```nginx
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       8080;
        server_name  localhost;

        access_log /dev/stdout;
        error_log /dev/stdout info;
        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /html;
            index  index.html index.htm;

            include mime.types;
            try_files $uri $uri/ =404;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}
```


## Networks

### Bridge

```bash
docker network ls              # Vemos las redes creadas

docker run -dit --name alpine1 alpine ash
docker run -dit --name alpine2 alpine ash

docker container ls            # Vemos los dos alpine

docker network inspect bridge  # Vemos que se han conectado al bridge

docker attach alpine1          # nos conectamos al contenedor

ping alpine2                   # NO FUNCIONA por nombre
ping 172.17.0.4                # FUNCIONA por ip

# Para hacer un detach ordenado del alpine1 C-p C-q
```

Vamos a crear una red bridge propia (_user defined bridge_):

```bash
docker network create --driver bridge alpine-net   # Creamos la red
docker network ls                                  # Vemos redes existentes
docker network inspect alpine-net

docker run -dit --name alpine1 --network alpine-net alpine ash # Creamos tres alpines en la red bridge alpine-net
docker run -dit --name alpine2 --network alpine-net alpine ash
docker run -dit --name alpine3 --network alpine-net alpine ash
docker run -dit --name alpine4  alpine ash       # este alpine4 está en la bridge default

docker network connect bridge alpine3            # alpine3 está conectado a dos redes: default y alpine-net

docker container ls
docker inspect alpine-net
docker inspect bridge

docker attach alpine3          # nos conectamos a alpine3
ping alpine1                   # FUNCIONA, en redes definidas funciona
                               # por nombre y por IP

ping alpine4                   # NO FUNCIONA alpine3 y alpine4 se conectan
                               # por la default bridge que no resuelve nombres
# detach C-p C-q
```

Si probamos desde `alpine1` podremos hacer ping a los `alpine2` y
`alpine3`, por nombre o dirección ip, pero no podremos llegar a
`alpine4` de inguna manera, ya que está en otra red.

----
__IMPORTANTE__
Un _docker-compose_ siempre crea una _user defined
bridge_,  aunque no se lo indiquemos.

----

## _Volumes_ y _Bind Mounts_

### Referencias

* [Understanding Docker Volumes: Persisting a Grafana Configuration
  File](https://www.datamachines.io/blog/understanding-docker-volume-persisting-a-grafana-configuration-file)
* [Stackoverflow: docker data volume vs mounted host directory](https://stackoverflow.com/questions/34357252/docker-data-volume-vs-mounted-host-directory)
* [Docker Storage](https://docs.docker.com/storage/)

Ojito con esto por que, para mi, son muy diferentes y no encontré
ningún sitio que lo dejara claro desde el principio. Se crean con la
misma opción `-v` o `--volume` en el comando pero __hay diferencias__.

### _Volumes_

En principio los contenedores son volátiles y eso está bien para
ciertas cosas se supone que tienes que diseñarlos para que sean muy
volátiles y que se puedan crear y matar fácilmente, pero en la
práctica no. Lo normal es que necesites tener datos persistentes, o
incluso datos compartidos entre varios contenedores.


* Un _volume_ es un espacio de almacenamiento que crea _Docker_
  internamente, le puedes dar nombre y _docker_ lo va a almacenar en
  algún lugar del disco duro del _host_ (en linux tipicamente
  `/var/lib/docker/volumes`)
* Se pueden crear previamente dándoles un nombre y separarlos de la
  creación del contenedor: `docker volume create grafana-storage`. En
  _docker-compose_ esto supone declararlos como _external_:

  ```docker-compose
  volumes:
  grafana-storage:
    external: true
  ```

  Eso hace que el _docker-compose file_ no sea independiente (no
  parece lo más elegante).

* Si los declaramos en el fichero _docker-compose.yml_ se crearán en el
  caso de que no existan (__parece la mejor manera__)

  ```docker-compose
  volumes:
      - web_data:/usr/share/nginx/html:ro
  ```

  O también:

  ```docker-compose
  volumes:
  web_data:
    name: ${VOLUME_ID}

  services:
    app:
      image: nginx:alpine
      ports:
        - 80:80
      volumes:
        - web_data:/usr/share/nginx/html:ro
  ```

### _Bind Mounts_

En este caso montamos un fichero o directory __del sistema de ficheros
del host__ en el contenedor.

### Diferencias entre _volumes_ y _bind mounts_

* Los _bind mounts_ son más simples a la hora de hacer copias de
  seguridad (aunque en linux no veo grandes diferencias)
* Los _volumes_ se pueden gestionar desde el CLI del _Docker_
* Los _volumes_ siempre funcionan, en cualquier S.O. así que tus
  _dockerfiles_ o tus _docker compose files_ serán multiplataforma.
* Los _volumes_ se pueden compartir entre contenedores fácilmente.
* El almacenamiento de los _volumes_ es teoricamente más facil de
  implementar en plataformas remotas (la nube)
* Los _bind mounts_ pueden dar problemas con los permisos de usuarios
  (el usuario que se usa en el _container_ quizás no exista en el
  _host_)
* Los _bind mounts_ son muy dependientes con el S.O. se suelen
  declarar en variables de entorno para intentar desacoplar los
  _dockerfiles_ y los _docker-compose files_ del S.O. que use cada
  uno, por que las rutas de ficheros evidentemente dependen del S.O.

### Renombrar un volumen (receta)

```bash
#/bin/bash
docker volume create --name $2
docker run --rm -it -v $1:/from -v $2:/to alpine ash -c "cd /from ; cp -av . /to"
[ $? -eq 0 ] && docker volume rm $1
```


## _docker-compose_

* [doc oficial](https://docs.docker.com/compose/compose-file)
* [edututorial](https://www.educative.io/blog/docker-compose-tutorial)


Es el siguiente paso en abstracción. Con _docker-compose_ podemos definir y ejecutar aplicaciones compuestas de múltiples contenedores.

La definición se especifica mediante un fichero _YAML_ que permite configurar las aplicaciones y también crearlas.

Las principales ventajas de _docker-compose_ son:

* Múltiples entornos aislados en un unico _host_
* Preservar los volúmenes de datos cuando se crean los contenedores
* Se recrean únicamente los contenedores que cambian
* Orquestar múltiples contenedores que trabajan juntos
* Permite definir variable y mover orquestaciones entre entornos

### Flujo de trabajo

1. Definir los entornos de aplicación con un _dockerfile_
1. Definir los servicios provistos mediante _docker-compose_. Esto permitirá ejecutarlos en un entorno aislado.
3. Lanzar los servicios ejecutando el _docker-compose_

### _docker-compose_ fichero de configuración

La estructura básica de un fichero de configuración para _docker-compose_ tiene esta pinta:

```dockercompose
version: 'X'

services:
  web:
    build: .
    ports:
     - "5000:5000"   # host:container mejor siempre como string
    volumes:
     - .:/code
  redis:
    image: redis
```

Se puede ver más claro con un fichero real:

```docker-compose
version: '3'
services:
  web:
    # Path to dockerfile.
    # '.' represents the current directory in which
    # docker-compose.yml is present.
    build: .

    # Mapping of container port to host
    ports:
      - "5000:5000"
    # Mount volume
    volumes:
      - "/usercode/:/code"

    # Link database container to app container
    # for reachability.
    links:
      - "database:backenddb"

  database:

    # image to fetch from docker hub
    image: mysql/mysql-server:5.7

    # Environment variables for startup script
    # container will use these variables
    # to start the container with these define variables.
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
      - "MYSQL_USER=testuser"
      - "MYSQL_PASSWORD=admin123"
      - "MYSQL_DATABASE=backend"
    # Mount init.sql file to automatically run
    # and create tables for us.
    # everything in docker-entrypoint-initdb.d folder
    # is executed as soon as container is up nd running.
    volumes:
      - "/usercode/db/init.sql:/docker-entrypoint-initdb.d/init.sql"
```

`version: '3'`

: Indica la versión del compose-file que estamos usando ([lista de
  versiones y manual de referencia]( https://docs.docker.com/compose/compose-file/))

`services`

: Esta sección define los diferentes containers que queremos crear, en
  nuestro ejemplo solo habrá dos "web" y "database"

`web`

: Marca el comienzo de la definición del servicio "web", en este
  ejemplo sería un servicio implementado con _Flask_

`build`

: Indica el _path_ al _dockerfile_ que define el servicio, en este
  caso es relativo y el "." significa que el _dockerfile_ está en el
  mismo directorio que el fichero _yaml_ del _compose_

`ports`

: Mapea puertos desde el container a puertos del _host_

`volumes`

: mapea sistemas de ficheros del host en el contenedor (igual que la
  opción `-v` en _docker_)

`links`

: Indica "enlaces" entre contenedores, para la _bridge network_
  debemos indicar que servicios pueden acceder a otros

`image`

: alternativamente al _dockerfile_ podemos especificar que nuestro
  servicio se basa en una imagen pre-construida

`enviroment`

: Permite especificar una variable de entorno en el contenedor (igual
  que la opción `-e` en _docker_)

### Gestión de variables en _docker-compose_

Las variables pueden definirse en varios sitios, por orden de
precedencia serían:

1. Compose file (en una sección _enviroment_)
2. Variables de entorno del sistema (en nuestro intérprete)
3. _Enviroment file_, es un fichero en el mismo directorio que el
   fichero de _compose_ con el nombre `.env`
4. En el _dockerfile_
5. La variable no está definida

La opción _Enviroment file_ es un fichero que se llama `.env`, podemos
usar otro nombre cualquiera incluyendo la directiva `env_file` en el
fichero _compose_. Por ejemplo:

```docker
env_file:
 - ./secret-stuff.env
```

Se supone que __no__ vas a subir tus secretos a un repo en la nube. El
fichero `.env` probablemente no deba estar incluido en _git_ en un
entorno de producción.

[Variables, documentación oficial](https://docs.docker.com/compose/environment-variables/)

### docker-compose comandos útiles

Reconstruir un contenedor
: Cuando cambiamos la definición de un servicio dentro del fichero
  `docker-compose.yml`, podemos reconstruir ese servicio con el
  comando:
  ```bash
  docker-compose up -d --no-deps --build <service>
  ```
  Tenemos una opción más moderna que sería el comando:
  ```bash
  docker-compose build --no-cache <service>
  ```

  O bien:
  ```bash
  docker-compose up -d --no-cache --build <service>
  ```


## Mosquitto + Influxdb + Graphana con _docker_ y _docker-compose_

[referencia](https://github.com/Nilhcem/home-monitoring-grafana)

Creamos un directorio para nuestro proyecto que llamamos `homesensor`
(por ejemplo)

```bash
mkdir homesensor
cd homesensor
```

### Mosquitto

Creamos un directorio para nuestro container de _Mosquitto_

```bash
mkdir 01_mosquitto
cd 01_mosquitto
```

#### Configuración de _mosquitto_

Creamos un fichero de configuración de *Mosquitto* `mosquitto.conf` con el siguiente
contenido:

```conf
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
allow_anonymous false
password_file /mosquitto/config/users
```

Con este fichero le indicamos a _Mosquitto_:

* Qué queremos que tenga persistencia de datos. Así salvará su estado
  a disco cada 30 minutos (configurable), de lo contrario usaría
  exclusivamente la memoria.
* le indicamos en que _path_ debe guardar los datos persistentes
  (`/mosquitto/data/`)
* configuramos el fichero de log (`/mosquitto/log/mosquitto.log`)
* prohibimos usuarios anónimos
* establecemos el listado de usuarios con su password

----
__Nota__: Puedes echar un ojo al fichero de ejemplo de configuración
de mosquitto arrancando un contenedor sin ninguna opción de mapeado de
volúmenes. O consultando la documentación de _mosquitto_. Hay
muchísimas más opciones de las que proponemos.

----

Tendremos que preparar también un fichero `users` de usuarios para
establecer los usuarios y sus password. Podemos crear un fichero de
usuarios y passwords en claro con el formato:

```
mqttuser:53cret0
```

Y cifrarlo con el comando `mosquitto_passwd -U <filename>`

Si solo tenemos un usuario lo podemos crear con el comando
`mosquitto_passwd -c <filename> <username>`

----
__Nota__:

Nos adelantamos a los acontecimientos, pero si no tienes _mosquitto_
instalado en tu máquina puedes cifrar el fichero de usuarios con un
contenedor de _mosquitto_ transitorio, que se encargue del cifrado:

```bash
docker run --rm \
-v `pwd`/users:/mosquitto/config/users \
eclipse-mosquitto \
mosquitto_passwd -U /mosquitto/config/users
```
----


#### _mosquitto_ en _docker_ desde linea de comandos

Una vez creados estos dos ficheros podemos crear un contenedor
_mosquitto_ con _ podemos hacerlo desde linea de comandos.

```bash
docker run -it -p 1883:1883 \
-v `pwd`/mosquitto.conf:/mosquitto/config/mosquitto.conf \
-v `pwd`/users:/mosquitto/config/users eclipse-mosquitto
```

`run`

: Ejecutará el contenedor, si la imagen especificada no existe la
  bajará del _dockerhub_

`-it`

: La opción `i` ejecuta el contenedor en modo interactivo, la opción
  `t` abrira un terminal contra el contenedor.

`-p 1883:1883`

: mapea el puerto 1883 del contendor al puerto 1883 del host

``-v `pwd`/mosquitto.conf:/mosquitto/config/mosquitto.conf``

: mapea el fichero `mosquitto.conf` del directorio actual (`pwd`) en
  el volumen del contenedor: `/mosquitto/config/mosquitto.conf`

``-v `pwd`/users:/mosquitto/config/users``

: Igual que el anterior pero con el fichero `users`

`eclipse-mosquitto`

: La imagen que vamos a usar para crear nuestro contenedor, si no
  especificamos nada se usará `:latest`, es decir la imagen más
  reciente.

Podemos probar esta configuración con los clientes de mqtt (tenemos
que tener instalado el paquete `mosquitto-clients` en nuestra máquina)

En un terminal lanzamos el _subscriber_:

```bash
mosquitto_sub -h localhost -t test -u mqttuser -P 53cret0
```

Y en otro terminal publicamos un mensaje en el _topic_ test:

```bash
mosquitto_pub -h localhost -t test -u mqttuser -P 53cret0 -m "Hola mundo"
```

Comprobaremos que los mensajes se reciben en el primer terminal.


### InfluxDB

Vamos a instalar _InfluxDB_ en _docker_. Igual que con _Mosquitto_
vamos a mapear los ficheros de configuración y los ficheros donde
realmente guarda los datos la base de datos en nuestro sistema de
ficheros local (o el del host)

Estamos en el directorio `homesensor` y creamos el directorio
`02_influxdb`

 ```bash
mkdir 02_influxdb
cd 02_influxdb
```

Nos bajamos la imagen oficial de _InfluxDB_ con `docker pull influxdb`

Podemos ejecutar un contenedor para probar la imagen con

```bash
docker run -d --name influxdbtest -p 8086:8086 influxdb
```

La opción `-d` (_detached_) es opcional, si no la pones verás el log de
influxdb, pero no libera el terminal.

Ahora desde otro terminal (o desde el mismo si usaste `-d`) podemos
conectarnos al contenedor con uno de los dos comandos siguientes:

```bash
docker exec -it influxdbtest  /bin/bash        # con un terminal
docker exec -it influxdbtest /usr/bin/influx   # con el cliente de base de datos
```

Dentro del cliente de base de datos podríamos hacer lo que quisieramos, por ejemplo:

```sql
show databases
create database mydb
use mydb
```

Como hemos mapeado el puerto 8086 del contenedor al host (nuestro pc)
también podemos conectarnos a la base de datos con un cliente en
nuestro pc, con scripts de python, etc. etc.

#### Dos maneras de lanzar el contenedor

Podemos examinar la historia de la imagen docker `influxdb` con
`docker history influxdb`(desde un terminal en nuestro pc). Si sale
muy apelotonada podemos indicarle que no recorte la salida con `docker
history --no-trunc influxdb`

Veremos que se define `ENTRYPOINT ["/entrypoint.sh"]`

Podemos investigar el contenido del fichero `entrypoint.sh` abriendo un
terminal contra el contenedor `influxdbtest`

Después de investigar el contenido del fichero `influxdbtest.sh` y el
contenido del fichero `init-influxdb.sh` veremos que hay dos formas de
lanzar un contenedor a partir de la imagen `influxdb` (También nos
podíamos haber leído [esto](https://hub.docker.com/_/influxdb) para
llegar a lo mismo más rápido)

La primera forma de lanzar el contenedor es la que hemos usado hasta
ahora:

```bash
docker run -d --name influxdbtest -p 8086:8086 influxdb
```

La segunda forma (más interesante para ciertas cosas) sería invocando
el script `init-influxdb.sh`:

```bash
docker run  --name influxdbtest influxdb /init-influxdb.sh
```

Con esta segunda forma de lanzar el contenedor tenemos dos ventajas:

* podemos usar variables de entorno y scripts de inicio, como nos
  explican en [la pagina de
  dockerhub](https://hub.docker.com/_/influxdb) que citamos antes.
* podemos tener un directorio `/docker-entrypoint-initdb.d` en la raiz
  de nuestro contenedor y cualquier script con extensión `.sh` (shell)
  o `.iql` (Influx QL) se ejecutará al arrancar el contenedor.

__Pero__ la segunda forma de lanzar el contenedor sólo es práctica
para crear y configurar una base de datos, no está pensada para lanzar
un contenedor "de producción"

Cuando acabemos de jugar, paramos el contenedor y lo borramos:

```bash
docker stop influxdbtest
docker rm influxdbtest
```

#### Configuración de _InfluxDB_

Vamos a preparar las cosas para lanzar un contenedor transitorio de
_InfluxDB_ que nos configure la base de datos que queremos usar.

Después lanzaremos el contenedor definitivo que usará la base de datos
configurada por el primero para dejar el servicio _InfluxDB_
operativo.

Lo primero es hacernos un fichero de configuración para _InfluxDB_.
Hay un truco para que el propio _InfluxDB_ nos escriba el fichero de
configuración por defecto:

```bash
cd homesensor/02_influxdb

docker run --rm influxdb influxd config |tee influxdb.conf
```

Este comando ejecuta un contenedor transitorio basado en la imagen
`influxdb`, es un contenedor transitorio por que con la opción `--rm`
le decimos a _docker_ que lo elimine al terminar la ejecución. Este
contenedor lo unico que hace es devolvernos el contenido del fichero
de configuración y desaparecer de la existencia.

Ahora tendremos un fichero de configuración `influxdb.conf` en nuestro
directorio `02_influxdb`. Este fichero lo tenemos que mapear o copiar
al fichero `/etc/influxdb/influxdb.conf` del contenedor (lo vamos a mapear)

Crearemos también un volumen en nuestro contenedor que asocie el
directorio `/var/lib/influxdb` del contenedor al directorio en el
sistema de ficheros local: `homesensor/data/influxdb` De esta forma
tendremos los ficheros que contienen la base de datos en el sistema de
ficheros del host.

También vamos a crear un directorio
`homesensor/02_influxdb/init-scripts` que mapearemos al directorio
`/docker-entrypoint-initdb.d` del contenedor. En este directorio
crearemos un script en _Influx QL_ para crear usuarios y políticas de
retención para nuestra base de datos.

En el directorio `homesensor/02_influxdb/init-scripts`, creamos el
fichero:

`influxdb_init.iql`

```sql
CREATE RETENTION POLICY one_week ON homesensor DURATION 168h REPLICATION 1 DEFAULT;
CREATE RETENTION POLICY one_year ON homesensor DURATION 52w REPLICATION 1;
CREATE USER telegraf WITH PASSWORD 'secretpass' WITH ALL PRIVILEGES;
CREATE USER nodered WITH PASSWORD 'secretpass';
CREATE USER pybridge WITH PASSWORD 'secretpass';
GRANT ALL ON homesensor TO nodered;
GRANT ALL ON homesensor TO pybridge;
```

---
__NOTA___:
Creamos el usuario telegraf con todos los privilegios, eso lo
convierte en administrador de _InfluxDB_. De momento lo vamos a dejar
así para facilitar las pruebas del contenedor _Telegraf_.

---

¡Vale! Ya tenemos todo listo. Ahora tenemos que hacer dos cosas.

1. Ejecutar un contenedor transitorio que va a crear
    * Mediante variables de entorno (opciones `-e`):
        * Un usario administrador de _InfluxDB_
        * Una base de datos `homesensor`
        * Un usuario genérico de la base de datos `homesensor` que se llama `hsuser`.
    * Mediante el script en _InfluxQL_:
        * Un par de políticas de retención para la base de datos `homesensor`
        * Tres usuarios de bases de datos, a los que damos privilegios
          sobre la base de datos `homesensor`:
            * `telegraf`
            * `noderedu`
            * `pybridge`
2. Una vez configurada toda la base de datos: activar el acceso seguro
   en la configuración y lanzar el contenedor `influxdb` que se va a
   quedar trabajando.

Para el primer paso __configurar la base de datos__ el comando es:

```bash
cd homesensor       # ASEGURATE de estar en el directorio homesensor
sudo rm -rf data/influxdb

docker run --rm \
      -e INFLUXDB_HTTP_AUTH_ENABLED=true \
      -e INFLUXDB_ADMIN_USER=admin -e INFLUXDB_ADMIN_PASSWORD=s3cr3t0 \
      -e INFLUXDB_DB=homesensor \
      -e INFLUXDB_USER=hsuser -e INFLUXDB_USER_PASSWORD=pr1vad0 \
      -v $PWD/data/influxdb:/var/lib/influxdb \
      -v $PWD/02_influxdb/influxdb.conf:/etc/influxdb/influxdb.conf \
      -v $PWD/02_influxdb/init-scripts:/docker-entrypoint-initdb.d \
      influxdb /init-influxdb.sh
```

----
__IMPORTANTE__

* Si no pones la opción `-e INFLUXDB_HTTP_AUTH_ENABLED=true` los
  comandos de creación de usuarios fallan sin dar error.
* Cada vez que lances el contenedor de iniciación __ASEGURATE__ de
  borrar previamente el directorio `homesensor/data/influxdb` o
  fallará todo sin dar errores.
----

Para el segundo paso, es __necesario__ habilitar la seguridad en el
fichero de configuración antes de arrancar el contenedor. En el
fichero `influxdb.conf` asegurate de cambiar la linea (de _false_ a _true_):

```ini
[http]
- auth-enabled = false
+ auth-enabled = true
```

Y por fin, para arrancar el contenedor `influxdb` que se quedará dando
servicio, el comando es:

```bash
cd homesensor   # asegurate de estar en el directorio homesensor
docker run -d --name influxdb -p 8086:8086 \
      -v $PWD/data/influxdb:/var/lib/influxdb \
      -v $PWD/02_influxdb/influxdb.conf:/etc/influxdb/influxdb.conf \
      influxdb
```

Con el contenedor _influxdb_ disponible podemos conectarnos a la base
de datos, ya sea desde nuestra máquina (si tenemos el cliente
instalado en local) o abriendo un terminal al contenedor.

Si ejecutamos:

```bash
> show databases
ERR: unable to parse authentication credentials
Warning: It is possible this error is due to not setting a database.
Please set a database with the command "use <database>".
```

Nos da un error de autenticación.

Si añadimos usuario y password entramos sin problemas y podemos vere
que la base de datos `homesensor` y todos los usuarios se han creado
correctamente.

```bash
influx --username admin --password s3cr3t0
Connected to http://localhost:8086 version 1.8.3
InfluxDB shell version: 1.8.3
> show databases
name: databases
name
----
homesensor
_internal
> show users
user         admin
----         -----
admin        true
idbuser      false
telegrafuser false
nodereduser  false
pybridgeuser false

```
Un ejemplo de continous query para el dia que haga falta:

```sql
CREATE CONTINUOUS QUERY "cq_30m" ON "homesensor"
BEGIN
  SELECT mean("temperature") AS "mean_temperature",mean("humidity") AS "mean_humidity" \
  INTO "a_year"."acum_ambient"
  FROM "ambient"
  GROUP BY time(30m)
END;

```

### Telegraf

Preparamos un fichero de configuración de _Telegraf_. _Telegraf_ es de
la familia _InfluxDB_ así que soporta el mismo truco, para volcar una
configuración por defecto:

```bash
cd homesensor
mkdir 03_telegraf
cd 03_telegraf
docker run --rm telegraf telegraf config |tee telegraf.conf
```

En _Telegraf_ todo se hace en el fichero de configuración, en el se
programan las entradas y las salidas.

#### Usando `container:network`

Este escenario lo pongo por que me pareció un caso interesante. Pero
no veo que sea posible implementarlo en _docker-compose_ así que no
será el definitivo.

1. Lanzamos el container `influxdb`

```bash
docker run -d --name influxdb -p 8086:8086 \
      -v $PWD/data/influxdb:/var/lib/influxdb \
      -v $PWD/02_influxdb/influxdb.conf:/etc/influxdb/influxdb.conf \
      -v $PWD/02_influxdb/init-scripts:/docker-entrypoint-initdb.d \
      influxdb
```
2. Modificamos el fichero de configuración de _Telegraf_ para poner usuario y password.

```init
## HTTP Basic Auth
username = "telegraf"
password = "secretpass"
```

3. Lanzamos el contenedor `telegraf`

```bash
docker run -d --name telegraf \
      --network=container:influxdb \
      -v /proc:/host/proc:ro \
      -v $PWD/03_telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro \
      telegraf
```

Si nos conectamos con el cliente `influx` al servidor de bases de
datos (o nos conectamos al contenedor y ejecutamos el cliente) veremos
que _Telegraf_ está mandando todas las estadísticas que tiene
definidas por defecto de nuestro host.

Para que todo esto funcione:

* Hemos mapeado el directorio `/proc` del linux de nuestro host
  (nuestro pc o servidor) en el directorio `/host/proc` del contenedor
  `telegraf`, así que _Telegraf_ puede leer varias estadísticas de
  nuestro _host_ (es decir nuestro pc) y escribir los distintos
  valores como _measurements_ en la base de datos `telegraf` (es el
  nombre de la base de datos que usa por defecto).
* El contenedor `telegraf` ha creado esta base de datos que no existía
  gracias a que el usario que le hemos configurado para acceder a la
  base de datos tiene permisos de administración, de lo contrario
  tendríamos que haber creado nosotros la base de datos antes de que
  se conectara.
* Hemos usado la opción `--network=container:influxdb`, esta opción
  hace que el contenedor `telegraf` se cree en el `network stack` del
  contenedor `influxdb`, en la práctica el _Telegraf_ se cree que está
  en la misma máquina que _InfluxDB_ y se comunica via el interfaz
  _loopback_ Esta opción tan curiosa creo que no se puede implementar
  en _docker-compose_ (al menos yo no he encontrado nada parecido)


### Grafana

Grafana se puede configurar a través de su propia interfaz web.


---- __NOTA__:

No me aclaro con [la documentación de _Grafana_ para
_Docker_](https://grafana.com/docs/grafana/latest/installation/docker/),
la única forma en que he conseguido mapear la base de datos en el
directorio local `data/grafana` ha sido lanzar el contenedor como
_root_. No se que riesgos implica eso.

----

Lanzamos el contenedor:

```bash
docker run --user root -d --name=grafana -p 3000:3000 \
       -v $PWD/data/grafana:/var/lib/grafana \
       grafana/grafana
```


La primera vez que nos conectamos a grafana tenemos que entrar con el
usuario `admin` con contraseña `admin`. Nos pedirá cambiar la
contraseña.


* Nos conectamos a <http://localhost:3000>
* Configuramos la contraseña del administrador
* Añadimos InfluxDB como fuente de datos. En este escenario tenemos que
  configurar la dirección IP del contenedor `influxdb` así que
  tendremos que usar algún comando del estilo `docker inspect
  influxdb` o `docker inspect bridge |grep influxdb -A 5` para
  averiguar la dirección IP.
    * __name__ influxdb
    * __query language__ InfluxQL
    * __http url__ <http://ipaddres:8086>
    * __Access__ server
    * __Auth__ Basic auth with credentials
    * __user__ telegraf (con su contraseña)
    * __database__ telegraf, configurar usuario y contraseña
    * __http method__ GET
* Por ultimo el report de grafana lo importamos de uno que hay
  pre-construido para este escenario con el id 1443

  [Grafana Data Sources tutorial](https://grafana.com/tutorials/grafana-fundamentals/?utm_source=grafana_gettingstarted#1)



### Solución completa con _docker-compose_

#### _mosquitto_ en _docker-compose_

Definiremos el servicio _mosquitto_ en nuestro fichero
`homesensor/docker-compose.yml`


```docker
version: '3'

services:
  mosquitto:
    image: eclipse-mosquitto
    container_name: mosquitto
    ports:
      - 1883:1883
    volumes:
      - ./01_mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf
      - ./01_mosquitto/users:/mosquitto/config/users
      - ${DATA_DIR}/mosquitto/data:/mosquitto/data
      - ${DATA_DIR}/mosquitto/log:/mosquitto/log
    restart: always
```

Como se puede ver en el fichero `homesensor/docker-compose.yml` hemos
definido dos volúmenes adicionales para tener accesibles desde el
_host_ el directorio de datos: `/mosquitto/data` y el directorio de
logs `/mosquitto/log`

El mapeado de los nuevos volúmenes está controlado por una variable
`DATA_DIR` que podemos pasar desde el entorno de sistema o con un
fichero `.env` para que apunte, por ejemplo, al directorio
`homesensor/data` en el host.

`.env`

```bash
DATA_DIR=./data
```


Ahora podemos lanzar el contenedor con `docker-compose up` (asegúrate
de borrar el contenedor que creamos a mano, y de establecer la
variable `DATA_DIR` de alguna manera)

Una vez lanzado el contenedor podemos hacer pruebas con los clientes
mqtt instalados y comprobaremos que los datos y el log de _mosquitto_
se guardan en el host.

Otra cosa que podemos comprobar al usar _docker-compose_ es que se
crea una red específica y segregada para nuestros contenedores.

```bash
docker network ls
NETWORK ID          NAME                 DRIVER              SCOPE
d48756f76467        bridge               bridge              local
315e4570dff1        homesensor_default   bridge              local
9b579976258b        host                 host                local
51c50bab6f35        none                 null                local

docker inspect homesensor_default

.
.
.
 "Containers": {
            "39a45dde629a0ab6f834e7c59f0d9785c64ba31b0d3d93c6c8d2622d31607c28": {
                "Name": "cmosquitto",
                "EndpointID": "504ae74b9109e6d87cd2359f7d6b17352388a5a9a48f7bba5aa7ad29fa43977e",
                "MacAddress": "02:42:ac:1b:00:02",
                "IPv4Address": "172.27.0.2/16",
                "IPv6Address": ""
            }
        },
.
.
.
```

Podemos ver que ahora tenemos la red `homesensor_default` y que dentro
de esa red solo tenemos el contenedor `mosquito`.



#### _InfluxDB_ en _docker-compose_

Añadimos la configuración de _InfluxDB_ a nuestro fichero `dockercompose.yml`

#### Grafana en _docker-compose_

Añadimos la configuración del servicio






<!--

Preparamos directorios y ficheros de trabajo

```bash
mkdir -p ~/work/docker/{traefik,portainer}
touch ~/work/docker/traefik/{acme.json,traefik.yml}
chmod 600 ~/work/docker/traefik/acme.json
```

`acme.json` es el fichero que almacenará los certificados HTTPS
`traefik.yml` es el fichero de configuración de _Traefik_.

Un fichero básico para la configuración de _Traefik_ sería:

```yml
api:
  dashboard: true
  insecure: true

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

certificatesResolvers:
  http:
    acme:
      email: <my_email@example.com>
      storage: acme.json
      httpChallenge:
        entryPoint: http
```

Un fichero de configuración más completo:

```yml

version: "3.3"

services:
  ################################################
  ####        Traefik Proxy Setup           #####
  ###############################################
  traefik:
    image: traefik:v2.0
    restart: always
    container_name: traefik
    ports:
      - "80:80"     # <== http
      - "8080:8080" # <== :8080 is where the dashboard runs on
      - "443:443"   # <== https
    command:
    #### These are the CLI commands that will configure Traefik and tell it how to work! ####
      ## API Settings - https://docs.traefik.io/operations/api/, endpoints - https://docs.traefik.io/operations/api/#endpoints ##
      - --api.insecure=true  # <== Enabling insecure api, NOT RECOMMENDED FOR PRODUCTION
      - --api.dashboard=true # <== Enabling the dashboard to view services, middlewares, routers, etc...
      - --api.debug=true     # <== Enabling additional endpoints for debugging and profiling
      ## Log Settings (options: ERROR, DEBUG, PANIC, FATAL, WARN, INFO) - https://docs.traefik.io/observability/logs/ ##
      - --log.level=DEBUG    # <== Setting the level of the logs from traefik
      ## Provider Settings - https://docs.traefik.io/providers/docker/#provider-configuration ##
      - --providers.docker=true                   # <== Enabling docker as the provider for traefik
      - --providers.docker.exposedbydefault=false # <== Don't expose every container to traefik, only expose enabled ones
      - --providers.file.filename=/dynamic.yaml   # <== Referring to a dynamic configuration file
      - --providers.docker.network=frontend       # <== Operate on the docker network named 'frontend'
      ## Entrypoints Settings - https://docs.traefik.io/routing/entrypoints/#configuration ##
      - --entrypoints.web.address=:80          # <== Defining an entrypoint for port :80 named 'web'
      - --entrypoints.web-secured.address=:443 # <== Defining an entrypoint for https on port :443 named 'web-secured'
      ## Certificate Settings (Let's Encrypt) -  https://docs.traefik.io/https/acme/#configuration-examples ##
      - --certificatesresolvers.mytlschallenge.acme.tlschallenge=true              # <== Enable TLS-ALPN-01 to generate and renew ACME certs
      - --certificatesresolvers.mytlschallenge.acme.email=salvari@gmail.com        # <== Setting email for certs
      - --certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json # <== Defining acme file to store cert information
    volumes:
      - ./letsencrypt:/letsencrypt                # <== Volume for certs (TLS)
      - /var/run/docker.sock:/var/run/docker.sock # <== Volume for docker admin
      - ./dynamic.yaml:/dynamic.yaml              # <== Volume for dynamic conf file, **ref: line 27
    networks:
      - frontend # <== Placing traefik on the network named 'frontend', to access containers on this network
    labels:
    #### Labels define the behavior and rules of the traefik proxy for this container ####
      - "traefik.enable=true"  # <== Enable traefik on itself to view dashboard and assign subdomain to view it
      - "traefik.http.routers.api.rule=Host(`monitor.comacero.com`)" # <== Setting the domain for the dashboard
      - "traefik.http.routers.api.service=api@internal"              # <== Enabling the api to be a service to access
```




----------

**LO QUE SIGUE SON NOTAS SUELTAS PENDIENTES DE ORDENAR Y REVISAR**



## Arrancando mariadb

~~~~{bash}
docker run --name mariadbtest -e MYSQL_ROOT_PASSWORD=mypass -d mariadb
docker inspect mariadbtest
~~~~

Del segundo comando sacamos la ip,

----------

**TODO**:hay que mirar los format que soporta el inspect.

---------




## Cosas variadas

El fichero para lanzar traefik que usamos [en el servidor de
Bricolabs](https://bricolabs.cc/wiki/privado/sysadmin/traefik_e_cryptpad).

~~~{yml}
version: "3.3"
services:
  app:
    image: "traefik:2.1"
    container_name: "traefik"
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
    ports:
      - "80:9080"
      - "443:9443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
~~~


## networks

bridge 172.17.0.0/16

Creamos también:

~~~~{bash}
docker network create --subnet 172.20.0.0/16 backend
docker network create --subnet 172.21.0.0/16 frontend
~~~~





# Configuración en comacero.com

## Networks

Vamos a crear dos redes en el docker:

~~~~{bash}
docker network create --subnet 172.20.0.0/16 backend
docker network create --subnet 172.21.0.0/16 frontend
~~~~

La red `backend` estará aislada del exterior y será solo para que unos
dockers se comuniquen con los otros.

La red `frontend` será la que tenga comunicación con internet. Ya
veremos como queda pero lo más probable es que todo tenga que pasar
por el servidor `traefik`.


## Traefik

Vamos a hacer pruebas con lo que nos recomiendan en [este
tutorial](https://www.youtube.com/watch?v=Gk9WER6DunE)

El fichero _compose_ para el _Traefik_:

```docker-compose.yaml

version: "3.3"

services:
  ################################################
  ####        Traefik Proxy Setup           #####
  ###############################################
  traefik:
    image: traefik:v2.0
    restart: always
    container_name: traefik
    ports:
      - "80:80"     # <== http
      - "8080:8080" # <== :8080 is where the dashboard runs on
      - "443:443"   # <== https
    command:
    #### These are the CLI commands that will configure Traefik and tell it how to work! ####
      ## API Settings - https://docs.traefik.io/operations/api/, endpoints - https://docs.traefik.io/operations/api/#endpoints ##
      - --api.insecure=true  # <== Enabling insecure api, NOT RECOMMENDED FOR PRODUCTION
      - --api.dashboard=true # <== Enabling the dashboard to view services, middlewares, routers, etc...
      - --api.debug=true     # <== Enabling additional endpoints for debugging and profiling
      ## Log Settings (options: ERROR, DEBUG, PANIC, FATAL, WARN, INFO) - https://docs.traefik.io/observability/logs/ ##
      - --log.level=DEBUG    # <== Setting the level of the logs from traefik
      ## Provider Settings - https://docs.traefik.io/providers/docker/#provider-configuration ##
      - --providers.docker=true                   # <== Enabling docker as the provider for traefik
      - --providers.docker.exposedbydefault=false # <== Don't expose every container to traefik, only expose enabled ones
      - --providers.file.filename=/dynamic.yaml   # <== Referring to a dynamic configuration file
      - --providers.docker.network=frontend       # <== Operate on the docker network named 'frontend'
      ## Entrypoints Settings - https://docs.traefik.io/routing/entrypoints/#configuration ##
      - --entrypoints.web.address=:80          # <== Defining an entrypoint for port :80 named 'web'
      - --entrypoints.web-secured.address=:443 # <== Defining an entrypoint for https on port :443 named 'web-secured'
      ## Certificate Settings (Let's Encrypt) -  https://docs.traefik.io/https/acme/#configuration-examples ##
      - --certificatesresolvers.mytlschallenge.acme.tlschallenge=true              # <== Enable TLS-ALPN-01 to generate and renew ACME certs
      - --certificatesresolvers.mytlschallenge.acme.email=salvari@gmail.com        # <== Setting email for certs
      - --certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json # <== Defining acme file to store cert information
    volumes:
      - ./letsencrypt:/letsencrypt                # <== Volume for certs (TLS)
      - /var/run/docker.sock:/var/run/docker.sock # <== Volume for docker admin
      - ./dynamic.yaml:/dynamic.yaml              # <== Volume for dynamic conf file, **ref: line 27
    networks:
      - frontend # <== Placing traefik on the network named 'frontend', to access containers on this network
    labels:
    #### Labels define the behavior and rules of the traefik proxy for this container ####
      - "traefik.enable=true"  # <== Enable traefik on itself to view dashboard and assign subdomain to view it
      - "traefik.http.routers.api.rule=Host(`monitor.comacero.com`)" # <== Setting the domain for the dashboard
      - "traefik.http.routers.api.service=api@internal"              # <== Enabling the api to be a service to access
```

## nginx docker


```shell
docker run --name docker-nginx -p 8080:80 nginx   # mapea el puerto 8080 del host

docker exec -it CONTAINER_ID bash     # para conectarnos con el terminal
```

Crear un contenedor corriendo nginx con contenido estático

Creamos los direcotorios:

```
nginx
└── shtml
```

Creamos un fichero `nginx/Dockerfile` con estas lineas:

``` Dockerfile
FROM nginx:latest
COPY shtml /usr/share/nginx/html
```

Y un contenido _html_ estático en el fichero `shtml/index.html`, por ejemplo:

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="docker-nginx" content="nginx-docker Test">
    <title>Plain HTML site using nginx and docker</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="navbar">
      <a href="https://comacero.gitlab.io/">Blog Comacero</a>
      <a href="https://comacero.gitlab.io/privado/">Comacero Dark</a>
      <a href="https://gitlab.com/pages/">Gitlab Pages</a>
    </div>

    <h1>Hello World!</h1>

    <p>
      This is a simple plain-HTML website on Docker.
    </p>
  </body>
</html>
```

Ahora creamos el contenedor con `docker build -t static-nginx .` (ojo al punto al final)

Y ejecutamos nuestro nuevo contenedor con `docker run --name nginx-static -p 80:80 -d static-nginx`

-->

## Recetas

### Backup de volúmenes

Los pasos típicos

1. Parar todos los contenedores que usen los volúmenes que queremos salvar
2. Arrancar un contenedor dedicado para hacer los backups. Tiene que montar los volúmenes que queremos salvaguardar y montar también un volumen (probablemente un _bind-mount_, donde salvaremos los backups)
3. Hacer los backups
4. Parar el contenedor de backups
5. Arrancar los contenedores de producción

Un ejemplo:

```bash
docker stop targetContainer
mkdir ./backup
docker run --rm --volumes-from targetContainer -v ~/backup:/backup ubuntu bash -c “cd /var/lib/targetCont/content && tar cvf /backup/ghost-site.tar .”

```

### Ejecutar aplicaciones X11 en un contenedor Docker

Supongamos que queremos ejecutar una aplicación X11 en un contenedor y verla en el escritorio del Host.

Tenemos dos alternativas, lanzar la aplicación contra el Xdisplay de nuestro ordenador (`echo "$DISPLAY"`) o preparar otro _display_ dedicado a nuestro contenedor.

{{< admonition type=tip title="Otras vias" open=false >}}

[Aquí](https://www.howtogeek.com/devops/how-to-run-gui-applications-in-a-docker-container/) describen como lanzar aplicaciones via el _X11 socket_ o utilizando VNC. Pero yo creo que las recetas que apunto son más fáciles.

{{< /admonition >}}


#### Usar el Xdisplay de nuestro escritorio

Las ventajas de esta opción es que la aplicación del contenedor aparecerá simplemente como una ventana más en nuestro escritorio, no deberíamos tener ningún problema a la hora de usar nuestros dispositivos de entrada (ratón, teclado, etc.) y de salida (pantalla básicamente).

1. Averiguar nuestro display con `echo "$DISPLAY"`
2. Autorizar las conexiones a nuestro display, por seguridad están prohibidas

   ```bash
   xhost +                  # Esto equivale a autorizar todas las conexiones
                            # Es mejor NO USAR esta opción

   xhost +"local:docker@"   # Esta opción es mucho más segura
                            # solo permite conexiones desde nuestros contenedores
   ```

3. Lanzar nuestro contenedor estableciendo la variable de entorno `$DISPLAY` y fijando el `net` de tipo `host`:

   ```bash
   docker run --rm --it --net=host -e DISPLAY=$DISPLAY
   ```

El detalle importante aquí es limitar lo más posible las conexiones autorizadas a nuestro Xdisplay


#### Lanzar otro display dedicado

Yo suelo usar _Xephyr_ para hacer estos experimentos. _Xephyr_ es un servidor de display anidado. _Xephyr_ nos da un Xdisplay al que podemos conectar aplicaciones X11 pero que funciona como una ventana dentro de nuestro entorno gráfico (es decir está anidado en el Xdisplay de nuestro escritorio).

La ventaja de esta opción es que no abrimos autorizaciones al Xdisplay de nuestro sistema. A cambio tendremos que ver como usa Xephyr nuestro teclado y ratón.

1. Instalar _Xephyr_
   ```bash
   sudo apt install xserver-xephyr
   ```

2. Lanzar una ventana de _Xephyr_

   ```bash
   echo "$DISPLAY"  # Averiguamos nuestro DISPLAY, que suele ser el 0 o el 1
                    # HAY QUE USAR UNO DISTINTO PARA Xephyr

   Xephyr -ac -screen 800x600 -br -reset -terminate 2> /dev/null :1 &  # Arrancamos el Xserver
   ```

    Opciones de Xephyr utilizadas:
    - __-ac__

      Autorizar conexiones de clientes indiscriminadamente (disable access restrictions)
    - __-screen__

      Especificar la geometría de la pantalla
    - __-br__

      La ventana raiz tendrá fondo negro

    - __-reset__

      Reset al terminar el último cliente

    - __-terminate__

      Finalizar cuando se resetee el servidor
    - __2> /dev/null__

      Mandar los mensajes de error al limbo (alias NE en nuestro pc)
    - __:1__

      Arrancar el server en el DISPLAY=1  __TIENE QUE SER DISTINTO DEL DISPLAY DEL SISTEMA__

3. Por último lanzamos nuestro contenedor igual que en la primera opción, pero con el Xdisplay correspondiente a _Xephyr_

    ```bash
    docker run --rm --it --net=host -e DISPLAY=":1"
    ```


## Referencias

* [Getting Started (oficial)](https://docs.docker.com/get-started/)
* [El tutorial de atareao](https://www.atareao.es/tutorial/docker/)
* [The smart person guide to Docker](https://www.techrepublic.com/article/docker-the-smart-persons-guide/)
* [Lista de versiones de compose-file y manual de referencia]( https://docs.docker.com/compose/compose-file/)
* [Kitematic: un interfaz gráfico para docker](https://kitematic.com/)
* [Podman, un docker alternativo](https://www.atareao.es/podcast/es-podman-la-alternativa-a-docker/)

* [Docker-compose tutorial](https://www.educative.io/blog/docker-compose-tutorial) <-- No está mal
* [Advanced Docker-compose configuration](https://runnable.com/docker/advanced-docker-compose-configuration)
* [Docker Compose Tutorial](https://vegibit.com/docker-compose-tutorial/)

* [Awesome Compose](https://github.com/docker/awesome-compose)


### influxdb
* https://thenewstack.io/how-to-setup-influxdb-telegraf-and-grafana-on-docker-part-1/
* https://lazyadmin.nl/it/installing-grafana-influxdb-and-telegraf-using-docker/
* https://dev.to/project42/install-grafana-influxdb-telegraf-using-docker-compose-56e9



### uwsgi and traefik

* https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https
* https://beenje.github.io/blog/posts/running-your-application-over-https-with-traefik/
* https://dockerquestions.com/2020/03/23/route-to-flask-and-vue-containers-with-traefik/
* https://www.fullstackpython.com/wsgi-servers.html
* https://gist.github.com/nknapp/20c7cd89f1f128b8425dd89cbad0b802
* https://medium.com/@tiangolo/full-stack-modern-web-applications-using-python-flask-docker-swagger-and-more-b6609dedb747
* https://stackoverflow.com/questions/44639958/nginx-behind-traefik-docker-swarm-mode-real-ip


### nginx and traefik
* [Static files with nginx and traefik](https://www.simplecto.com/use-traefik-with-nginx-apache-caddyserver-serve-static-files/)
* [Install traefik](https://www.howtoforge.com/tutorial/ubuntu-docker-traefik-proxy/)
* [Traefik and worpress, as usual](https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-16-04)
* [nginx behind traefik question](https://stackoverflow.com/questions/44639958/nginx-behind-traefik-docker-swarm-mode-real-ip)



* https://docs.ovh.com/gb/en/domains/create_a_dns_zone_for_a_domain_which_is_not_registered_at_ovh/



### Kibana and Elastic Search

* [elastic kibana and docker-compose](https://codingfundas.com/how-to-install-elasticsearch-7-with-kibana-using-docker-compose/index.html)
* [quick start](https://www.devopsroles.com/quick-start-install-elasticsearch-and-kibana-with-docker/)
* [docker logs elastic and kibana](https://www.sarulabs.com/post/5/2019-08-12/sending-docker-logs-to-elasticsearch-and-kibana-with-filebeat.html)
