Rootless Docker를 선택한 이유
서버에서 Docker를 운영하는 방법은 크게 두 가지입니다.
| 방식 | root 권한 | 포트 1024 미만 | 보안 |
|---|---|---|---|
| 일반 Docker (root 모드) | 필요 | 바인딩 가능 | 컨테이너 탈출 시 호스트 root 탈취 위험 |
| Rootless Docker | 불필요 | 바인딩 불가 | 컨테이너 탈출해도 일반 사용자 권한만 |
Rootless Docker는 Docker 데몬 자체를 일반 사용자 권한으로 실행합니다.
컨테이너 프로세스가 호스트 OS에 미치는 영향이 제한되므로 보안이 더 강합니다.
단, 1024 미만 포트를 바인딩할 수 없다는 제약이 있습니다.
LDAP 표준 포트는 389(LDAP)와 636(LDAPS)이지만, Rootless 환경에서는 이 포트를 쓸 수 없습니다.
그래서 10389 / 10636을 사용합니다. 내부 서비스 연동이므로 포트 번호는 맞춰서 설정하면 됩니다.
디렉토리 구조 설계
설치 전에 파일과 데이터가 어디에 놓일지 먼저 정합니다.
/app/openldap/ ← 설정·스크립트 (docker-compose.yml, .env, ldif 등)
/data/openldap/ ← LDAP DB (MDB 데이터베이스)
/data/ldap-config/ ← slapd 설정 (cn=config OLC)
/data/ldap-certs/ ← TLS 인증서 (ca.crt, ldap.crt, ldap.key)
/log/openldap/ ← slapd 로그
/data/backup/ldap/ ← 백업 (daily / weekly / monthly)
/app은 애플리케이션 설정, /data는 영구 데이터, /log는 로그로 역할을 분리했습니다.
컨테이너를 지우고 다시 올려도 /data와 /log 아래 데이터는 그대로 남습니다.
# 디렉토리 생성
mkdir -p /data/openldap /data/ldap-config /data/ldap-certs
mkdir -p /log/openldap /log/phpldapadmin
mkdir -p /data/backup/ldap
mkdir -p /app/openldap
.env 파일 작성
인증 정보와 경로를 docker-compose.yml에 하드코딩하지 않고 .env로 분리합니다.
git에 올릴 때 .env를 .gitignore에 추가하는 것을 잊지 마세요.
# /app/openldap/.env
# 도메인 설정
LDAP_ORGANISATION=YourOrganization
LDAP_DOMAIN=ldap.example.com
LDAP_BASE_DN=dc=ldap,dc=example,dc=com
# 관리자 비밀번호 — 강력한 값으로 변경 필수
LDAP_ADMIN_PASSWORD=change_this_strong_password
LDAP_CONFIG_PASSWORD=change_this_strong_password
LDAP_READONLY_USER_PASSWORD=change_this_readonly_password
# TLS 설정
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
# 로그 레벨 (256 = stats, 0 = 없음)
LDAP_LOG_LEVEL=256
# 포트 (Rootless Docker: 1024 이상 필수)
LDAP_PORT=10389
LDAPS_PORT=10636
PHPLDAPADMIN_PORT=8080
# 데이터 경로
DATA_PATH=/data
LOG_PATH=/log
# phpLDAPadmin 설정
PHPLDAPADMIN_HTTPS=false
PHPLDAPADMIN_LDAP_HOSTS=openldap
docker-compose.yml 작성
version: '3.8'
services:
openldap:
image: osixia/openldap:1.5.0
container_name: openldap
hostname: openldap
restart: unless-stopped
environment:
- LDAP_ORGANISATION=${LDAP_ORGANISATION}
- LDAP_DOMAIN=${LDAP_DOMAIN}
- LDAP_BASE_DN=${LDAP_BASE_DN}
- 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_USER_PASSWORD}
- LDAP_TLS=${LDAP_TLS}
- LDAP_TLS_CRT_FILENAME=${LDAP_TLS_CRT_FILENAME}
- LDAP_TLS_KEY_FILENAME=${LDAP_TLS_KEY_FILENAME}
- LDAP_TLS_CA_CRT_FILENAME=${LDAP_TLS_CA_CRT_FILENAME}
- LDAP_TLS_ENFORCE=${LDAP_TLS_ENFORCE}
- LDAP_TLS_VERIFY_CLIENT=${LDAP_TLS_VERIFY_CLIENT}
- LDAP_LOG_LEVEL=${LDAP_LOG_LEVEL}
ports:
- "${LDAP_PORT}:389"
- "${LDAPS_PORT}:636"
volumes:
- ${DATA_PATH}/openldap:/var/lib/ldap
- ${DATA_PATH}/ldap-config:/etc/ldap/slapd.d
- ${DATA_PATH}/ldap-certs:/container/service/slapd/assets/certs
- ${LOG_PATH}/openldap:/var/log/slapd
- ./ldap-init.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-init.ldif
command: --copy-service --loglevel debug
networks:
- ldap-network
phpldapadmin:
image: osixia/phpldapadmin:0.9.0
container_name: phpldapadmin
hostname: phpldapadmin
restart: unless-stopped
environment:
- PHPLDAPADMIN_LDAP_HOSTS=${PHPLDAPADMIN_LDAP_HOSTS}
- PHPLDAPADMIN_HTTPS=${PHPLDAPADMIN_HTTPS}
ports:
- "${PHPLDAPADMIN_PORT}:80"
volumes:
- ${DATA_PATH}/phpldapadmin:/var/www/phpldapadmin
- ${LOG_PATH}/phpldapadmin:/var/log/nginx
depends_on:
- openldap
networks:
- ldap-network
networks:
ldap-network:
driver: bridge
name: ldap-network
주요 설정 설명
restart: unless-stopped
수동으로 멈추지 않는 한 컨테이너가 항상 재시작됩니다. systemd와 함께 쓸 때 유용합니다.
--copy-service 커맨드osixia/openldap 이미지는 초기화 스크립트가 /container/service/ 아래에 있습니다.--copy-service를 붙이면 이 스크립트를 컨테이너 내부로 복사해서 볼륨 마운트와 충돌 없이 실행됩니다.
50-init.ldif 마운트
컨테이너가 처음 시작될 때 자동으로 로드할 LDIF 파일을 지정합니다.
OU 구조, 기본 그룹 같은 초기 데이터를 여기에 넣어두면 컨테이너 재생성 시 자동으로 반영됩니다.
파일 이름의 숫자(50-)는 로드 순서를 제어합니다. 기본 스크립트들이 먼저 실행된 후 로드됩니다.
ldap-network
phpLDAPadmin과 openldap이 같은 Docker 네트워크에 있어야 컨테이너 이름(openldap)으로 내부 통신이 됩니다.
phpLDAPadmin의 PHPLDAPADMIN_LDAP_HOSTS=openldap 설정이 이 네트워크 이름을 사용합니다.
ldap-init.ldif: 초기 OU 구조 정의
컨테이너가 처음 뜰 때 자동으로 생성할 OU와 기본 엔트리를 정의합니다.
# OU: 사용자
dn: ou=people,dc=ldap,dc=example,dc=com
objectClass: organizationalUnit
ou: people
description: User accounts
# OU: 그룹
dn: ou=Groups,dc=ldap,dc=example,dc=com
objectClass: organizationalUnit
ou: Groups
description: Groups
# OU: 서비스 계정
dn: ou=services,dc=ldap,dc=example,dc=com
objectClass: organizationalUnit
ou: services
description: Service accounts
주의: 이 파일은 컨테이너가 처음 초기화될 때 한 번만 실행됩니다. 이미
/data/openldap에 데이터가 있으면 재실행하지 않습니다. 초기화를 다시 하려면/data/openldap와/data/ldap-config를 비우고 컨테이너를 재생성해야 합니다.
서비스 시작
cd /app/openldap
# 처음 시작
docker compose up -d
# 로그 확인
docker compose logs -f openldap
정상 시작 로그에는 이런 메시지가 나옵니다.
slapd starting
동작 확인
# LDAP 연결 테스트 (익명 바인드)
docker exec openldap ldapsearch -x \
-H ldap://localhost:389 \
-s base -b "" "(objectClass=*)" namingContexts
# 관리자로 OU 조회
docker exec openldap ldapsearch -x \
-H ldap://localhost:389 \
-D "cn=admin,dc=ldap,dc=example,dc=com" \
-w 'your_admin_password' \
-b "dc=ldap,dc=example,dc=com" \
"(objectClass=organizationalUnit)" dn
systemd user 서비스 등록
Docker Compose를 수동으로 up하면 서버가 재시작될 때 LDAP도 같이 내려갑니다.
systemd user 서비스로 등록하면 로그인 없이도 부팅 시 자동으로 올라옵니다.
서비스 파일 작성
mkdir -p ~/.config/systemd/user
# ~/.config/systemd/user/openldap.service
[Unit]
Description=OpenLDAP Docker Service
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/app/openldap
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=30
[Install]
WantedBy=default.target
서비스 등록 및 활성화
# systemd 데몬 재로드
systemctl --user daemon-reload
# 부팅 시 자동 시작 등록
systemctl --user enable openldap.service
# 지금 시작
systemctl --user start openldap.service
# 상태 확인
systemctl --user status openldap.service
Linger 설정 (로그인 없이 자동 실행)
systemd user 서비스는 기본적으로 해당 사용자가 로그인해야 시작됩니다.
서버 재시작 후 로그인 없이 LDAP이 올라오려면 Linger를 활성화해야 합니다.
# 현재 사용자에 대해 Linger 활성화
sudo loginctl enable-linger $USER
# 확인
loginctl show-user $USER | grep Linger
# Linger=yes
관리 스크립트 (ldapserver.sh)
매번 systemctl --user 명령을 치는 게 번거롭다면 래퍼 스크립트 하나로 묶어두면 편합니다.
#!/bin/bash
# ldapserver.sh — OpenLDAP 서비스 관리
SERVICE="openldap.service"
COMPOSE_DIR="$(cd "$(dirname "$0")" && pwd)"
case "$1" in
start)
systemctl --user start $SERVICE
;;
stop)
systemctl --user stop $SERVICE
;;
restart)
systemctl --user restart $SERVICE
;;
status)
systemctl --user status $SERVICE --no-pager
docker compose -f $COMPOSE_DIR/docker-compose.yml ps
;;
logs)
docker compose -f $COMPOSE_DIR/docker-compose.yml logs --tail=${2:-50}
;;
health)
docker inspect --format='{{.State.Status}}' openldap
docker stats --no-stream openldap
;;
esac
chmod +x ldapserver.sh
./ldapserver.sh start
./ldapserver.sh status
./ldapserver.sh logs 100
여기까지의 상태
이 편을 마치면 다음 상태입니다.
- OpenLDAP 컨테이너가 10389(LDAP) / 10636(LDAPS) 포트로 실행 중
- phpLDAPadmin이 8080 포트에서 접속 가능
- 서버 재시작 후 자동으로 올라오는 systemd user 서비스 등록 완료
ou=people,ou=Groups,ou=servicesOU 구조 초기화 완료
다음 편에서는 LDAPS 통신에 필요한 TLS 인증서를 직접 발급하고 OpenLDAP에 적용하는 방법을 다룹니다.