[{"data":1,"prerenderedAt":179},["ShallowReactive",2],{"seo-verification":3,"blog-docker-secrets-produccion-proteger-credenciales-vps-es":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-docker-secrets-produccion-proteger-credenciales-vps-es",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":28,"featuredImage":30,"bgImage":31,"posterImage":32,"relatedSolution":30,"intro":33,"sections":34,"ctaTitle":118,"ctaBody":119,"ctaButton":120,"ctaUrl":121,"relatedPosts":122},369,"docker-secrets-produccion-proteger-credenciales-vps",{"fr":12,"en":13,"ar":14,"es":10},"docker-secrets-securite-vps-production","docker-secrets-production-protecting-credentials-on-vps","اسرار-docker-في-الانتاج-حماية-بيانات-الاعتماد-على-vps","Docker Secrets en producción: proteger credenciales en VPS","Gestiona secretos Docker en producción sin exponerlos en docker-compose.yml. Docker Secrets sin Swarm, .env.vault y SOPS: método y comparativa.",7,0,false,"2026-09-20T00:00:00+00:00","2026-09-20T21:13:51+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},8,"Seguridad y monitorización","securite-monitoring","bg-rose-500\u002F10 text-rose-400","security",[29],{"id":23,"name":24,"slug":25,"color":26,"icon":27},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fdocker-secrets-securite-vps-production-poster.svg","Un docker-compose.yml con claves API en texto plano es una filtración esperando al primer escáner. Este artículo explica cómo eliminar ese riesgo en un VPS root: Docker Secrets nativo (sin Swarm desde Compose v2.24), .env.vault y SOPS — cada método con sus comandos exactos y sus limitaciones reales.",[35,39,48,51,70,105,108,112,115],{"type":36,"title":37,"body":38},"h2","El riesgo real: tus secretos viajan dentro de tus imágenes","En 2024, GitGuardian detectó más de 12,8 millones de secretos expuestos en repositorios públicos de GitHub — un aumento del 28% respecto al año anterior según el **State of Secrets Sprawl 2025**. Las imágenes de Docker Hub son uno de los vectores más subestimados.\n\nCuando escribes `ARG API_KEY` en un `Dockerfile` o pasas `-e DB_PASSWORD=hunter2` al iniciar un contenedor, ese secreto no queda confinado al runtime. Puede terminar:\n\n- en las **capas de la imagen** (inspeccionable con `docker history --no-trunc`);\n- en los **metadatos de la imagen** exportada a Docker Hub (`docker inspect`);\n- en tu archivo `.env` **confirmado por error** durante un `git add .` apresurado.\n\nLos escáneres automáticos (Trivy, Grype, GitGuardian) recorren Docker Hub continuamente. Un repositorio público con un secreto en texto plano se indexa en minutos. La ventana de exposición es prácticamente nula.",{"type":40,"title":41,"items":42},"ul","5 errores de configuración que exponen tus secretos",[43,44,45,46,47],"**Variables de entorno en texto plano en `docker-compose.yml`** — `environment: DB_PASSWORD: hunter2` es legible por cualquiera que acceda al archivo o la imagen.","**Archivo `.env` confirmado en el repositorio** — un error en `.gitignore` y el secreto queda en el historial de git para siempre (incluso después de `git rm`, accesible vía `git log`).","**`ARG` pasado en el build y copiado en la imagen** — los `ARG` quedan grabados en los metadatos de la capa de imagen y son legibles con `docker history`.","**Secretos en los logs** — una aplicación que registra sus variables de entorno al arrancar (Spring Boot, Rails en modo debug, algunos servidores Node) imprime las credenciales en `docker logs`.","**Volúmenes bind-mount en `\u002Froot` o el directorio del proyecto** — un archivo `.env` montado desde el host sigue siendo accesible para cualquier proceso del contenedor con privilegios root.",{"type":36,"title":49,"body":50},"Requisitos previos","Para seguir este artículo necesitas:\n\n- Un **VPS Linux** (Debian 12 o Ubuntu 22.04+) con acceso root.\n- **Docker Engine ≥ 24** y **Docker Compose ≥ 2.24** (verifica con `docker compose version`).\n- Para SOPS: `age` instalado (paquete `age` en Debian\u002FUbuntu, o binario desde \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FFiloSottile\u002Fage\u002Freleases\">github.com\u002FFiloSottile\u002Fage\u003C\u002Fa>).\n- Para .env.vault: Node.js ≥ 18 y el CLI `dotenvx` (`npm install -g @dotenvx\u002Fdotenvx`).\n\nNo se requiere ningún clúster Swarm para ninguno de los métodos presentados aquí.",{"type":52,"title":53,"steps":54},"steps","Método 1 — Docker Secrets en Compose sin Swarm (paso a paso)",[55,58,61,64,67],{"title":56,"body":57},"Verificar la versión de Compose","Docker Secrets funciona sin Swarm desde **Docker Compose v2.24.0** (lanzado el 11 de enero de 2024). Verifica:\n\n```bash\ndocker compose version\n# Docker Compose version v2.27.1\n```\n\nSi estás por debajo de v2.24, actualiza Compose antes de continuar (`apt-get install docker-compose-plugin` en Debian\u002FUbuntu).",{"title":59,"body":60},"Crear los archivos de secretos","Los secretos de Docker Compose en modo no-Swarm son **archivos en el host**, montados como tmpfs dentro del contenedor. Créalos fuera del directorio del proyecto:\n\n```bash\nmkdir -p \u002Fetc\u002Fmyapp\u002Fsecrets\necho -n 'contraseña-db-segura' > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\necho -n 'clave-api-stripe-xxxxx' > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fstripe_key\nchmod 600 \u002Fetc\u002Fmyapp\u002Fsecrets\u002F*\nchown root:root \u002Fetc\u002Fmyapp\u002Fsecrets\u002F*\n```\n\nLa opción `-n` de `echo` evita el salto de línea final — algunas aplicaciones leen el archivo completo incluido el salto de línea, lo que invalida la clave.",{"title":62,"body":63},"Declarar los secretos en docker-compose.yml","```bash\nservices:\n  app:\n    image: miapp:latest\n    secrets:\n      - db_password\n      - stripe_key\n    environment:\n      # indicamos la RUTA, no el valor\n      DB_PASSWORD_FILE: \u002Frun\u002Fsecrets\u002Fdb_password\n      STRIPE_KEY_FILE: \u002Frun\u002Fsecrets\u002Fstripe_key\n\nsecrets:\n  db_password:\n    file: \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\n  stripe_key:\n    file: \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fstripe_key\n```\n\nObserva el uso de la convención `*_FILE`: tu aplicación debe leer la variable `DB_PASSWORD_FILE`, abrir el archivo indicado y leer su contenido. Las imágenes oficiales de PostgreSQL, MySQL, Redis y la mayoría de imágenes Bitnami **soportan esta convención de forma nativa** — consulta la documentación de tu imagen.",{"title":65,"body":66},"Verificar el montaje dentro del contenedor","Tras `docker compose up -d`, inspecciona el montaje:\n\n```bash\ndocker compose exec app ls -la \u002Frun\u002Fsecrets\u002F\n# -r-------- 1 root root 20 Sep 20 08:12 db_password\n# -r-------- 1 root root 28 Sep 20 08:12 stripe_key\n\ndocker inspect miapp_app_1 | grep -A5 Mounts\n# \"Type\": \"tmpfs\",\n# \"Destination\": \"\u002Frun\u002Fsecrets\",\n```\n\nEl tipo de montaje es **tmpfs**: el contenido vive en RAM, nunca escrito en el disco del contenedor. Desaparece al detener el contenedor.",{"title":68,"body":69},"Lo que Docker Secrets NO hace — entiéndelo antes de continuar","Docker Secrets no es una caja fuerte hermética. Lo que **no protege**:\n\n- El archivo fuente (`\u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password`) permanece en el **disco del host** en texto plano — root en la VM siempre tiene acceso.\n- Cualquier **proceso del contenedor** (PID 1 o un subproceso lanzado por la app) puede leer `\u002Frun\u002Fsecrets\u002F*`.\n- Una **variable de entorno derivada** del secreto (`DB_PASSWORD=$(cat \u002Frun\u002Fsecrets\u002Fdb_password)` en un entrypoint) vuelve a poner el secreto en el entorno, visible vía `docker inspect`.\n\nDocker Secrets protege contra fugas en capas de imagen y en `docker-compose.yml`. No protege contra un proceso comprometido dentro del contenedor.",{"type":71,"title":72,"headers":73,"rows":78},"comparison","Docker Secrets vs .env.vault vs SOPS — qué método para qué contexto",[74,75,76,77],"Criterio","Docker Secrets (Compose)",".env.vault (dotenvx)","SOPS + age",[79,83,87,91,95,100],[80,81,82,82],"Swarm requerido","No (desde Compose v2.24)","No",[84,85,86,86],"Secreto almacenado en texto plano en host","Sí (archivo fuente)","No (cifrado en repositorio)",[88,82,89,90],"KMS remoto requerido","No (clave simétrica local posible)","No (age funciona offline)",[92,93,94,94],"Rotación sin redespliegue","No (reinicio necesario)","No (rebuild .env)",[96,97,98,99],"CI\u002FCD: inyección en el pipeline","Complejo (archivos a provisionar)","Simple (variable `DOTENV_PRIVATE_KEY`)","Medio (clave age como secreto CI)",[101,102,103,104],"Curva de aprendizaje","Baja (nativo Compose)","Baja (CLI dotenvx)","Media (sintaxis YAML + claves age\u002FGPG)",{"type":36,"title":106,"body":107},"Configuración complementaria: rotación y cifrado en reposo","**Rotación de secretos en Docker Compose.** Docker Compose no-Swarm no soporta rotación en caliente (a diferencia de Swarm, que puede actualizar un secreto sin detener el servicio). Para cambiar un secreto:\n\n```bash\n# 1. Escribir el nuevo valor\necho -n 'nueva-contraseña' > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\n# 2. Reiniciar el servicio afectado\ndocker compose restart app\n```\n\n**Cifrar los archivos fuente con age.** Si quieres cifrar los archivos en el host (contra robo de snapshot o backup comprometido), SOPS + age permite almacenar archivos cifrados y descifrarlos al arrancar:\n\n```bash\n# Generar una clave age\nage-keygen -o \u002Froot\u002F.config\u002Fsops\u002Fage\u002Fkeys.txt\n# Cifrar el archivo de secreto\nsops --encrypt --age $(age-keygen -y \u002Froot\u002F.config\u002Fsops\u002Fage\u002Fkeys.txt) \\\n  \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password.enc\n# En el script de arranque, descifrar antes de docker compose up\nsops --decrypt \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password.enc > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\n```\n\n**Auditoría de accesos.** Activa los logs de Docker con `journald` (`--log-driver=journald` en `\u002Fetc\u002Fdocker\u002Fdaemon.json`) para mantener un registro de quién lanzó qué contenedores y cuándo.",{"type":109,"title":110,"body":111},"tip","Lo que Docker Secrets no protege","Docker Secrets monta el secreto como **tmpfs en `\u002Frun\u002Fsecrets\u002F`**: es una red de seguridad contra fugas en imágenes y archivos Compose, no contra un proceso comprometido dentro del contenedor. Cualquier proceso que corra en el contenedor — incluyendo un shell obtenido vía RCE — puede leer `\u002Frun\u002Fsecrets\u002F*`. Y root en el host siempre tiene acceso al archivo fuente.\n\nSi tu modelo de amenaza incluye un contenedor comprometido, la respuesta correcta es un gestor de secretos externo (HashiCorp Vault, AWS Secrets Manager, Infisical self-hosted) que entregue secretos vía API con autenticación, sin escribirlos nunca en el disco del contenedor.",{"type":36,"title":113,"body":114},"Resolución de problemas — errores comunes","**`unknown shorthand flag: 's' in -s`** durante `docker compose up`\nEstás usando el comando antiguo `docker-compose` (v1, Python). Cambia a `docker compose` (v2, plugin Go) con `apt-get install docker-compose-plugin`.\n\n**`secrets are only supported when deploying to a swarm`**\nTu versión de Docker Compose es inferior a v2.24. Verifica con `docker compose version` y actualiza.\n\n**El contenedor arranca pero `\u002Frun\u002Fsecrets\u002Fdb_password` está vacío**\nVerifica que el archivo fuente existe y no está vacío: `cat \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password | wc -c`. Un archivo vacío crea un montaje tmpfs vacío, sin error.\n\n**`permission denied` al leer `\u002Frun\u002Fsecrets\u002F`**\nLos archivos se montan con los permisos del archivo fuente. Si tu proceso corre como usuario no-root en el contenedor, ajusta los permisos del archivo en el host: `chmod 640 \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password` y verifica el GID del proceso.\n\n**`docker inspect` sigue mostrando la variable de entorno en texto plano**\nDeclaraste el secreto pero también pasaste el valor en `environment:`. Elimina la entrada con valor directo y usa solo la convención `*_FILE` en `environment:`.",{"type":36,"title":116,"body":117},"Tus secretos bajo control — y los siguientes pasos","Docker Secrets en modo Compose no-Swarm elimina la principal causa de filtración: credenciales en texto plano en archivos de configuración y capas de imagen. El método cabe en cinco pasos, funciona sin infraestructura externa y se integra en cualquier flujo de trabajo existente.\n\nPara ir más lejos:\n\n- **Checklist de producción**: \u003Ca href=\"\u002Fblog\u002Fdocker-compose-production-checklist\">los 10 puntos imprescindibles para un docker-compose.yml listo para producción\u003C\u002Fa>.\n- **Primeros pasos con Docker en VPS**: \u003Ca href=\"\u002Fblog\u002Fdemarrer-avec-docker-vps\">empezar con Docker en VPS\u003C\u002Fa> si estás construyendo tu primer entorno.\n- **Hardening del SO**: \u003Ca href=\"\u002Fblog\u002Flinux-hardening-vps-checklist\">checklist de hardening Linux\u003C\u002Fa> para asegurar la capa host sobre la que corre Docker.","Controla tu stack, controla tus secretos","Un VPS root con acceso Docker completo: tú decides la gestión de secretos, la superficie de ataque y cada capa de tu infraestructura.","Desplegar en VPS Cloud","\u002Fvps-cloud",[123,142,163],{"id":124,"slug":125,"slugs":126,"title":130,"excerpt":131,"readTime":23,"views":18,"isPinned":19,"publishedAt":132,"updatedAt":133,"category":134,"categories":139,"featuredImage":30,"bgImage":31,"posterImage":141,"relatedSolution":30},229,"checklist-docker-compose-en-produccion",{"fr":127,"en":128,"ar":129,"es":125},"docker-compose-production-checklist","docker-compose-in-production-10-point-checklist","docker-compose-في-الإنتاج-قائمة-التحقق-من-10-نقاط","Docker Compose en producción: checklist de 10 puntos","Checklist de Docker Compose en producción: 10 ajustes esenciales, gestión de secretos sin downtime, copias de volúmenes sin corrupción, CVE-2026-17106.","2026-08-06T00:00:00+00:00","2026-09-07T11:26:10+00:00",{"id":135,"name":136,"slug":137,"color":138,"icon":137},3,"Despliegue","deploiement","bg-success\u002F10 text-success",[140],{"id":135,"name":136,"slug":137,"color":138,"icon":137},"\u002Fblog\u002Fcovers\u002Fdocker-compose-production-checklist-poster.svg",{"id":143,"slug":144,"slugs":145,"title":149,"excerpt":150,"readTime":135,"views":18,"isPinned":19,"publishedAt":151,"updatedAt":133,"category":152,"categories":158,"featuredImage":30,"bgImage":31,"posterImage":160,"relatedSolution":161},136,"instalar-docker-en-un-vps",{"fr":146,"en":147,"ar":148,"es":144},"demarrer-avec-docker-vps","installing-docker-on-a-vps-a-clean-base-for-your-apps","تثبيت-docker-على-vps-قاعدة-نظيفة-لتطبيقاتك","Instalar Docker en un VPS: una base limpia para sus apps","Prepare un VPS Docker fiable: aislamiento, Compose, volúmenes, red y buenas prácticas para desplegar sin improvisar.","2026-02-10T00:00:00+00:00",{"id":153,"name":154,"slug":155,"color":156,"icon":157},4,"Desarrollo","developpement","bg-warning\u002F10 text-warning","dev",[159],{"id":153,"name":154,"slug":155,"color":156,"icon":157},"\u002Fblog\u002Fcovers\u002Fdemarrer-avec-docker-vps-poster.svg",{"categorySlug":157,"appSlug":162},"docker-starter",{"id":164,"slug":165,"slugs":166,"title":170,"excerpt":171,"readTime":172,"views":173,"isPinned":19,"publishedAt":174,"updatedAt":133,"category":175,"categories":176,"featuredImage":30,"bgImage":31,"posterImage":178,"relatedSolution":30},317,"hardening-linux-vps-checklist-para-agencias-tras-la-entrega",{"fr":167,"en":168,"ar":169,"es":165},"linux-hardening-vps-checklist","linux-vps-hardening-checklist-for-agencies","قائمة-تصليب-خادم-لينكس-للوكالات-بعد-التسليم","Hardening Linux VPS : checklist para agencias tras la entrega","Checklist de hardening Linux reproducible para agencias: auditd, sudo, clave SSH, UFW, fail2ban y desactivación de root — con trazabilidad por cliente.",11,1,"2026-08-30T00:00:00+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},[177],{"id":23,"name":24,"slug":25,"color":26,"icon":27},"\u002Fblog\u002Fcovers\u002Flinux-hardening-vps-checklist-poster.svg",1789939224673]