MAATRIX / Блог / OpenLDAP в Docker Compose: готовый файл

OpenLDAP в Docker Compose: готовый файл

MAATRIX

Keycloak, Authentik и прочие модные identity-провайдеры в итоге почти всегда упираются в один и тот же вопрос: а где физически хранится список пользователей и групп? Часто ответ — в OpenLDAP, который тянется из инфраструктуры десятилетиями и продолжает работать, потому что LDAP как протокол не устарел и умеет то, что многим SSO-панелькам недоступно: прямую интеграцию с SSH, Samba, почтовиками и легаси-приложениями. Ниже — рабочий docker-compose.yml, LDIF для начального наполнения дерева и разбор, как подключить к этому каталогу реальные сервисы.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Зачем поднимать OpenLDAP в 2026 году

LDAP (Lightweight Directory Access Protocol) — не самый молодой протокол, но именно поэтому его понимают почти все: SSH через libnss-ldap/sssd, Samba File Server, почтовые серверы (Postfix, Dovecot), VPN-шлюзы, старые корпоративные CRM и ERP, сетевое оборудование с поддержкой LDAP-аутентификации. Нужен один каталог пользователей, который знают буквально все системы без плагинов и коннекторов — LDAP закрывает эту задачу лучше, чем что-либо ещё.

Отдельный сценарий — федерация. Keycloak, Authentik и Authelia умеют работать как фронт для LDAP: каталог остаётся источником правды (пользователи, группы, пароли), а поверх него — современный OIDC/SAML-слой для веб-приложений. Легаси-системы ходят напрямую в LDAP, современные веб-сервисы — через SSO-провайдер, синхронизированный с тем же деревом.

Ставить OpenLDAP имеет смысл, если у вас:

  • SSH-доступ на несколько серверов, который хочется централизовать (один пользователь — один пароль на всех машинах);
  • Samba или почтовый сервер, которым нужен именно LDAP-бэкенд;
  • legacy-приложение с готовой LDAP-интеграцией, которое проще подключить к каталогу, чем переписывать под OIDC;
  • Keycloak/Authentik уже стоят или планируются, и вы хотите разделить источник правды (LDAP) и витрину для веб-логина (SSO-провайдер).

Если задача — только SSO для десятка веб-приложений и легаси-систем нет, скорее всего хватит одного Keycloak или Authentik со встроенной базой пользователей, без отдельного LDAP-слоя.

Готовый docker-compose.yml

Ниже связка из самого OpenLDAP (образ osixia/openldap, один из самых обкатанных контейнеров для этой задачи) и веб-интерфейса phpLDAPadmin для правки дерева без консольных утилит. Оба сервиса слушают только на loopback хоста — снаружи каталог напрямую недоступен, доступ через VPN/туннель или reverse proxy со своей аутентификацией перед phpLDAPadmin.

services:
  openldap:
    image: osixia/openldap:1.5.0
    container_name: openldap
    restart: unless-stopped
    environment:
      LDAP_ORGANISATION: "Example Corp"
      LDAP_DOMAIN: "corp.example.com"
      LDAP_BASE_DN: "dc=corp,dc=example,dc=com"
      LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD}
      LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD}
      LDAP_READONLY_USER: "true"
      LDAP_READONLY_USER_USERNAME: "readonly"
      LDAP_READONLY_USER_PASSWORD: ${LDAP_READONLY_PASSWORD}
      LDAP_TLS: "true"
      LDAP_TLS_CRT_FILENAME: "ldap.crt"
      LDAP_TLS_KEY_FILENAME: "ldap.key"
      LDAP_TLS_CA_CRT_FILENAME: "ca.crt"
      LDAP_TLS_ENFORCE: "false"
      LDAP_TLS_VERIFY_CLIENT: "never"
      LDAP_REPLICATION: "false"
      LDAP_REMOVE_CONFIG_AFTER_SETUP: "true"
    volumes:
      - ldap_data:/var/lib/ldap
      - ldap_config:/etc/ldap/slapd.d
      - ./certs:/container/service/slapd/assets/certs:ro
      - ./seed:/container/service/slapd/assets/config/bootstrap/ldif/custom:ro
    ports:
      - "127.0.0.1:389:389"
      - "127.0.0.1:636:636"
    networks:
      - ldap_net

  phpldapadmin:
    image: osixia/phpldapadmin:0.9.0
    container_name: phpldapadmin
    restart: unless-stopped
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "openldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "127.0.0.1:8081:80"
    depends_on:
      - openldap
    networks:
      - ldap_net

networks:
  ldap_net:
    driver: bridge

volumes:
  ldap_data:
  ldap_config:

.env рядом с файлом:

