# Gestionar datos sensibles con secretos de Docker


## Acerca de los secretos

En el contexto de los servicios de Docker Swarm, un *secreto* es un bloque de datos, como una contraseña, una clave privada SSH, un certificado SSL u otra información que no debe transmitirse por una red ni almacenarse sin cifrar en un Dockerfile o en el código fuente de tu aplicación. Puedes utilizar los *secretos* de Docker para gestionar centralmente estos datos y transmitirlos de forma segura solo a los contenedores que necesitan acceder a ellos. Los secretos se cifran durante el tránsito y en reposo en un swarm de Docker. Un secreto determinado solo es accesible para aquellos servicios a los que se les ha concedido acceso explícito, y solo mientras se ejecutan las tareas de ese servicio.

Puedes usar secretos para gestionar cualquier dato sensible que un contenedor necesite en tiempo de ejecución pero que no quieras almacenar en la imagen ni en el control de código fuente, como:

- Nombres de usuario y contraseñas
- Certificados y claves TLS
- Claves SSH
- Otros datos importantes como el nombre de una base de datos o de un servidor interno
- Cadenas genéricas o contenido binario (de hasta 500 KB de tamaño)

> [!NOTE]
> 
> Los secretos de Docker solo están disponibles para servicios de swarm, no para contenedores independientes. Para usar esta característica, considera adaptar tu contenedor para que se ejecute como un servicio. Los contenedores con estado normalmente pueden ejecutarse con una escala de 1 sin necesidad de modificar el código del contenedor.

Otro caso de uso para utilizar secretos es proporcionar una capa de abstracción entre el contenedor y un conjunto de credenciales. Considera un escenario donde tienes entornos de desarrollo, prueba y producción separados para tu aplicación. Cada uno de estos entornos puede tener diferentes credenciales, almacenadas en los swarms de desarrollo, prueba y producción con el mismo nombre de secreto. Tus contenedores solo necesitan saber el nombre del secreto para funcionar en los tres entornos.

También puedes usar secretos para gestionar datos no confidenciales, como archivos de configuración. Sin embargo, Docker admite el uso de [configuraciones (configs)](/engine/swarm/secrets/configs/) para almacenar datos no confidenciales. Las configuraciones se montan directamente en el sistema de archivos del contenedor, sin utilizar un disco RAM.

### Soporte en Windows

Docker incluye soporte para secretos en contenedores de Windows. En los ejemplos a continuación se detallan las diferencias en las implementaciones. Ten en cuenta las siguientes diferencias importantes:

- Microsoft Windows no tiene un controlador integrado para gestionar discos RAM, por lo que dentro de los contenedores de Windows en ejecución, los secretos se persisten en texto claro en el disco raíz del contenedor. Sin embargo, los secretos se eliminan explícitamente cuando el contenedor se detiene. Además, Windows no admite la persistencia de un contenedor en ejecución como una imagen utilizando `docker commit` o comandos similares.

