Docker Secrets

Deploy OpenLDAP with passwords loaded from Docker secrets instead of plaintext environment variables. Passwords never appear in docker inspect, process lists, or container metadata.

Project Files

openldap-secrets
Explorer
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# Single-node OpenLDAP.
#
# Usage:
#   cp .env.example .env    # optional - the defaults below work with no .env
#   docker compose up -d
#
# Compose reads .env automatically for the ${VAR:-default} interpolation here,
# so no env_file is required and the stack starts on a fresh clone.
services:
  openldap:
    image: ${LDAP_IMAGE:-vibhuvioio/openldap:latest}
    container_name: openldap
    restart: unless-stopped
    ports:
      - "389:389"
      - "636:636"
    environment:
      - LDAP_DOMAIN=${LDAP_DOMAIN:-example.com}
      - LDAP_ORGANIZATION=${LDAP_ORGANIZATION:-Example Organization}
      - LDAP_ADMIN_PASSWORD=${LDAP_ADMIN_PASSWORD:-changeme}
      - LDAP_CONFIG_PASSWORD=${LDAP_CONFIG_PASSWORD:-changeme}
      - ENABLE_REPLICATION=${ENABLE_REPLICATION:-false}
      - ENABLE_MONITORING=${ENABLE_MONITORING:-true}
      - ENABLE_MEMBEROF=${ENABLE_MEMBEROF:-false}
      - ENABLE_PASSWORD_POLICY=${ENABLE_PASSWORD_POLICY:-false}
      - ENABLE_AUDIT_LOG=${ENABLE_AUDIT_LOG:-false}
      - INCLUDE_SCHEMAS=${INCLUDE_SCHEMAS:-}
      - LDAP_LOG_LEVEL=${LDAP_LOG_LEVEL:-}
    volumes:
      - ldap-data:/var/lib/ldap
      - ldap-config:/etc/openldap/slapd.d
      - ldap-logs:/logs

    # Docker sends SIGTERM and then SIGKILL once stop_grace_period elapses. The
    # entrypoint waits up to 30s for slapd to drain, so the default 10s would
    # kill slapd mid-transaction and force a full database scan on next start.
    stop_grace_period: 30s

    # slapd uses one file descriptor per connection.
    ulimits:
      nofile:
        soft: 10240
        hard: 10240

    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH
      - DAC_OVERRIDE
      - NET_BIND_SERVICE
      - SETUID
      - SETGID
      - CHOWN

    healthcheck:
      test: ["CMD", "/usr/local/bin/scripts/healthcheck.sh", "auto"]
      interval: 30s
      timeout: 10s
      retries: 3
      # A cold start configures the database and waits for port 389 to be
      # released; a 30s start period marked a healthy container unhealthy while
      # it was still initialising.
      start_period: 90s

    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
        reservations:
          memory: 128M

    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

    networks:
      - ldap-network

volumes:
  ldap-data:
    driver: local
  ldap-config:
    driver: local
  ldap-logs:
    driver: local

networks:
  ldap-network:
    driver: bridge
YAMLUTF-8
Ln 951 files

Set image

export LDAP_IMAGE=vibhuvioio/openldap:latest

Create Secret Files

mkdir -p secrets
echo "YourSecureAdminPassword" > secrets/admin_password.txt
echo "YourSecureConfigPassword" > secrets/config_password.txt
Important: Add secrets/.txt to your .gitignore to avoid committing passwords.

Start

docker compose up -d

Verify Secrets Are Used

# Passwords should NOT appear in environment variables
docker exec openldap-secrets env | grep -i password
# Expected: LDAP_ADMIN_PASSWORD_FILE=/run/secrets/ldap_admin_password
# (not the actual password)

# Verify the secret file is mounted
docker exec openldap-secrets cat /run/secrets/ldap_admin_password

Test Authentication

ADMIN_PASS=$(cat secrets/admin_password.txt)

ldapsearch -x -H ldap://localhost:389 \
  -D "cn=Manager,dc=example,dc=com" \
  -w "$ADMIN_PASS" \
  -b "dc=example,dc=com" -s base

How It Works

The Docker Compose file uses the secrets directive:

services:
  openldap:
    environment:
      - LDAP_ADMIN_PASSWORD_FILE=/run/secrets/ldap_admin_password
      - LDAP_CONFIG_PASSWORD_FILE=/run/secrets/ldap_config_password
    secrets:
      - ldap_admin_password
      - ldap_config_password

secrets:
  ldap_admin_password:
    file: ./secrets/admin_password.txt
  ldap_config_password:
    file: ./secrets/config_password.txt

The startup.sh script detects _FILE variants and reads the password from the file at runtime:

if [ -n "$LDAP_ADMIN_PASSWORD_FILE" ] && [ -f "$LDAP_ADMIN_PASSWORD_FILE" ]; then
    LDAP_ADMIN_PASSWORD=$(cat "$LDAP_ADMIN_PASSWORD_FILE")
fi

Security Comparison

Plaintext Env VarDocker Secrets
Visible in docker inspectHidden from container metadata
In process list (ps e)Only in file readable by container
May be loggedNot logged by default
Risk of git commitCan be gitignored

Connection Details

SettingValue
Hostlocalhost
LDAP Port389
LDAPS Port636
Bind DNcn=Manager,dc=example,dc=com
Base DNdc=example,dc=com
PasswordContents of secrets/admin_password.txt

Production Recommendations

  1. 1 Never commit secrets to git — add secrets/.txt to .gitignore
  2. 2 Use proper secret managers — Docker Swarm (docker secret create), Kubernetes Sealed Secrets, or HashiCorp Vault
  3. 3 Rotate secrets regularly — update the file and restart the container
  4. 4 Set strict permissions — chmod 600 secrets/*.txt

Cleanup

docker compose down -v