LDAP_ADMIN_PASSWORD=замените_на_длинный_случайный_пароль
LDAP_CONFIG_PASSWORD=замените_на_другой_длинный_пароль
LDAP_READONLY_PASSWORD=замените_на_третий_пароль

Пароли сгенерировать проще всего так:

openssl rand -base64 24

Версию образа фиксируйте явно — osixia/openldap:1.5.0 держится стабильным тегом уже давно, но перед разворачиванием сверьтесь с актуальными тегами на Docker Hub: активная разработка у образа почти остановилась, поэтому альтернативой стоит держать в уме bitnami/openldap — там свежее база, но переменные окружения другие, конфиг придётся адаптировать.

Запуск:

mkdir -p certs seed
docker compose up -d
docker compose logs -f openldap

Первый старт создаёт базовое дерево из LDAP_BASE_DN, административного пользователя cn=admin,dc=corp,dc=example,dc=com и read-only учётку — её удобно использовать для сервисов, которым нужен только поиск (bind) без права на запись.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Инициализация дерева: организационные юниты и первые записи

Пустое дерево без структуры не особо полезно — нужны хотя бы ou=people и ou=groups. Файлы LDIF, положенные в ./seed, образ подхватывает автоматически при первом старте (при пустом volume ldap_data). Создайте seed/01-ou.ldif:

dn: ou=people,dc=corp,dc=example,dc=com
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=corp,dc=example,dc=com
objectClass: organizationalUnit
ou: groups

Дальше seed/02-users.ldif с первым пользователем и группой:

dn: uid=ivanov,ou=people,dc=corp,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: ivanov
sn: Ivanov
cn: Ivan Ivanov
uidNumber: 10001
gidNumber: 10001
userPassword: {SSHA}замените_на_хеш_пароля
loginShell: /bin/bash
homeDirectory: /home/ivanov
mail: ivanov@corp.example.com

dn: cn=developers,ou=groups,dc=corp,dc=example,dc=com
objectClass: posixGroup
gidNumber: 10001
memberUid: ivanov

Хеш пароля для userPassword — утилитой slappasswd внутри контейнера:

docker exec openldap slappasswd -s "пароль_пользователя"

Если LDIF-файлы кладутся уже после первого запуска (volume не пустой) — автозагрузка их не подхватит, применяйте вручную через ldapadd:

docker exec openldap ldapadd -x -D "cn=admin,dc=corp,dc=example,dc=com" \
  -w "$LDAP_ADMIN_PASSWORD" -f /container/service/slapd/assets/config/bootstrap/ldif/custom/02-users.ldif

Проверить, что запись появилась:

docker exec openldap ldapsearch -x -H ldap://localhost \
  -b "dc=corp,dc=example,dc=com" -D "cn=admin,dc=corp,dc=example,dc=com" -w "$LDAP_ADMIN_PASSWORD"

Для повседневной работы с деревом — заводить новых людей, менять группы — удобнее веб-интерфейс. phpLDAPadmin из compose-файла выше доступен на http://127.0.0.1:8081 (проброшен только на loopback, снаружи сервера не виден); логин — тот же cn=admin,dc=corp,dc=example,dc=com с паролем администратора.

TLS: без него передавать пароли по LDAP небезопасно

По умолчанию LDAP гоняет запросы в открытом виде — включая bind-запросы с паролями. Даже если каталог стоит за файрволом, использовать LDAPS (порт 636) или STARTTLS на 389-м порту — базовая гигиена, особенно если клиенты подключаются не только из соседнего контейнера, а с других серверов инфраструктуры.

Самый простой путь — свой CA и самоподписанный сертификат для внутреннего использования:

mkdir -p certs && cd certs
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650 -nodes \
  -subj "/CN=Internal LDAP CA"
openssl req -newkey rsa:4096 -keyout ldap.key -out ldap.csr -nodes \
  -subj "/CN=openldap.corp.example.com"
openssl x509 -req -in ldap.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out ldap.crt -days 825

Три файла (ca.crt, ldap.crt, ldap.key) попадают в volume ./certs, уже смонтированный в compose-файле выше — переменные LDAP_TLS_CRT_FILENAME и подобные подхватят их при следующем перезапуске. Клиентам, подключающимся извне, нужно будет доверять ca.crt — либо в системном хранилище сертификатов, либо явным путём в конфиге самого клиента.

Если каталог смотрит только внутрь Docker-сети — можно ограничиться LDAP_TLS_ENFORCE: "false" и держать 389 без шифрования, полагаясь на изоляцию сети. Но как только появляется хотя бы один клиент за пределами хоста (второй сервер, SSH через LDAP на других машинах) — переключайте на обязательный TLS и закрывайте 389-й порт наружу совсем.

