[{"data":1,"prerenderedAt":179},["ShallowReactive",2],{"seo-verification":3,"blog-docker-secrets-production-protecting-credentials-on-vps-en":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-docker-secrets-production-protecting-credentials-on-vps-en",{"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-production-protecting-credentials-on-vps",{"fr":12,"en":10,"ar":13,"es":14},"docker-secrets-securite-vps-production","اسرار-docker-في-الانتاج-حماية-بيانات-الاعتماد-على-vps","docker-secrets-produccion-proteger-credenciales-vps","Docker Secrets in production: protecting secrets on a VPS","Manage Docker secrets in production without exposing them in docker-compose.yml. Docker Secrets without Swarm, .env.vault and SOPS: method and comparison.",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,"Security & Monitoring","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","A docker-compose.yml with API keys in plaintext is a leak waiting for a scanner. This article shows how to eliminate that risk on a root VPS: native Docker Secrets (without Swarm since Compose v2.24), .env.vault and SOPS — each method with exact commands and real limitations.",[35,39,48,51,70,105,108,112,115],{"type":36,"title":37,"body":38},"h2","The real risk: your secrets travel inside your images","In 2024, GitGuardian detected over 12.8 million exposed secrets in public GitHub repositories — a 28% increase year-over-year according to the **State of Secrets Sprawl 2025**. Docker Hub images are one of the most underestimated vectors.\n\nWhen you write `ARG API_KEY` in a `Dockerfile` or pass `-e DB_PASSWORD=hunter2` at container startup, that secret doesn't stay confined to runtime. It can end up:\n\n- in the **image layers** (inspectable with `docker history --no-trunc`);\n- in the **image metadata** exported to Docker Hub (`docker inspect`);\n- in your `.env` file **accidentally committed** during a hurried `git add .`.\n\nAutomated scanners (Trivy, Grype, GitGuardian) continuously crawl Docker Hub. A public repository with a plaintext secret is indexed within minutes. The exposure window is nearly zero.",{"type":40,"title":41,"items":42},"ul","5 configuration mistakes that expose your secrets",[43,44,45,46,47],"**Plaintext env vars in `docker-compose.yml`** — `environment: DB_PASSWORD: hunter2` is readable by anyone who accesses the file or the image.","**Committed `.env` file** — miss `.gitignore` once, and the secret is in git history forever (even after `git rm`, accessible via `git log`).","**`ARG` passed at build then copied into the image** — `ARG`s are baked into layer metadata and readable via `docker history`.","**Secrets in logs** — an application that logs its environment variables at startup (Spring Boot, Rails in debug mode, some Node servers) prints credentials in `docker logs`.","**Bind-mount volumes on `\u002Froot` or the project directory** — a `.env` file mounted from the host remains accessible to any process in the container with root privileges.",{"type":36,"title":49,"body":50},"Prerequisites","To follow this article, you need:\n\n- A **Linux VPS** (Debian 12 or Ubuntu 22.04+) with root access.\n- **Docker Engine ≥ 24** and **Docker Compose ≥ 2.24** (check with `docker compose version`).\n- For SOPS: `age` installed (`age` package on Debian\u002FUbuntu, or binary from \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FFiloSottile\u002Fage\u002Freleases\">github.com\u002FFiloSottile\u002Fage\u003C\u002Fa>).\n- For .env.vault: Node.js ≥ 18 and the `dotenvx` CLI (`npm install -g @dotenvx\u002Fdotenvx`).\n\nNo Swarm cluster is required for any of the methods presented here.",{"type":52,"title":53,"steps":54},"steps","Method 1 — Docker Secrets in non-Swarm Compose (step by step)",[55,58,61,64,67],{"title":56,"body":57},"Check the Compose version","Docker Secrets work without Swarm since **Docker Compose v2.24.0** (released January 11, 2024). Verify:\n\n```bash\ndocker compose version\n# Docker Compose version v2.27.1\n```\n\nIf you're below v2.24, update Compose before continuing (`apt-get install docker-compose-plugin` on Debian\u002FUbuntu).",{"title":59,"body":60},"Create the secret files","Docker Compose secrets in non-Swarm mode are **files on the host**, mounted as tmpfs inside the container. Create them outside the project directory:\n\n```bash\nmkdir -p \u002Fetc\u002Fmyapp\u002Fsecrets\necho -n 'strong-db-password' > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\necho -n 'stripe-api-key-xxxxx' > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fstripe_key\nchmod 600 \u002Fetc\u002Fmyapp\u002Fsecrets\u002F*\nchown root:root \u002Fetc\u002Fmyapp\u002Fsecrets\u002F*\n```\n\nThe `-n` flag of `echo` avoids a trailing newline — some applications read the entire file including the newline, which invalidates the key.",{"title":62,"body":63},"Declare secrets in docker-compose.yml","```bash\nservices:\n  app:\n    image: myapp:latest\n    secrets:\n      - db_password\n      - stripe_key\n    environment:\n      # indicate the PATH, not the value\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\nNote the use of the `*_FILE` convention: your application must read the `DB_PASSWORD_FILE` variable, open the indicated file and read its content. Official PostgreSQL, MySQL, Redis images and most Bitnami images **natively support this convention** — check your image's documentation.",{"title":65,"body":66},"Verify the mount inside the container","After `docker compose up -d`, inspect the mount:\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 myapp_app_1 | grep -A5 Mounts\n# \"Type\": \"tmpfs\",\n# \"Destination\": \"\u002Frun\u002Fsecrets\",\n```\n\nThe mount type is **tmpfs**: the content lives in RAM, never written to the container's disk. It disappears when the container stops.",{"title":68,"body":69},"What Docker Secrets does NOT do — understand before continuing","Docker Secrets is not a hermetic vault. What it **does not protect against**:\n\n- The source file (`\u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password`) remains on the **host disk** in plaintext — root on the VM always has access.\n- Any **process in the container** (PID 1 or a subprocess launched by the app) can read `\u002Frun\u002Fsecrets\u002F*`.\n- An **env variable derived** from the secret (`DB_PASSWORD=$(cat \u002Frun\u002Fsecrets\u002Fdb_password)` in an entrypoint) puts the secret back into the env, visible via `docker inspect`.\n\nDocker Secrets protects against leaks in image layers and in `docker-compose.yml`. It does not protect against a compromised process inside the container.",{"type":71,"title":72,"headers":73,"rows":78},"comparison","Docker Secrets vs .env.vault vs SOPS — which method for which context",[74,75,76,77],"Criterion","Docker Secrets (Compose)",".env.vault (dotenvx)","SOPS + age",[79,83,87,91,95,100],[80,81,82,82],"Swarm required","No (since Compose v2.24)","No",[84,85,86,86],"Secret stored in plaintext on host","Yes (source file)","No (encrypted in repo)",[88,82,89,90],"Remote KMS required","No (local symmetric key possible)","No (age works offline)",[92,93,94,94],"Rotation without redeployment","No (restart needed)","No (rebuild .env)",[96,97,98,99],"CI\u002FCD: injection into the pipeline","Complex (files to provision)","Simple (`DOTENV_PRIVATE_KEY` variable)","Medium (age key as CI secret)",[101,102,103,104],"Learning curve","Low (native Compose)","Low (dotenvx CLI)","Medium (YAML syntax + age\u002FGPG keys)",{"type":36,"title":106,"body":107},"Supplementary configuration: rotation and encryption at rest","**Docker Compose secret rotation.** Non-Swarm Docker Compose does not support hot rotation (unlike Swarm which can update a secret without stopping the service). To change a secret:\n\n```bash\n# 1. Write the new value\necho -n 'new-password' > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\n# 2. Restart the affected service\ndocker compose restart app\n```\n\n**Encrypting source files with age.** If you want to encrypt files on the host (against snapshot theft or a compromised backup), SOPS + age lets you store encrypted files and decrypt them at startup:\n\n```bash\n# Generate an age key\nage-keygen -o \u002Froot\u002F.config\u002Fsops\u002Fage\u002Fkeys.txt\n# Encrypt the secret file\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# In your startup script, decrypt before docker compose up\nsops --decrypt \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password.enc > \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password\n```\n\n**Access auditing.** Enable Docker logs with `journald` (`--log-driver=journald` in `\u002Fetc\u002Fdocker\u002Fdaemon.json`) to keep a record of who launched which containers and when.",{"type":109,"title":110,"body":111},"tip","What Docker Secrets does not protect","Docker Secrets mounts the secret as **tmpfs in `\u002Frun\u002Fsecrets\u002F`**: it's a safety net against leaks in images and Compose files, not against a compromised process inside the container. Any process running in the container — including a shell obtained via RCE — can read `\u002Frun\u002Fsecrets\u002F*`. And root on the host always has access to the source file.\n\nIf your threat model includes a compromised container, the right answer is an external secret manager (HashiCorp Vault, AWS Secrets Manager, self-hosted Infisical) that delivers secrets via API with authentication, without ever writing them to the container disk.",{"type":36,"title":113,"body":114},"Troubleshooting — common errors","**`unknown shorthand flag: 's' in -s`** during `docker compose up`\nYou are using the old `docker-compose` command (v1, Python). Switch to `docker compose` (v2, Go plugin) with `apt-get install docker-compose-plugin`.\n\n**`secrets are only supported when deploying to a swarm`**\nYour Docker Compose version is below v2.24. Check with `docker compose version` and update.\n\n**Container starts but `\u002Frun\u002Fsecrets\u002Fdb_password` is empty**\nVerify that the source file exists and is not empty: `cat \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password | wc -c`. An empty file creates an empty tmpfs mount, without error.\n\n**`permission denied` when reading `\u002Frun\u002Fsecrets\u002F`**\nFiles are mounted with the source file's permissions. If your process runs as a non-root user in the container, adjust the host file permissions: `chmod 640 \u002Fetc\u002Fmyapp\u002Fsecrets\u002Fdb_password` and check the process GID.\n\n**`docker inspect` still shows the env var in plaintext**\nYou declared the secret but also passed the value in `environment:`. Remove the direct-value entry and only use the `*_FILE` convention in `environment:`.",{"type":36,"title":116,"body":117},"Your secrets under control — and what's next","Docker Secrets in non-Swarm Compose mode eliminates the main cause of leaks: plaintext credentials in configuration files and image layers. The method fits in five steps, works without external infrastructure and integrates into any existing workflow.\n\nTo go further:\n\n- **Production checklist**: \u003Ca href=\"\u002Fblog\u002Fdocker-compose-production-checklist\">the 10 essential points for a production-ready docker-compose.yml\u003C\u002Fa>.\n- **First steps with Docker on VPS**: \u003Ca href=\"\u002Fblog\u002Fdemarrer-avec-docker-vps\">getting started with Docker on VPS\u003C\u002Fa> if you are building your first environment.\n- **OS hardening**: \u003Ca href=\"\u002Fblog\u002Flinux-hardening-vps-checklist\">Linux hardening checklist\u003C\u002Fa> to secure the host layer on which Docker runs.","Control your stack, control your secrets","A root VPS with full Docker access: you decide on secrets management, attack surface, and every layer of your infrastructure.","Deploy on 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,"docker-compose-in-production-10-point-checklist",{"fr":127,"en":125,"ar":128,"es":129},"docker-compose-production-checklist","docker-compose-في-الإنتاج-قائمة-التحقق-من-10-نقاط","checklist-docker-compose-en-produccion","Docker Compose in Production: 10-Point Checklist","10 Docker Compose settings to verify before any production deployment: restart, healthchecks, limits, secrets and logs.","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,"Deployment","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,"installing-docker-on-a-vps-a-clean-base-for-your-apps",{"fr":146,"en":144,"ar":147,"es":148},"demarrer-avec-docker-vps","تثبيت-docker-على-vps-قاعدة-نظيفة-لتطبيقاتك","instalar-docker-en-un-vps","Installing Docker on a VPS: A Clean Base for Your Apps","Set up a reliable Docker VPS: isolation, Compose, volumes, networking and best practices to deploy without improvising.","2026-02-10T00:00:00+00:00",{"id":153,"name":154,"slug":155,"color":156,"icon":157},4,"Development","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,"linux-vps-hardening-checklist-for-agencies",{"fr":167,"en":165,"ar":168,"es":169},"linux-hardening-vps-checklist","قائمة-تصليب-خادم-لينكس-للوكالات-بعد-التسليم","hardening-linux-vps-checklist-para-agencias-tras-la-entrega","Linux VPS Hardening Checklist for Agencies","Reproducible Linux hardening checklist for agencies: auditd, sudo user, SSH key auth, UFW, fail2ban and root lockout — with per-client traceability.",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",1789939156718]