Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Docker Secrets in Compose and Swarm

By Kokil Thapa | Last reviewed: September 2026

Hard-coded passwords in docker-compose.yml files cause real breaches. Docker Secrets in Compose and Swarm give you a built-in way to inject credentials at runtime without baking them into images or plain environment variables. Swarm encrypts secrets in its Raft store and mounts them as read-only files under /run/secrets/. Compose supports the same file-mount pattern for local stacks. If you run multi-container Docker Compose apps or small Swarm clusters on Ubuntu, this guide covers the commands, YAML, and production habits that actually stick.

What Are Docker Secrets in Compose and Swarm?

Docker secrets are named blobs of sensitive data. Swarm distributes them only to tasks that declare a need. Compose maps the same concept to files on your dev machine. Neither approach puts the raw value in your image layers or in docker inspect output the way environment: blocks do.

In Swarm mode, secrets are encrypted at rest in the cluster state. They travel over mutual TLS between manager and worker nodes. Inside a running container, Docker mounts each secret on a tmpfs filesystem. The file is world-readable inside the container namespace, but it never lands on the host disk outside that mount.

Compose without Swarm still benefits from the secrets block. You define a file path or external name, and Compose bind-mounts it into the container at the standard path. That keeps your Laravel .env database password out of Git while you follow a local Laravel Docker Compose workflow.

Docker Swarm Secrets FlowSwarm Managerdocker secret createEncrypted RaftAES-GCM at restWorker NodeTLS deliveryRunning Containertmpfs at /run/secrets/db_passwordApp reads file — never ENV or image layer
How Docker Secrets in Compose and Swarm move from manager to encrypted store to in-container tmpfs mount

Secrets vs environment variables vs bind mounts

Environment variables show up in process listings and child shells. Bind mounts from host paths leak files onto disk that backups and log scrapers can touch. Secrets target a narrow middle ground: file-based consumption with tmpfs backing in Swarm.

MethodVisible in docker inspectOn host diskSwarm encrypted transitBest for
environment:Yes (value exposed)N/ANoNon-sensitive config flags
Bind mount fileMount path onlyYesNoDev-only shortcuts
Docker secretName onlyNo (tmpfs)YesDB passwords, API keys, TLS keys
External vaultDepends on sidecarDependsDependsMulti-cluster, rotation at scale

For larger fleets, pair Swarm secrets with HashiCorp Vault for secrets management or an external secrets operator. Swarm secrets solve single-cluster delivery. Vault solves issuance, audit, and dynamic credentials.

How Do You Create and List Secrets in Docker Swarm?

Swarm secrets require an active Swarm manager. Initialize one if needed, then create secrets from stdin, a file, or a pipe. Names are immutable; updates require a new secret name and a service redeploy.

Initialize Swarm and create secrets

  1. Run docker swarm init on your manager node after you install Docker on Ubuntu.
  2. Create a secret from a file or stdin:
# From stdin (preferred — avoids leaving files on disk)
printf 'SuperSecretDbPass2026' | docker secret create db_password -

# From a file (delete the file immediately after)
docker secret create api_key ./api_key.txt

# List secrets (names and IDs only — never values)
docker secret ls

Remove a secret only when no running service references it:

docker secret rm db_password

Inspect metadata without revealing the payload:

docker secret inspect db_password --format '{{ .Spec.Name }}'

Attach secrets to a service

Standalone docker service create accepts --secret flags. Compose with deploy: blocks is easier for multi-service stacks.

docker service create \
  --name web \
  --secret db_password \
  --secret source=api_key,target=payment_key,mode=0440 \
  myregistry/laravel-app:2026

The optional target renames the in-container filename. The optional mode sets Unix permissions. Default mode is 0444 (read-only for all users in the container).

How Do You Configure Docker Compose Secrets for Local and Swarm Stacks?

Compose file format 3.1 and later supports a top-level secrets: key. Services reference secret names; Compose wires the mount. Behaviour differs slightly between docker compose up on a laptop and docker stack deploy on Swarm.

Compose Secrets Local WorkflowHost Secret File./secrets/db.txtcompose.ymlsecrets: file: mappingContainer/run/secrets/db.gitignore secrets/ — never commit plaintextUse .env.example for structure onlyPHP/Laravel: file_get_contents('/run/secrets/db')
Docker Compose secrets map host files into containers at the standard /run/secrets path for local development

Local Compose with file-based secrets

Store secret files outside version control. Add secrets/ to .gitignore. Reference them in Compose:

services:
  app:
    image: laravel-app:local
    secrets:
      - db_password
      - redis_password
    environment:
      APP_ENV: local
    networks:
      - backend

  db:
    image: mysql:9.7
    environment:
      MYSQL_DATABASE: app
    secrets:
      - db_password
    # Custom entrypoint reads /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
  redis_password:
    file: ./secrets/redis_password.txt