Подключение приложений: SSH, Nextcloud, Keycloak

SSH-логин через LDAP. На клиентских серверах ставится libnss-ldap и libpam-ldap (или современнее — sssd с модулем ldap). Конфиг /etc/sssd/sssd.conf в упрощённом виде:

[sssd]
services = nss, pam
domains = corp.example.com

[domain/corp.example.com]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://openldap.corp.example.com:636
ldap_search_base = dc=corp,dc=example,dc=com
ldap_tls_cacert = /etc/ssl/certs/ldap-ca.crt
cache_credentials = true

cache_credentials = true критичен — без него при недоступном LDAP-сервере никто не залогинится по SSH ни на один сервер сразу, а единая точка аутентификации без кеша превращается в единую точку отказа.

Nextcloud. В приложении «LDAP/AD integration» (входит в стандартную поставку, нужно только включить) указываются хост openldap (или внешний адрес с TLS), Base DN и Bind DN — можно использовать read-only учётку из переменных окружения выше, вместо admin-пароля.

Keycloak/Authentik как федерация поверх LDAP. В Keycloak это User FederationAdd providerldap, где указывается тот же Base DN, Bind DN и Edit mode: READ_ONLY, если LDAP остаётся источником правды. Подробно про сам Keycloak — в статье Keycloak в Docker Compose; про более лёгкую альтернативу — Authentik в Docker Compose.

Общий принцип для любого приложения: если оно поддерживает LDAP «из коробки», настройка сводится к четырём параметрам — адрес сервера, Base DN, Bind DN/пароль для поиска и атрибут, по которому матчится логин (обычно uid или sAMAccountName для совместимости с AD-подобными схемами).

Резервное копирование дерева

Данные каталога — это не файлы на диске в привычном смысле, а записи в BerkeleyDB/MDB-хранилище внутри /var/lib/ldap. Бэкапить сырые файлы можно, но переносимый и предсказуемый способ — экспорт через slapcat:

docker exec openldap slapcat -n 1 > ldap_backup_$(date +%F).ldif

Восстановление на чистом инстансе (том должен быть пустым, сервис — остановлен):

docker compose stop openldap
docker run --rm -v ldap_data:/var/lib/ldap -v $(pwd):/backup osixia/openldap:1.5.0 \
  slapadd -n 1 -F /etc/ldap/slapd.d -l /backup/ldap_backup_2026-08-20.ldif
docker compose start openldap

Для регулярного бэкапа проще всего повесить slapcat на cron хоста и складывать дампы за пределы этого же сервера — на S3-совместимое хранилище или второй сервер. Общая методика бэкапа docker-volume разобрана в статье про бэкап Docker volume на VPS. LDIF-файл текстовый и версионируемый — удобно хранить ещё и в git (хеши {SSHA} можно оставить, а вот TLS-ключ в репозиторий класть не стоит).

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужен ли под OpenLDAP отдельный сервер?

Процесс slapd лёгкий — десятки-сотни МБ RAM на каталог из нескольких сотен пользователей, отдельная машина не нужна. Важнее изоляция сети: держите LDAP-порты на loopback или во внутренней docker-сети, если на хосте есть публичные веб-сервисы.

Чем OpenLDAP отличается от Keycloak/Authentik?

OpenLDAP — каталог (хранилище пользователей и групп по LDAP-протоколу), Keycloak/Authentik — identity-провайдеры с современным SSO-слоем (OIDC/SAML) поверх какого-то хранилища. Они не взаимоисключающие: LDAP часто остаётся источником правды, а Keycloak или Authentik подключаются к нему как к внешнему провайдеру пользователей.

Как перенести пользователей из Active Directory?

Через экспорт из AD в LDIF (например, ldifde на Windows-сервере) с адаптацией схемы — атрибуты и objectClass у AD и стандартного OpenLDAP различаются (sAMAccountName вместо uid, другая сериализация паролей). Отдельная миграционная задача с обязательным тестовым прогоном.

Безопасно ли открывать порт 389/636 в интернет?

По возможности — нет. Даже с TLS на 636-м LDAP плохо приспособлен для лицом-к-интернету: нет встроенной защиты от перебора паролей. Доступ — только через VPN/WireGuard-туннель или из доверенной внутренней сети.

Как обновить контейнер без потери данных?

Данные лежат в volume ldap_data, а не в образе, поэтому docker compose pull openldap && docker compose up -d openldap безопасен для минорных апдейтов. Перед мажорным скачком версии сделайте slapcat-бэкап и проверьте на копии — формат хранения между релизами менялся.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →