--- title: "Cómo gestionar secretos en Docker Compose y Docker Swarm" description: "Guarda contraseñas y claves fuera de las variables de entorno con secretos de Docker Compose y Swarm, rótalos sin reconstruir imágenes y úsalos en el build." category: "Docker Avanzado" language: es source: https://cubepath.com/es/docs/docker-avanzado/gestion-de-secretos-de-docker updated: 2026-09-25 --- # Cómo gestionar secretos en Docker Compose y Docker Swarm Pasar contraseñas a un contenedor con variables de entorno es cómodo, pero esas variables aparecen en `docker inspect`, se heredan en los procesos hijos y acaban a menudo en logs o volcados de error. Los secretos de Docker entregan cada credencial como un archivo dentro del contenedor, en `/run/secrets/`, que solo ven los servicios autorizados. En este tutorial usarás secretos con Docker Compose en un único servidor, después con Docker Swarm (donde se cifran y distribuyen entre nodos), aprenderás a rotarlos y a pasar credenciales durante la construcción de una imagen sin que queden en ella. Todo en Ubuntu 24.04. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker. - Tu usuario en el grupo `docker`, o anteponer `sudo` a los comandos `docker`. ## Paso 1: Ver el problema de las variables de entorno Arranca un contenedor de PostgreSQL pasando la contraseña como variable de entorno: ```bash docker run -d --name pg-env -e POSTGRES_PASSWORD=supersecreto postgres:16 ``` Cualquiera con acceso al socket de Docker puede leerla sin entrar en el contenedor: ```bash docker inspect pg-env --format '{{json .Config.Env}}' ``` ```output ["POSTGRES_PASSWORD=supersecreto","PATH=/usr/local/sbin:...","PG_MAJOR=16",...] ``` Elimina el contenedor de prueba: ```bash docker rm -f pg-env ``` Con secretos, la variable contiene solo la ruta a un archivo y el valor no aparece en la configuración del contenedor. Las imágenes oficiales de PostgreSQL, MySQL, MariaDB y otras admiten variables con sufijo `_FILE` (por ejemplo `POSTGRES_PASSWORD_FILE`) precisamente para leer la credencial desde un archivo. ## Paso 2: Usar secretos con Docker Compose Docker Compose admite secretos sin necesidad de Swarm. En este modo el secreto es un archivo del servidor que Compose monta en `/run/secrets/` dentro del contenedor. No se cifra, pero sale de las variables de entorno y del archivo `compose.yaml`, que así puedes versionar sin credenciales. Crea el directorio del proyecto y un subdirectorio para los secretos accesible solo por tu usuario: ```bash mkdir -p ~/secrets-demo/secrets && cd ~/secrets-demo chmod 700 secrets ``` Genera una contraseña aleatoria y guárdala sin salto de línea final: ```bash openssl rand -base64 32 | tr -d '\n' > secrets/db_password.txt chmod 644 secrets/db_password.txt ``` El archivo tiene permisos `644` porque se monta tal cual en el contenedor y el proceso de PostgreSQL, que se ejecuta con el usuario `postgres`, tiene que poder leerlo. Lo que lo protege en el servidor es el directorio `secrets/` con permisos `700`. Evita que el directorio acabe en Git: ```bash echo "secrets/" >> .gitignore ``` Crea el archivo de Compose: ```bash nano compose.yaml ``` ```yaml services: db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_DB: app POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password volumes: - db_data:/var/lib/postgresql/data secrets: db_password: file: ./secrets/db_password.txt volumes: db_data: ``` La sección `secrets` de nivel superior declara de dónde sale cada secreto y la clave `secrets` del servicio indica qué servicios pueden usarlo. Un servicio que no lo declare no verá el archivo. Levanta el servicio: ```bash docker compose up -d ``` Comprueba que el secreto está montado y que la contraseña ya no aparece en el entorno: ```bash docker compose exec db ls -l /run/secrets/ docker compose exec db env | grep POSTGRES_PASSWORD ``` ```output total 4 -rw-r--r-- 1 1000 1000 44 Sep 25 10:20 db_password POSTGRES_PASSWORD_FILE=/run/secrets/db_password ``` Verifica que la contraseña funciona conectándote por TCP con el valor del archivo: ```bash docker compose exec -e PGPASSWORD="$(cat secrets/db_password.txt)" db psql -h 127.0.0.1 -U app -d app -c 'SELECT 1;' ``` ```output ?column? ---------- 1 (1 row) ``` Detén la pila antes de pasar a Swarm: ```bash docker compose down ``` ## Paso 3: Inicializar Docker Swarm y crear un secreto En Docker Swarm los secretos se guardan cifrados en el registro Raft de los nodos manager, viajan por TLS solo a los nodos que ejecutan un servicio autorizado y se montan en memoria (`tmpfs`) dentro del contenedor. Nunca se escriben en claro en el disco de los workers. Inicializa un Swarm de un solo nodo. Si el servidor tiene varias IP, indica la privada con `--advertise-addr`: ```bash docker swarm init --advertise-addr your_server_ip ``` ```output Swarm initialized: current node (k2x9...) is now a manager. ``` Crea el secreto leyendo el valor desde la entrada estándar (el `-` final). Así no queda en el historial de la shell ni en ningún archivo: ```bash openssl rand -base64 32 | tr -d '\n' | docker secret create db_password - ``` Lista los secretos. Docker muestra sus metadatos, pero nunca su contenido: ```bash docker secret ls ``` ```output ID NAME DRIVER CREATED UPDATED p3j8t0x4wz1m6k2c9v7b5n0qa db_password 10 seconds ago 10 seconds ago ``` `docker secret inspect db_password` tampoco muestra el valor. Una vez creado, un secreto no se puede leer desde la CLI ni modificar: solo se puede usar o eliminar. ## Paso 4: Desplegar una pila que usa el secreto Con Swarm despliegas pilas con `docker stack deploy`, usando el mismo formato de Compose. Crea un archivo nuevo para la pila: ```bash nano stack.yaml ``` ```yaml services: db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_DB: app POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password volumes: - db_data:/var/lib/postgresql/data deploy: replicas: 1 secrets: db_password: external: true volumes: db_data: ``` `external: true` indica que el secreto ya existe en el Swarm y no debe crearse a partir de un archivo. Despliega la pila: ```bash docker stack deploy -c stack.yaml app ``` Comprueba que el servicio está en marcha: ```bash docker service ls ``` ```output ID NAME MODE REPLICAS IMAGE PORTS r8f2k1m0c5qz app_db replicated 1/1 postgres:16 ``` Comprueba el montaje dentro del contenedor de la tarea: ```bash docker exec "$(docker ps -q -f name=app_db)" ls -l /run/secrets/ ``` ```output total 4 -r--r--r-- 1 root root 44 Sep 25 10:31 db_password ``` Por defecto Swarm monta los secretos con propietario `root` y modo `0444`. Si tu aplicación necesita otro propietario, un nombre de archivo distinto o permisos más restrictivos, usa la sintaxis larga en el servicio: ```yaml secrets: - source: db_password target: db_password uid: "999" gid: "999" mode: 0400 ``` En la imagen oficial de PostgreSQL el usuario `postgres` tiene UID y GID `999`. ## Paso 5: Rotar un secreto Como un secreto de Swarm es inmutable, rotarlo consiste en crear uno nuevo y cambiar el servicio para que lo use. El truco es montar el secreto nuevo con el mismo nombre de archivo (`target`) que el antiguo, para no tener que tocar la configuración de la aplicación. Crea la nueva versión: ```bash openssl rand -base64 32 | tr -d '\n' | docker secret create db_password_v2 - ``` Actualiza el servicio: quita el secreto antiguo y añade el nuevo en la misma ruta. Swarm sustituye las tareas una a una: ```bash docker service update \ --secret-rm db_password \ --secret-add source=db_password_v2,target=db_password \ app_db ``` ```output app_db overall progress: 1 out of 1 tasks 1/1: running [==================================================>] verify: Service app_db converged ``` Cuando ningún servicio usa ya el secreto antiguo, elimínalo: ```bash docker secret rm db_password ``` > Importante: rotar el secreto solo cambia el archivo que ve el contenedor. PostgreSQL lee `POSTGRES_PASSWORD_FILE` únicamente al inicializar un volumen vacío, así que en una base de datos existente debes cambiar antes la contraseña del usuario (`ALTER USER app WITH PASSWORD '...'`) y después rotar el secreto en los servicios que se conectan a ella. Lo mismo ocurre con cualquier credencial que viva también en otro sistema. Si después actualizas `stack.yaml`, cambia la declaración a `db_password_v2` (con `external: true`) y la referencia del servicio a la sintaxis larga con `target: db_password`, o el siguiente `docker stack deploy` intentará volver al secreto borrado. ## Paso 6: Usar secretos durante el build Los secretos de Compose y Swarm existen solo en tiempo de ejecución. Si necesitas una credencial para construir la imagen (un token para descargar paquetes privados, por ejemplo), no la pases con `ARG` ni la copies con `COPY`: quedaría grabada en el historial o en una capa de la imagen. BuildKit, el motor de build por defecto de Docker, permite montar un secreto solo durante una instrucción `RUN`. Crea un directorio de prueba y un token de ejemplo: ```bash mkdir -p ~/build-secret-demo && cd ~/build-secret-demo printf 'token-de-ejemplo' > api_token.txt ``` Crea el Dockerfile: ```bash nano Dockerfile ``` ```dockerfile FROM alpine:3.20 RUN --mount=type=secret,id=api_token \ echo "El token tiene $(wc -c < /run/secrets/api_token) bytes" > /build-info.txt ``` En un caso real, esa instrucción `RUN` usaría el token para autenticarse, por ejemplo leyéndolo en la variable de un gestor de paquetes. Construye la imagen pasando el secreto: ```bash docker build --secret id=api_token,src=api_token.txt -t build-secret-demo . ``` Comprueba que el build pudo leerlo y que el token no ha quedado en la imagen: ```bash docker run --rm build-secret-demo sh -c 'cat /build-info.txt; ls /run/secrets 2>&1' ``` ```output El token tiene 16 bytes ls: /run/secrets: No such file or directory ``` El secreto solo estuvo montado mientras se ejecutaba esa instrucción `RUN`. ## Paso 7: Proteger las claves del Swarm con autolock Las claves que cifran el registro Raft se guardan en el disco de cada manager. Si alguien obtiene una copia del disco, puede descifrar los secretos. Con autolock, un manager que se reinicia necesita una clave de desbloqueo antes de volver a funcionar: ```bash docker swarm update --autolock=true ``` ```output Swarm updated. To unlock a swarm manager after it restarts, run the `docker swarm unlock` command and provide the following key: SWMKEY-1-... ``` Guarda esa clave en tu gestor de contraseñas. Tras reiniciar Docker en un manager, desbloquéalo con: ```bash docker swarm unlock ``` Valora si te compensa: autolock mejora la protección de los secretos a cambio de un paso manual en cada reinicio de un manager. ## Solución de problemas **`secret not found: db_password` al desplegar la pila.** El secreto está marcado como `external: true` pero no existe en el Swarm. Créalo con `docker secret create` o revisa el nombre con `docker secret ls`. **`secret 'db_password' is in use by the following service` al borrar.** Algún servicio lo sigue usando. Quítalo antes con `docker service update --secret-rm`. **PostgreSQL no arranca con `Permission denied` al leer el secreto en Compose.** El archivo del servidor no es legible para el UID del contenedor. Dale permisos `644` y protege el directorio que lo contiene. **`this node is not a swarm manager`.** Los comandos `docker secret` solo funcionan en un nodo manager de Swarm. Con Compose sin Swarm usa la clave `file:` como en el paso 2. ## Conclusión Has sacado las credenciales de las variables de entorno usando secretos de Compose en un único servidor y secretos cifrados de Swarm, has rotado un secreto sin reconstruir imágenes y has pasado un token al build sin dejarlo en la imagen. Como siguientes pasos, puedes añadir nodos worker al Swarm, centralizar las credenciales en un gestor externo como HashiCorp Vault o preparar un procedimiento de rotación periódica para las contraseñas de tus bases de datos.