networks:
  backend:

Run the stack:

docker compose up -d

For override patterns—different secret paths per developer—use Docker Compose profiles and overrides instead of duplicating entire compose files.

Swarm stack deploy with external secrets

When you deploy with docker stack deploy, secrets must already exist in Swarm. Compose marks them as external: true.

services:
  app:
    image: registry.example.com/laravel:13
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
    secrets:
      - db_password
      - app_key
    networks:
      - web

secrets:
  db_password:
    external: true
  app_key:
    external: true

networks:
  web:
    driver: overlay

Deploy sequence:

printf 'prod-db-pass' | docker secret create db_password -
printf 'base64-app-key' | docker secret create app_key -
docker stack deploy -c docker-compose.prod.yml myapp

I've used this pattern on shared EC2 infrastructure where several sites share a Deployer and GitLab CI pipeline. Secrets are created once per environment on the Swarm manager. CI never prints values—only checks that names exist before deploy.

How Should Your Application Read Docker Secrets?

Applications should treat /run/secrets/<name> like any other credential file. Read once at boot, cache in memory, and never log the contents. Strip trailing newlines—Docker does not trim stdin payloads.

PHP and Laravel example

Laravel 13 on PHP 8.3+ can read a secret before the config cache builds:

<?php
// config/database.php — read Swarm/Compose secret when present
$secretPath = '/run/secrets/db_password';
$dbPassword = file_exists($secretPath)
    ? trim(file_get_contents($secretPath))
    : env('DB_PASSWORD');

return [
    'connections' => [
        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', 'db'),
            'password' => $dbPassword,
            // ...
        ],
    ],
];

Do not run php artisan config:cache with a baked-in password from .env on production Swarm nodes. Cache config after secrets are mounted, or keep password resolution dynamic as shown above. On booking systems like Adventure Third Pole Trek, credential rotation without redeploying code saved maintenance windows.

Entrypoint scripts for official images

MySQL and PostgreSQL official images expect env vars, not secret files. A thin entrypoint wrapper bridges the gap:

#!/bin/sh
set -e
export MYSQL_ROOT_PASSWORD="$(cat /run/secrets/db_password)"
exec docker-entrypoint.sh mysqld

Mount the script via a custom image layer or a config object in Swarm. Keep the script in Git; keep secrets out.

Node.js and generic shells

const fs = require('fs');
const dbPass = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

Validate file existence early. A missing secret should fail fast with a clear error—not fall back to an empty password.

ENV vs Docker Secretsenvironment: blockVisible in inspectLeaks to child processesLogged by debug toolsDocker secret fileName only in inspecttmpfs — no host fileExplicit service grantRule: ENV for config flagsSecrets for passwords, tokens, private keysScan Git with gitleaks before every push
Why Docker Secrets in Compose and Swarm beat plain environment variables for credentials in production stacks

What Security Pitfalls Break Docker Secrets in Production?

Secrets solve delivery, not every security problem. Teams still leak credentials through logs, images, and stale secret names. Treat the items below as a pre-deploy checklist.