- En Windows, se recomienda habilitar [BitLocker](https://technet.microsoft.com/en-us/library/cc732774(v=ws.11).aspx) en el volumen que contiene el directorio raíz de Docker en la máquina host para garantizar que los secretos de los contenedores en ejecución estén cifrados en reposo.

- Los archivos de secretos con destinos personalizados no se montan directamente en contenedores de Windows, ya que Windows no admite montajes de archivos individuales que no sean directorios. En su lugar, todos los secretos de un contenedor se montan en `C:\ProgramData\Docker\internal\secrets` (un detalle de implementación en el que las aplicaciones no deberían confiar) dentro del contenedor. Se utilizan enlaces simbólicos para apuntar desde allí al destino deseado del secreto dentro del contenedor. El destino predeterminado es `C:\ProgramData\Docker\secrets`.

- Al crear un servicio que utiliza contenedores de Windows, no se admiten las opciones para especificar UID, GID y modo para los secretos. Los secretos solo son accesibles por administradores y usuarios con acceso `system` dentro del contenedor.

## Cómo gestiona Docker los secretos

Cuando añades un secreto al swarm, Docker lo envía al administrador del swarm a través de una conexión TLS mutua. El secreto se almacena en el registro de Raft, que está cifrado. Todo el registro de Raft se replica en los demás administradores, lo que garantiza para los secretos la misma alta disponibilidad que para el resto de los datos de gestión del swarm.

Cuando concedes a un servicio recién creado o en ejecución acceso a un secreto, el secreto descifrado se monta en el contenedor en un sistema de archivos en memoria. La ubicación predeterminada del punto de montaje dentro del contenedor es `/run/secrets/<secret_name>` en contenedores de Linux, o `C:\ProgramData\Docker\secrets` en contenedores de Windows. También puedes especificar una ubicación personalizada.

Puedes actualizar un servicio para concederle acceso a secretos adicionales o revocar su acceso a un secreto determinado en cualquier momento.

Un nodo solo tiene acceso a los secretos (cifrados) si es un administrador de swarm o si está ejecutando tareas de servicio a las que se les ha concedido acceso al secreto. Cuando una tarea de contenedor deja de ejecutarse, los secretos descifrados compartidos con ella se desmontan del sistema de archivos en memoria de ese contenedor y se eliminan de la memoria del nodo.

Si un nodo pierde la conectividad con el swarm mientras está ejecutando un contenedor de tareas con acceso a un secreto, el contenedor de tareas seguirá teniendo acceso a sus secretos, pero no podrá recibir actualizaciones hasta que el nodo se vuelva a conectar al swarm.

Puedes añadir o inspeccionar un secreto individual en cualquier momento, o listar todos los secretos. No puedes eliminar un secreto que un servicio en ejecución esté utilizando. Consulta [Rotar un secreto](/engine/swarm/secrets/secrets/#ejemplo-rotar-un-secreto) para conocer una forma de eliminar un secreto sin interrumpir los servicios en ejecución.

Para actualizar o revertir secretos con mayor comodidad, considera añadir un número de versión o una fecha al nombre del secreto. Esto se ve facilitado por la posibilidad de controlar el punto de montaje del secreto dentro de un contenedor determinado.

## Más información sobre los comandos `docker secret`

Utiliza estos enlaces para leer sobre comandos específicos o continúa con el [ejemplo sobre el uso de secretos con un servicio](/engine/swarm/secrets/secrets/#ejemplo-simple-primeros-pasos-con-los-secretos).

- [`docker secret create`](/reference/cli/docker/secret/create/)
- [`docker secret inspect`](/reference/cli/docker/secret/inspect/)
- [`docker secret ls`](/reference/cli/docker/secret/ls/)
- [`docker secret rm`](/reference/cli/docker/secret/rm/)
- Opción [`--secret`](/reference/cli/docker/service/create/#secret) para `docker service create`
- Opciones [`--secret-add` y `--secret-rm`](/reference/cli/docker/service/update/#secret-add) para `docker service update`

## Ejemplos

Esta sección incluye tres ejemplos progresivos que ilustran cómo utilizar los secretos de Docker. Las imágenes utilizadas en estos ejemplos han sido actualizadas para facilitar el uso de los secretos de Docker. Para saber cómo modificar tus propias imágenes de manera similar, consulta [Integrar el soporte de secretos de Docker en tus imágenes](#integrar-el-soporte-de-secretos-de-docker-en-tus-imágenes).

> [!NOTE]
> 
> Estos ejemplos utilizan un swarm de un solo motor y servicios no escalados para mayor simplicidad. Los ejemplos utilizan contenedores de Linux, pero los contenedores de Windows también admiten secretos. Consulta el [soporte en Windows](#soporte-en-windows).

### Definir y usar secretos en archivos compose

Tanto el comando `docker-compose` como `docker stack` admiten la definición de secretos en un archivo Compose. Consulta la [referencia del archivo Compose](/reference/compose-file/legacy-versions/) para más detalles.

### Ejemplo simple: Primeros pasos con los secretos

Este ejemplo simple muestra cómo funcionan los secretos en solo unos pocos comandos. Para un ejemplo del mundo real, continúa en [Ejemplo intermedio: Usar secretos con un servicio Nginx](#ejemplo-intermedio-usar-secretos-con-un-servicio-nginx).

1.  Añade un secreto a Docker. El comando `docker secret create` lee la entrada estándar porque el último argumento, que representa el archivo del cual leer el secreto, está establecido en `-`.

    ```console
    $ printf "This is a secret" | docker secret create my_secret_data -
    ```

2.  Crea un servicio `redis` y concédele acceso al secreto. Por defecto, el contenedor puede acceder al secreto en `/run/secrets/<secret_name>`, pero puedes personalizar el nombre del archivo en el contenedor utilizando la opción `target`.

    ```console
    $ docker service  create --name redis --secret my_secret_data redis:alpine
    ```

3.  Verifica que la tarea se esté ejecutando sin problemas utilizando `docker service ps`. Si todo está funcionando, la salida se verá similar a esto:

    ```console
    $ docker service ps redis

    ID            NAME     IMAGE         NODE              DESIRED STATE  CURRENT STATE          ERROR  PORTS
    bkna6bpn8r1a  redis.1  redis:alpine  ip-172-31-46-109  Running        Running 8 seconds ago  
    ```

    Si hubiera un error y la tarea fallara y se reiniciara repetidamente, verías algo como esto:

    ```console
    $ docker service ps redis

    NAME                      IMAGE         NODE  DESIRED STATE  CURRENT STATE          ERROR                      PORTS
    redis.1.siftice35gla      redis:alpine  moby  Running        Running 4 seconds ago                             
     \_ redis.1.whum5b7gu13e  redis:alpine  moby  Shutdown       Failed 20 seconds ago      "task: non-zero exit (1)"  
     \_ redis.1.2s6yorvd9zow  redis:alpine  moby  Shutdown       Failed 56 seconds ago      "task: non-zero exit (1)"  
     \_ redis.1.ulfzrcyaf6pg  redis:alpine  moby  Shutdown       Failed about a minute ago  "task: non-zero exit (1)"  
     \_ redis.1.wrny5v4xyps6  redis:alpine  moby  Shutdown       Failed 2 minutes ago       "task: non-zero exit (1)"
    ```

4.  Obtén el ID del contenedor de la tarea del servicio `redis` utilizando `docker ps`, de modo que puedas usar `docker container exec` para conectarte al contenedor y leer el contenido del archivo de datos del secreto. Este archivo, por defecto, es legible por todos y tiene el mismo nombre que el secreto. El primer comando a continuación ilustra cómo encontrar el ID del contenedor, y el segundo y tercer comando utilizan la autocompletación de la terminal para hacerlo automáticamente.

    ```console
    $ docker ps --filter name=redis -q

    5cb1c2348a59

    $ docker container exec $(docker ps --filter name=redis -q) ls -l /run/secrets

    total 4
    -r--r--r--    1 root     root            17 Dec 13 22:48 my_secret_data

    $ docker container exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data

    This is a secret
    ```

5.  Verifica que el secreto no esté disponible si confirmas (commit) el contenedor.

    ```console
    $ docker commit $(docker ps --filter name=redis -q) committed_redis

    $ docker run --rm -it committed_redis cat /run/secrets/my_secret_data

    cat: can't open '/run/secrets/my_secret_data': No such file or directory
    ```

6.  Intenta eliminar el secreto. La eliminación fallará porque el servicio `redis` está en ejecución y tiene acceso al secreto.

    ```console
    $ docker secret ls

    ID                          NAME                CREATED             UPDATED
    wwwrxza8sxy025bas86593fqs   my_secret_data      4 hours ago         4 hours ago


    $ docker secret rm my_secret_data

    Error response from daemon: rpc error: code = 3 desc = secret
    'my_secret_data' is in use by the following service: redis
    ```

7.  Quita el acceso al secreto del servicio `redis` en ejecución actualizando el servicio.

    ```console
    $ docker service update --secret-rm my_secret_data redis
    ```

8.  Repite los pasos 3 y 4 de nuevo, verificando que el servicio ya no tenga acceso al secreto. El ID del contenedor es diferente porque el comando `service update` vuelve a desplegar el servicio.

    ```console
    $ docker container exec -it $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data

    cat: can't open '/run/secrets/my_secret_data': No such file or directory
    ```

9.  Detén e elimina el servicio, y elimina el secreto de Docker.

    ```console
    $ docker service rm redis

    $ docker secret rm my_secret_data
    ```

### Ejemplo simple: Usar secretos en un servicio de Windows

Este es un ejemplo muy simple que muestra cómo usar secretos con un servicio de Microsoft IIS que se ejecuta en Docker para Windows con contenedores de Windows en Microsoft Windows 10. Es un ejemplo sencillo que almacena la página web en un secreto.

Este ejemplo asume que tienes PowerShell instalado.

1.  Guarda el siguiente contenido en un nuevo archivo `index.html`.

    ```html
    <html lang="en">
      <head><title>Hello Docker</title></head>
      <body>
        <p>Hello Docker! You have deployed a HTML page.</p>
      </body>
    </html>
    ```

2.  Si aún no lo has hecho, inicializa o únete al swarm.

    ```console
    > docker swarm init
    ```

3.  Guarda el archivo `index.html` como un secreto de swarm llamado `homepage`.

    ```console
    > docker secret create homepage index.html
    ```

4.  Crea un servicio IIS y concédele acceso al secreto `homepage`.

    ```console
    > docker service create `
        --name my-iis `
        --publish published=8000,target=8000 `
        --secret src=homepage,target="\inetpub\wwwroot\index.html" `
        microsoft/iis:nanoserver
    ```

    > [!NOTE]
    > 
    > Técnicamente no hay razón para utilizar secretos en este ejemplo; las [configuraciones (configs)](/engine/swarm/secrets/configs/) son una mejor opción. Este ejemplo es solo con fines ilustrativos.

5.  Accede al servicio IIS en `http://localhost:8000/`. Debería servir el contenido HTML del primer paso.

6.  Elimina el servicio y el secreto.

    ```console
    > docker service rm my-iis
    > docker secret rm homepage
    > docker image remove secret-test
    ```

### Ejemplo intermedio: Usar secretos con un servicio Nginx

Este ejemplo está dividido en dos partes. La [primera parte](#generar-el-certificado-del-sitio) trata sobre la generación del certificado del sitio y no involucra directamente los secretos de Docker, pero prepara la [segunda parte](#configurar-el-contenedor-nginx-1), donde se almacena y utiliza el certificado del sitio y la configuración de Nginx como secretos.

#### Generar el certificado del sitio

Genera una CA raíz y un certificado y clave TLS para tu sitio. Para sitios de producción, es recomendable usar un servicio como `Let’s Encrypt` para generar el certificado y la clave TLS, pero este ejemplo utiliza herramientas de línea de comandos. Este paso es un poco complicado, pero es solo un paso de configuración para que tengas algo que almacenar como secreto de Docker. Si deseas omitir estos subpasos, puedes [usar Let's Encrypt](https://letsencrypt.org/getting-started/) para generar la clave y el certificado del sitio, nombrar los archivos `site.key` y `site.crt`, y saltar a [Configurar el contenedor Nginx](#configurar-el-contenedor-nginx-1).

1.  Genera una clave raíz.

    ```console
    $ openssl genrsa -out "root-ca.key" 4096
    ```

2.  Genera un CSR utilizando la clave raíz.

    ```console
    $ openssl req \
              -new -key "root-ca.key" \
              -out "root-ca.csr" -sha256 \
              -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA'
    ```

3.  Configura la CA raíz. Edita un nuevo archivo llamado `root-ca.cnf` y pega el siguiente contenido en él. Esto restringe la CA raíz para que solo firme certificados de hoja y no CAs intermedias.

    ```ini
    [root_ca]
    basicConstraints = critical,CA:TRUE,pathlen:1
    keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
    subjectKeyIdentifier=hash
    ```

4.  Firma el certificado.

    ```console
    $ openssl x509 -req  -days 3650  -in "root-ca.csr" \
                   -signkey "root-ca.key" -sha256 -out "root-ca.crt" \
                   -extfile "root-ca.cnf" -extensions \
                   root_ca
    ```

5.  Genera la clave del sitio.

    ```console
    $ openssl genrsa -out "site.key" 4096
    ```

6.  Genera el certificado del sitio y fírmalo con la clave del sitio.

    ```console
    $ openssl req -new -key "site.key" -out "site.csr" -sha256 \
              -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
    ```

7.  Configura el certificado del sitio. Edita un nuevo archivo llamado `site.cnf` y pega el siguiente contenido en él. Esto limita el certificado del sitio para que solo se pueda utilizar para autenticar un servidor y no para firmar otros certificados.

    ```ini
    [server]
    authorityKeyIdentifier=keyid,issuer
    basicConstraints = critical,CA:FALSE
    extendedKeyUsage=serverAuth
    keyUsage = critical, digitalSignature, keyEncipherment
    subjectAltName = DNS:localhost, IP:127.0.0.1
    subjectKeyIdentifier=hash
    ```

8.  Firma el certificado del sitio.

    ```console
    $ openssl x509 -req -days 750 -in "site.csr" -sha256 \
        -CA "root-ca.crt" -CAkey "root-ca.key"  -CAcreateserial \
        -out "site.crt" -extfile "site.cnf" -extensions server
    ```

9.  El servicio Nginx no necesita los archivos `site.csr` y `site.cnf`, pero los necesitarás si deseas generar un nuevo certificado para el sitio. Protege el archivo `root-ca.key`.

#### Configurar el contenedor Nginx

1.  Produce una configuración de Nginx muy básica que sirva archivos estáticos a través de HTTPS. El certificado y la clave TLS se almacenan como secretos de Docker para que se puedan rotar sin complicaciones.

    En el directorio actual, crea un nuevo archivo llamado `site.conf` con el siguiente contenido:

    ```nginx
    server {
        listen                443 ssl;
        server_name           localhost;
        ssl_certificate       /run/secrets/site.crt;
        ssl_certificate_key   /run/secrets/site.key;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
    }
    ```

2.  Crea tres secretos que representen la clave, el certificado y el archivo `site.conf`. Puedes almacenar cualquier archivo como secreto siempre que sea inferior a 500 KB. Esto te permite desacoplar la clave, el certificado y la configuración de los servicios que los utilizan. En cada uno de estos comandos, el último argumento representa la ruta al archivo del cual leer el secreto en el sistema de archivos de la máquina host. En estos ejemplos, el nombre del secreto y el del archivo son iguales.

    ```console
    $ docker secret create site.key site.key

    $ docker secret create site.crt site.crt

    $ docker secret create site.conf site.conf
    ```

    ```console
    $ docker secret ls

    ID                          NAME                  CREATED             UPDATED
    2hvoi9mnnaof7olr3z5g3g7fp   site.key       58 seconds ago      58 seconds ago
    aya1dh363719pkiuoldpter4b   site.crt       24 seconds ago      24 seconds ago
    zoa5df26f7vpcoz42qf2csth8   site.conf      11 seconds ago      11 seconds ago
    ```

3.  Crea un servicio que ejecute Nginx y que tenga acceso a los tres secretos. El comando `docker service create` permite crear un enlace simbólico desde la ubicación del secreto `site.conf` a `/etc/nginx.conf.d/`, donde Nginx busca archivos de configuración adicionales. Este paso ocurre antes de que Nginx se inicie realmente, por lo que no es necesario volver a construir tu imagen si cambias la configuración de Nginx.

    > [!NOTE]
    > 
    > Normalmente crearías un Dockerfile que copie el archivo `site.conf` en su lugar, construirías la imagen y ejecutarías un contenedor utilizando tu imagen personalizada. Este ejemplo no requiere una imagen personalizada. Coloca el `site.conf` en su lugar y ejecuta el contenedor en un solo paso.

    Los secretos se encuentran dentro del directorio `/run/secrets/` en el contenedor por defecto, lo que puede requerir pasos adicionales en el contenedor para que el secreto esté disponible en una ruta diferente. El ejemplo a continuación crea un enlace simbólico a la ubicación real del archivo `site.conf` para que Nginx pueda leerlo:

    ```console
    $ docker service create \
         --name nginx \
         --secret site.key \
         --secret site.crt \
         --secret site.conf \
         --publish published=3000,target=443 \
         nginx:latest \
         sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'"
    ```

    En el caso de no querer crear enlaces simbólicos, los secretos te permiten especificar una ubicación personalizada utilizando la opción `target`. El ejemplo a continuación ilustra cómo el secreto `site.conf` se pone a disposición en `/etc/nginx/conf.d/site.conf` dentro del contenedor sin el uso de enlaces simbólicos:

    ```console
    $ docker service create \
         --name nginx \
         --secret site.key \
         --secret site.crt \
         --secret source=site.conf,target=/etc/nginx/conf.d/site.conf \
         --publish published=3000,target=443 \
         nginx:latest \
         sh -c "exec nginx -g 'daemon off;'"
    ```

    Los secretos `site.key` y `site.crt` utilizan la sintaxis corta, sin una ubicación `target` personalizada establecida. La sintaxis corta monta los secretos en `/run/secrets/` con el mismo nombre que el secreto. Dentro de los contenedores en ejecución, ahora existen los siguientes tres archivos:

    - `/run/secrets/site.key`
    - `/run/secrets/site.crt`
    - `/etc/nginx/conf.d/site.conf`

4.  Verifica que el servicio Nginx se esté ejecutando.

    ```console
    $ docker service ls

    ID            NAME   MODE        REPLICAS  IMAGE
    zeskcec62q24  nginx  replicated  1/1       nginx:latest

    $ docker service ps nginx

    NAME                  IMAGE         NODE  DESIRED STATE  CURRENT STATE          ERROR  PORTS
    nginx.1.9ls3yo9ugcls  nginx:latest  moby  Running        Running 3 minutes ago
    ```

5.  Verifica que el servicio esté operativo: que puedas acceder al servidor Nginx y que se esté utilizando el certificado TLS correcto.

    ```console
    $ curl --cacert root-ca.crt https://localhost:3000

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
        body {
            width: 35em;
            margin: 0 auto;
            font-family: Tahoma, Verdana, Arial, sans-serif;
        }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>

    <p>For online documentation and support. refer to
    <a href="https://nginx.org">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="https://www.nginx.com">nginx.com</a>.</p>

    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    ```

    ```console
    $ openssl s_client -connect localhost:3000 -CAfile root-ca.crt

    CONNECTED(00000003)
    depth=1 /C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
    verify return:1
    depth=0 /C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
    verify return:1
    ---
    Certificate chain
     0 s:/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
       i:/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
    ---
    Server certificate
    -----BEGIN CERTIFICATE-----
    …
    -----END CERTIFICATE-----
    subject=/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
    issuer=/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
    ---
    No client certificate CA names sent
    ---
    SSL handshake has read 1663 bytes and written 712 bytes
    ---
    New, TLSv1/SSLv3, Cipher is AES256-SHA
    Server public key is 4096 bit
    Secure Renegotiation IS supported
    Compression: NONE
    Expansion: NONE
    SSL-Session:
        Protocol  : TLSv1
        Cipher    : AES256-SHA
        Session-ID: A1A8BF35549C5715648A12FD7B7E3D861539316B03440187D9DA6C2E48822853
        Session-ID-ctx:
        Master-Key: F39D1B12274BA16D3A906F390A61438221E381952E9E1E05D3DD784F0135FB81353DA38C6D5C021CB926E844DFC49FC4
        Key-Arg   : None
        Start Time: 1481685096
        Timeout   : 300 (sec)
        Verify return code: 0 (ok)
    ```

6.  Para limpiar después de ejecutar este ejemplo, elimina el servicio `nginx` y los secretos almacenados.

    ```console
    $ docker service rm nginx

    $ docker secret rm site.crt site.key site.conf
    ```

### Ejemplo avanzado: Usar secretos con un servicio WordPress

En este ejemplo, crearás un servicio MySQL de un solo nodo con una contraseña de root personalizada, añadirás las credenciales como secretos y crearás un servicio WordPress de un solo nodo que utilizará estas credenciales para conectarse a MySQL. El [siguiente ejemplo](#ejemplo-rotar-un-secreto) se basa en este y muestra cómo rotar la contraseña de MySQL y actualizar los servicios para que el servicio WordPress pueda seguir conectándose a MySQL.

Este ejemplo ilustra algunas técnicas para usar secretos de Docker a fin de evitar guardar credenciales sensibles dentro de tu imagen o pasarlas directamente en la línea de comandos.

> [!NOTE]
> 
> Este ejemplo utiliza un swarm de un solo motor por simplicidad, y utiliza un servicio MySQL de un solo nodo debido a que una única instancia del servidor MySQL no se puede escalar solo mediante un servicio replicado, y la configuración de un clúster MySQL está fuera del alcance de este ejemplo.
>
> Además, cambiar una frase de contraseña de root de MySQL no consiste en cambiar un archivo en el disco. Debes utilizar una consulta o un comando `mysqladmin` para cambiar la contraseña en MySQL.

1.  Genera una contraseña alfanumérica aleatoria para MySQL y almacénala como un secreto de Docker con el nombre `mysql_password` utilizando el comando `docker secret create`. Para hacer la contraseña más corta o más larga, ajusta el último argumento del comando `openssl`. Esta es solo una forma de crear una contraseña relativamente aleatoria. Puedes utilizar otro comando para generar la contraseña si lo prefieres.

    > [!NOTE]
    > 
    > Después de crear un secreto, no puedes actualizarlo. Solo puedes eliminarlo y volver a crearlo, y no puedes eliminar un secreto que un servicio esté utilizando. Sin embargo, puedes conceder o revocar el acceso de un servicio en ejecución a los secretos utilizando `docker service update`. Si necesitas la capacidad de actualizar un secreto, considera añadir un componente de versión al nombre del secreto, de modo que puedas añadir una nueva versión más adelante, actualizar el servicio para que la use y luego eliminar la versión anterior.

    El último argumento se establece en `-`, lo que indica que la entrada se lee desde la entrada estándar.

    ```console
    $ openssl rand -base64 20 | docker secret create mysql_password -

    l1vinzevzhj4goakjap5ya409
    ```

    El valor devuelto no es la contraseña, sino el ID del secreto. En el resto de este tutorial se omite la salida del ID.

    Genera un segundo secreto para el usuario `root` de MySQL. Este secreto no se comparte con el servicio WordPress creado posteriormente. Solo se necesita para inicializar el servicio `mysql`.

    ```console
    $ openssl rand -base64 20 | docker secret create mysql_root_password -
    ```

    Lista los secretos gestionados por Docker utilizando `docker secret ls`:

    ```console
    $ docker secret ls

    ID                          NAME                  CREATED             UPDATED
    l1vinzevzhj4goakjap5ya409   mysql_password        41 seconds ago      41 seconds ago
    yvsczlx9votfw3l0nz5rlidig   mysql_root_password   12 seconds ago      12 seconds ago
    ```

    Los secretos se almacenan en los registros de Raft cifrados para el swarm.

2.  Crea una red superpuesta definida por el usuario que se utilizará para la comunicación entre los servicios MySQL y WordPress. No hay necesidad de exponer el servicio MySQL a ningún host o contenedor externo.

    ```console
    $ docker network create -d overlay mysql_private
    ```

3.  Crea el servicio MySQL. El servicio MySQL tiene las siguientes características:

    - Dado que la escala está establecida en `1`, solo se ejecuta una única tarea de MySQL. El balanceo de carga de MySQL se deja como ejercicio para el lector e implica más que solo escalar el servicio.
    - Solo es accesible por otros contenedores en la red `mysql_private`.
    - Utiliza el volumen `mydata` para almacenar los datos de MySQL, de modo que persistan a través de los reinicios del servicio `mysql`.
    - Cada uno de los secretos se monta en un sistema de archivos `tmpfs` en `/run/secrets/mysql_password` y `/run/secrets/mysql_root_password`. Nunca se exponen como variables de entorno, ni se pueden confirmar en una imagen si se ejecuta el comando `docker commit`. El secreto `mysql_password` es el utilizado por el contenedor WordPress no privilegiado para conectarse a MySQL.
    - Establece las variables de entorno `MYSQL_ROOT_PASSWORD_FILE` y `MYSQL_PASSWORD_FILE` para apuntar a los archivos `/run/secrets/mysql_root_password` y `/run/secrets/mysql_password`. La imagen de `mysql` lee las cadenas de contraseña de esos archivos al inicializar la base de datos del sistema por primera vez. Después, las contraseñas se almacenan en la propia base de datos del sistema MySQL.
    - Establece las variables de entorno `MYSQL_USER` and `MYSQL_DATABASE`. Se crea una nueva base de datos llamada `wordpress` cuando se inicia el contenedor, y el usuario `wordpress` tiene permisos completos solo para esta base de datos. Este usuario no puede crear ni eliminar bases de datos ni cambiar la configuración de MySQL.

      ```console
      $ docker service create \
           --name mysql \
           --replicas 1 \
           --network mysql_private \
           --mount type=volume,source=mydata,destination=/var/lib/mysql \
           --secret source=mysql_root_password,target=mysql_root_password \
           --secret source=mysql_password,target=mysql_password \
           -e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
           -e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \
           -e MYSQL_USER="wordpress" \
           -e MYSQL_DATABASE="wordpress" \
           mysql:latest
      ```

4.  Verifica que el contenedor `mysql` se esté ejecutando utilizando el comando `docker service ls`.

    ```console
    $ docker service ls

    ID            NAME   MODE        REPLICAS  IMAGE
    wvnh0siktqr3  mysql  replicated  1/1       mysql:latest
    ```

5.  Ahora que MySQL está configurado, crea un servicio WordPress que se conecte al servicio MySQL. El servicio WordPress tiene las siguientes características:

    - Dado que la escala está establecida en `1`, solo se ejecuta una única tarea de WordPress. El balanceo de carga de WordPress se deja como ejercicio para el lector, debido a las limitaciones con el almacenamiento de datos de sesión de WordPress en el sistema de archivos del contenedor.
    - Expone WordPress en el puerto 30000 de la máquina host, para que puedas acceder desde hosts externos. Puedes exponer el puerto 80 en su lugar si no tienes un servidor web ejecutándose en el puerto 80 de la máquina host.
    - Se conecta a la red `mysql_private` para poder comunicarse con el contenedor `mysql`, y también publica el puerto 80 en el puerto 30000 en todos los nodos del swarm.
    - Tiene acceso al secreto `mysql_password`, pero especifica un nombre de archivo de destino diferente dentro del contenedor. El contenedor de WordPress utiliza el punto de montaje `/run/secrets/wp_db_password`.
    - Establece la variable de entorno `WORDPRESS_DB_PASSWORD_FILE` en la ruta de archivo donde está montado el secreto. El servicio WordPress lee la cadena de la contraseña de MySQL de ese archivo y la añade al archivo de configuración `wp-config.php`.
    - Se conecta al contenedor MySQL utilizando el nombre de usuario `wordpress` y la contraseña en `/run/secrets/wp_db_password` y crea la base de datos `wordpress` si aún no existe.
    - Almacena sus datos, como temas y plugins, en un volumen llamado `wpdata` para que estos archivos persistan cuando el servicio se reinicie.

    ```console
    $ docker service create \
         --name wordpress \
         --replicas 1 \
         --network mysql_private \
         --publish published=30000,target=80 \
         --mount type=volume,source=wpdata,destination=/var/www/html \
         --secret source=mysql_password,target=wp_db_password \
         -e WORDPRESS_DB_USER="wordpress" \
         -e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \
         -e WORDPRESS_DB_HOST="mysql:3306" \
         -e WORDPRESS_DB_NAME="wordpress" \
         wordpress:latest
    ```

6.  Verifica que el servicio se esté ejecutando utilizando los comandos `docker service ls` y `docker service ps`.

    ```console
    $ docker service ls

    ID            NAME       MODE        REPLICAS  IMAGE
    wvnh0siktqr3  mysql      replicated  1/1       mysql:latest
    nzt5xzae4n62  wordpress  replicated  1/1       wordpress:latest
    ```

    ```console
    $ docker service ps wordpress

    ID            NAME         IMAGE             NODE  DESIRED STATE  CURRENT STATE           ERROR  PORTS
    aukx6hgs9gwc  wordpress.1  wordpress:latest  moby  Running        Running 52 seconds ago   
    ```

    En este punto, podrías revocar el acceso del servicio WordPress al secreto `mysql_password`, porque WordPress ha copiado el secreto a su archivo de configuración `wp-config.php`. No lo hagas por ahora, ya que se utilizará más adelante para facilitar la rotación de la contraseña de MySQL.

7.  Accede a `http://localhost:30000/` desde cualquier nodo del swarm y configura WordPress utilizando el asistente basado en la web. Todos estos ajustes se almacenan en la base de datos `wordpress` de MySQL. WordPress genera automáticamente una contraseña para tu usuario de WordPress, que es completamente diferente de la contraseña que utiliza WordPress para acceder a MySQL. Guarda esta contraseña de forma segura, por ejemplo, en un gestor de contraseñas. La necesitarás para iniciar sesión en WordPress después de [rotar el secreto](#ejemplo-rotar-un-secreto).

    Escribe una entrada de blog o dos e instala un plugin o tema de WordPress para verificar que WordPress esté completamente operativo y que su estado se guarde a través de los reinicios del servicio.

8.  No limpies ningún servicio ni secreto si tienes la intención de proceder al siguiente ejemplo, que demuestra cómo rotar la contraseña de root de MySQL.

### Ejemplo: Rotar un secreto

Este ejemplo se basa en el anterior. En este escenario, creas un nuevo secreto con una nueva contraseña de MySQL, actualizas los servicios `mysql` y `wordpress` para usarlo y luego eliminas el secreto anterior.

> [!NOTE]
> 
> Cambiar la contraseña en una base de datos MySQL implica ejecutar consultas o comandos adicionales, en lugar de simplemente cambiar una sola variable de entorno o un archivo, ya que la imagen solo establece la contraseña de MySQL si la base de datos aún no existe, y MySQL almacena la contraseña dentro de una base de datos de MySQL por defecto. Rotar contraseñas u otros secretos puede implicar pasos adicionales fuera de Docker.

1.  Crea la nueva contraseña y almacénala como un secreto llamado `mysql_password_v2`.

    ```console
    $ openssl rand -base64 20 | docker secret create mysql_password_v2 -
    ```

2.  Actualiza el servicio MySQL para darle acceso tanto al secreto antiguo como al nuevo. Recuerda que no puedes actualizar ni cambiar el nombre de un secreto, pero puedes revocar un secreto y conceder acceso a él utilizando un nuevo nombre de archivo de destino.

    ```console
    $ docker service update \
         --secret-rm mysql_password mysql

    $ docker service update \
         --secret-add source=mysql_password,target=old_mysql_password \
         --secret-add source=mysql_password_v2,target=mysql_password \
         mysql
    ```

    Actualizar un servicio provoca su reinicio, y cuando el servicio MySQL se reinicia por segunda vez, tiene acceso al secreto antiguo en `/run/secrets/old_mysql_password` y al nuevo secreto en `/run/secrets/mysql_password`.

    Aunque el servicio MySQL ahora tiene acceso tanto al secreto antiguo como al nuevo, la contraseña de MySQL para el usuario de WordPress aún no se ha cambiado.

    > [!NOTE]
    > 
    > Este ejemplo no rota la contraseña `root` de MySQL.

3.  Ahora, cambia la contraseña de MySQL para el usuario `wordpress` utilizando la CLI `mysqladmin`. Este comando lee la contraseña antigua y la nueva de los archivos en `/run/secrets` pero no las expone en la línea de comandos ni las guarda en el historial de la terminal.

    Haz esto rápidamente y pasa al siguiente paso, porque WordPress perderá la capacidad de conectarse a MySQL.

    Primero, encuentra el ID de la tarea del contenedor `mysql`.

    ```console
    $ docker ps --filter name=mysql -q

    c7705cf6176f
    ```

    Sustituye el ID en el comando a continuación, o utiliza la segunda variante que utiliza la expansión de la terminal para hacerlo todo en un solo paso.

    ```console
    $ docker container exec <CONTAINER_ID> \
        bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'
    ```

    O:

    ```console
    $ docker container exec $(docker ps --filter name=mysql -q) \
        bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'
    ```

4.  Actualiza el servicio `wordpress` para usar la nueva contraseña, manteniendo la ruta de destino en `/run/secrets/wp_db_password`. Esto desencadena un reinicio continuo (rolling restart) del servicio WordPress y se utiliza el nuevo secreto.

    ```console
    $ docker service update \
         --secret-rm mysql_password \
         --secret-add source=mysql_password_v2,target=wp_db_password \
         wordpress    
    ```

5.  Verifica que WordPress funcione navegando a http://localhost:30000/ en cualquier nodo del swarm nuevamente. Utiliza el nombre de usuario y la contraseña de WordPress de cuando realizaste el asistente de WordPress en la tarea anterior.

    Verifica que la entrada de blog que escribiste todavía exista y, si cambiaste algún valor de configuración, verifica que sigan modificados.

6.  Revoca el acceso al secreto antiguo del servicio MySQL y elimina el secreto antiguo de Docker.

    ```console
    $ docker service update \
         --secret-rm mysql_password \
         mysql

    $ docker secret rm mysql_password
    ```

7.  Ejecuta los siguientes comandos para eliminar el servicio WordPress, el contenedor MySQL, los volúmenes `mydata` y `wpdata`, y los secretos de Docker:

    ```console
    $ docker service rm wordpress mysql

    $ docker volume rm mydata wpdata

    $ docker secret rm mysql_password_v2 mysql_root_password
    ```

## Integrar el soporte de secretos de Docker en tus imágenes

Si desarrollas un contenedor que se puede desplegar como un servicio y requiere datos sensibles, como una credencial, como una variable de entorno, considera adaptar tu imagen para aprovechar los secretos de Docker. Una forma de hacerlo es asegurarte de que cada parámetro que pases a la imagen al crear el contenedor también se pueda leer desde un archivo.

Muchas de las imágenes oficiales de Docker en la biblioteca de Docker, como la imagen de WordPress utilizada en los ejemplos anteriores, han sido actualizadas de esta manera.

Al iniciar un contenedor de WordPress, le proporcionas los parámetros que necesita estableciéndolos como variables de entorno. La imagen de WordPress ha sido actualizada para que las variables de entorno que contienen datos importantes para WordPress, como `WORDPRESS_DB_PASSWORD`, también tengan variantes que puedan leer sus valores desde un archivo (`WORDPRESS_DB_PASSWORD_FILE`). Esta estrategia garantiza que se preserve la compatibilidad con versiones anteriores, al mismo tiempo que permite que tu contenedor lea la información de un secreto gestionado por Docker en lugar de ser pasada directamente.

> [!NOTE]
> 
> Los secretos de Docker no establecen variables de entorno directamente. Esta fue una decisión consciente, porque las variables de entorno se pueden filtrar involuntariamente entre contenedores (por ejemplo, si utilizas `--link`).

## Usar secretos en Compose

```yaml
services:
   db:
     image: mysql:latest
     volumes:
       - db_data:/var/lib/mysql
     environment:
       MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD_FILE: /run/secrets/db_password
     secrets:
       - db_root_password
       - db_password

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
     secrets:
       - db_password


secrets:
   db_password:
     file: db_password.txt
   db_root_password:
     file: db_root_password.txt

volumes:
    db_data:
```

Este ejemplo crea un sitio simple de WordPress utilizando dos secretos en un archivo Compose.

El elemento de nivel superior `secrets` define dos secretos: `db_password` y `db_root_password`.

Al desplegar, Docker crea estos dos secretos y los llena con el contenido del archivo especificado en el archivo Compose.

El servicio `db` utiliza ambos secretos y `wordpress` utiliza uno.

Al realizar el despliegue, Docker monta un archivo en `/run/secrets/<secret_name>` en los servicios. Estos archivos nunca se persisten en disco, sino que se gestionan en memoria.

Cada servicio utiliza variables de entorno para especificar dónde debe buscar el servicio esos datos del secreto.

Se puede encontrar más información sobre la sintaxis corta y larga para secretos en la [Especificación de Compose](/reference/compose-file/secrets/).