Common mistakes

  • Committing secrets/*.txt because someone copied a tutorial path without .gitignore rules. Run secrets scanning in Git and CI with gitleaks on every pipeline.
  • Using ARG in Dockerfiles for passwords. Build args persist in image history. Pass secrets only at runtime.
  • Assuming Swarm secrets rotate in place. They do not. Create db_password_v2, update the service, then remove the old secret.
  • Granting every service every secret. Attach only what each task needs—same least-privilege mindset as RBAC in Laravel.
  • Storing TLS private keys as secrets but serving HTTP anyway. Pair secrets with proper Docker networking and edge TLS termination.

Swarm secrets are unavailable on plain docker compose up without Swarm unless you use the file-based Compose secrets block. Do not confuse the two modes during a handoff from dev laptop to production cluster. Document which secret names exist on the manager.

Rotation workflow

  1. Create a new secret with a version suffix.
  2. Update database credentials on the server side.
  3. Redeploy the service with the new secret reference.
  4. Confirm health checks pass.
  5. Remove the old secret after all tasks roll forward.

For databases that support dual-password windows, rotation is painless. For static API keys at payment gateways—eSewa, Khalti, Stripe—schedule maintenance and test callbacks in staging first.

When Swarm secrets are not enough

Swarm mode is declining in greenfield projects. Many teams choose Kubernetes or stay on single-node Compose with external vaults. Compare orchestrators in Kubernetes vs Docker Swarm when to use which before investing in Swarm-specific secret tooling.

If you stay on Swarm, integrate CI secrets separately. GitLab CI masked variables can pipe into docker secret create during deploy jobs. Follow broader CI/CD secrets management best practices so build logs stay clean. Ansible users often encrypt files with Ansible Vault for secrets before a playbook creates Docker secrets on the target host.

Secret Rotation on SwarmCreate v2Update DBRolling deployDrop v1Immutable secret names — no in-place editdocker service update --secret-rm --secret-addHealthcheck must pass before old tasks drain
Production rotation for Docker Secrets in Compose and Swarm uses versioned names and rolling service updates

How Do Docker Secrets Fit a Laravel or Small-Team Deploy Pipeline?

Most of my client stacks still run Compose on a single VPS or a two-node Swarm. The workflow is boring on purpose. Developers use file secrets locally. Production CI creates Swarm secrets over SSH. Deployer symlinks releases; Docker mounts credentials at runtime.

Generate strong local dev passwords with a secure password generator. Never reuse production values on a laptop. For enterprise apps with audit requirements, see enterprise application development services that wire vault backends when Swarm alone is too thin.

A practical docker-compose.override.yml for developers might point at sample secrets while production compose stays external-only. Combine with Docker Compose multi-container local setup guidance so new hires boot the stack in one command.

Health checks and secret-dependent startup

If your app exits when a secret file is missing, set Compose health checks after the entrypoint reads credentials. See Docker healthchecks explained for timing that avoids flapping during slow DB restarts.

Linux host hardening still matters. Secrets do not replace firewall rules, fail2ban, or patched kernels. Linux system administration covers the host layer Swarm sits on.

Alternatives worth knowing

Vault dynamic secrets for databases issue short-lived credentials—better than static files for high-churn teams. External Secrets Operator with Vault targets Kubernetes, not Swarm, but the read path in your app stays similar: read a file, connect, discard.

For teams migrating off Swarm, export secret names and rotation runbooks before you touch orchestration. Document every consumer that reads /run/secrets/. A missed reference becomes a midnight outage.

Ongoing ops—monitoring failed deploys, cleaning orphaned secrets, renewing TLS—fits support and maintenance retainers. Secret hygiene is monthly housekeeping, not a one-time migration.

Key Takeaways

  • Create Swarm secrets with docker secret create; use file: mappings in Compose for local dev and external: true for stack deploys.
  • Read credentials from /run/secrets/<name> in application code—never from environment: for passwords or API keys.
  • Rotate by creating versioned secret names and rolling services forward; Swarm does not support in-place secret edits.
  • Keep secrets/ out of Git, scan repos in CI, and avoid ARG for sensitive build-time values.
  • Pair Docker secrets with vault tools when you need audit trails, dynamic DB credentials, or multi-cluster delivery.
  • Document which services consume which secret names before handoff—missing mounts fail silently until the app crashes.

People Also Ask

Can Docker Compose use secrets without Swarm mode?

Yes. Compose mounts file-based secrets into containers on a single Docker engine. You do not need docker swarm init for local development. Production Swarm deploys require secrets to exist in the cluster before docker stack deploy.

Are Docker Swarm secrets encrypted?

Yes. Swarm encrypts secrets at rest in the Raft log and transmits them over TLS to worker nodes. Inside containers they live on tmpfs. They are not encrypted separately inside the container filesystem—any process in that container namespace can read the mounted file.

What is the difference between Docker configs and secrets?

Both mount as files under /run/. Configs hold non-sensitive data like nginx.conf. Secrets hold credentials and keys. Swarm stores both in the cluster state, but secrets get stricter handling and separate CLI commands. Do not put passwords in configs.

How do I update a Docker secret without downtime?

Add a new secret with a new name, update the service to reference it alongside or instead of the old one, and use rolling updates with health checks. Remove the old secret only after every task uses the new mount. Plan database password changes to allow overlap when possible.

Ship Credentials the Right Way

Docker Secrets in Compose and Swarm are the native path to keep passwords and keys out of images, Git, and casual docker inspect output. Start with file secrets locally, promote to Swarm externals in production, and read everything from /run/secrets/ in your app bootstrap. If you want help wiring secrets into a Laravel stack, CI pipeline, or small Swarm cluster on Ubuntu, contact us or explore custom software development for a deployment review.

Frequently Asked Questions

Named blobs of sensitive data stored outside images, then mounted as read-only files at /run/secrets/ inside containers at runtime.

Yes. Compose file-based secrets work on a single Docker engine for local development. Production Swarm stack deploys require secrets to exist in the cluster before you run docker stack deploy.

Yes. Swarm encrypts secrets at rest in the Raft log and transmits them over TLS to workers. Inside containers they live on tmpfs, not encrypted separately—any process in that namespace can read the file.

Initialize a manager with docker swarm init, then create secrets from stdin or a file using docker secret create. Names are immutable; updates need a new name and service redeploy. List with docker secret ls, which shows names and IDs only—never values. Remove with docker secret rm only when no running service references the secret. Inspect metadata with docker secret inspect without revealing the payload. Prefer piping from stdin so credential files do not linger on disk.

Both mount as files under /run/. Configs hold non-sensitive data like nginx.conf. Secrets hold credentials, API keys, and TLS private keys. Swarm stores both in cluster state, but secrets get stricter handling and separate CLI commands. Never put passwords in configs—use the secrets block and read from /run/secrets/ in your application instead.

Compose file format 3.1 and later supports a top-level secrets key. For local dev, store files outside Git, add secrets/ to .gitignore, and map each secret with file: ./secrets/name.txt. Services declare secrets under their service block; Compose bind-mounts them at /run/secrets/. For Swarm, mark secrets as external: true and create them on the manager before docker stack deploy. Use Compose profiles and override files for per-developer paths instead of duplicating entire compose files.

Treat /run/secrets/ like any credential file: read once at boot, cache in memory, trim trailing newlines, and never log contents. In Laravel 13 on PHP 8.3 or higher, resolve the database password in config/database.php by checking file_exists on /run/secrets/db_password before falling back to env('DB_PASSWORD'). Do not bake passwords into php artisan config:cache from .env on production Swarm nodes—either cache after secrets mount or keep password resolution dynamic at runtime.

Environment variables appear in docker inspect output, process listings, and child shells. Bind mounts from host paths leave files on disk where backups and log scrapers can reach them. Docker secrets expose only the name in inspect output, use tmpfs backing in Swarm, and deliver encrypted transit between nodes. They suit DB passwords, API keys, and TLS keys. Reserve environment blocks for non-sensitive config flags like APP_ENV or feature toggles, not production credentials.

Pass --secret flags to docker service create or declare secrets in a Compose deploy block for docker stack deploy. Each flag accepts optional source and target to rename the in-container filename, plus mode for Unix permissions—default 0444. Example: attach db_password and rename api_key to payment_key with mode 0440. Only mount secrets each task actually needs; granting every service every secret violates least-privilege the same way over-broad RBAC does in Laravel applications.

Swarm does not support in-place secret edits. Create a new secret with a version suffix such as db_password_v2, update credentials on the database side, redeploy the service referencing the new secret, and confirm health checks pass during the rolling update. Remove the old secret only after every task has rolled forward. Databases with dual-password windows make this painless. For static payment gateway keys—eSewa, Khalti, Stripe—test callbacks in staging and schedule a maintenance window first.

Common failures include committing secrets/*.txt without .gitignore, using ARG in Dockerfiles where values persist in image history, assuming secrets rotate in place, attaching every secret to every service, and storing TLS keys while still serving plain HTTP. Run gitleaks in Git and CI on every pipeline. Document which secret names exist on the manager—confusing Compose file mode with Swarm external mode during dev-to-prod handoff causes silent missing mounts until the app crashes at startup.

Official database images expect environment variables, not secret files directly. Bridge the gap with a thin entrypoint wrapper that exports MYSQL_ROOT_PASSWORD from cat /run/secrets/db_password, then exec docker-entrypoint.sh mysqld. Keep the wrapper script in Git; keep secret values out. Mount the script via a custom image layer or a Swarm config object. The same pattern applies when PostgreSQL images need credentials injected from /run/secrets/ at container start.

Developers use file-based Compose secrets locally with sample values in docker-compose.override.yml. Production CI creates Swarm secrets over SSH on the manager—values created once per environment, never printed in build logs. GitLab CI masked variables can pipe into docker secret create during deploy jobs; CI only verifies secret names exist before deploy. On shared EC2 infrastructure with Deployer 7 and GitLab CI, this keeps credentials out of Git while releases symlink normally and Docker mounts secrets at runtime.

Swarm secrets solve single-cluster credential delivery, not issuance, audit, or dynamic rotation at scale. Pair them with HashiCorp Vault when you need those capabilities. External Secrets Operator targets Kubernetes, not Swarm, but the application read path stays similar: read a file, connect, discard. Swarm mode is declining in greenfield projects—many teams choose Kubernetes or single-node Compose with external vaults. Before migrating, export secret names, document every consumer reading /run/secrets/, and write rotation runbooks to avoid midnight outages.

Fail fast with a clear error—never fall back to an empty password or silent defaults. Validate file existence early in application boot or entrypoint scripts. If your app exits when credentials are absent, configure Compose health checks to run after the entrypoint reads secrets, with timing that avoids flapping during slow database restarts. Missing mounts often fail silently until the process crashes; documenting which services consume which secret names before handoff prevents that class of production outage.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: