Day 11: Local DevOps Stack: Integrating OpenLDAP, Jenkins, and Sonatype Nexus via Docker
Local DevOps Stack: Integrating OpenLDAP, Jenkins, and Sonatype Nexus via Docker
Modern DevOps engineering relies heavily on Centralized Identity and Access Management (IAM). Managing local, decoupled user accounts across a sprawling CI/CD toolchain introduces severe administrative overhead and security vulnerabilities.
This comprehensive technical guide details the end-to-end architecture, configuration templates, and operational theory required to construct a local, unified development stack featuring Jenkins, Sonatype Nexus Repository Manager 3, and OpenLDAP using Docker Compose.
1. Architectural Blueprint & Network Topology
To establish container-to-container communications without leaking traffic to the public internet, the stack utilizes a single, isolated virtual bridge network called dev-network. Within this layout, Docker’s built-in DNS engine resolves hostnames dynamically using two native routing pathways:
- Service Name Aliases (Container-to-Container): Services running inside the private network (such as Jenkins or Nexus) address the LDAP server directly using the service key defined in the Compose file (
openldap:389). - Container Name Aliases (Direct Instance): Specifying the
container_nameproperty (local-ldap) injects a secondary, absolute DNS modifier string pointing to the same internal IP layout (e.g., 172.18.0.2). - Host Port Forwarding (External Access): The host computer targets local loopback addresses (
localhost:389or127.0.0.1:389) via explicitportsmapping to tunnel external desktop management utilities straight into the containerized services.
┌──────────────────────────────────────────────┐
│ HOST MACHINE │
│ (Access via: http://localhost:8080, 8081) │
└──────────────────────┬───────────────────────┘
│
▼ [Port Forwarding]
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ DOCKER COMPOSE NETWORKING (dev-network Bridge) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ local-jenkins │ │ local-nexus │ │ local-ldap-admin │ │
│ │ (Jenkins Server) │ │ (Nexus Repository) │ │ (phpLDAPadmin UI) │ │
│ └──────────┬───────────┘ └──────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ │ [Target: openldap:389] │ [Target: openldap:389] │ [Target: openldap:389]
│ └─────────────────────────────┼─────────────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ local-ldap │ │
│ │ (OpenLDAP Server) │ │
│ └───────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────────────┘
2. Infrastructure Deployment (docker-compose.yml)
Save the following configuration as docker-compose.yml in an empty workspace directory. This file spins up OpenLDAP, a graphical phpLDAPadmin dashboard with security restrictions loosened for development bypass, Jenkins LTS, and Nexus 3.
version: '3.8'
services:
# 1. OpenLDAP Server
openldap:
image: osixia/openldap:1.5.0
container_name: local-ldap
environment:
- LDAP_ORGANISATION=LocalDev
- LDAP_DOMAIN=dev.local
- LDAP_ADMIN_PASSWORD=adminpassword
ports:
- "389:389"
- "636:636"
networks:
- dev-network
# 2. phpLDAPadmin (UI to manage directories)
phpldapadmin:
image: osixia/phpldapadmin:0.9.0
container_name: local-ldap-admin
environment:
- PHPLDAPADMIN_LDAP_HOSTS=openldap
- PHPLDAPADMIN_HTTPS=false # Bypasses 403 Forbidden constraints on localhost
ports:
- "8085:80"
- "443:443"
depends_on:
- openldap
networks:
- dev-network
# 3. Jenkins CI Server
jenkins:
image: jenkins/jenkins:lts
container_name: local-jenkins
user: root
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_data:/var/jenkins_home
depends_on:
- openldap
networks:
- dev-network
# 4. Sonatype Nexus Repository Manager
nexus:
image: sonatype/nexus3:latest
container_name: local-nexus
ports:
- "8081:8081"
volumes:
- nexus_data:/nexus-data
depends_on:
- openldap
networks:
- dev-network
networks:
dev-network:
driver: bridge
volumes:
jenkins_data:
nexus_data:
Initial Initialization Commands
Execute this command to launch the full environment in detached background mode:
docker compose up -d
To extract the system-generated bootstrapping administrative credentials, execute the following log queries:
# Retrieve Initial Jenkins Root Password
docker exec local-jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# Retrieve Initial Nexus Root Password
docker exec local-nexus cat /nexus-data/admin.password
3. Directory Services: Schema Theory & Data Injection
Parsing the Tree: Understanding LDAP Syntax
LDAP handles records backwards compared to regular systems, formatting information from right to left like an inverted folder structure:
- Domain Component (
dc): Represents the base disk formatting partition or network address blocks. The domain name configurationdev.localmaps inside LDAP asdc=dev,dc=local. - Organizational Unit (
ou): Structural directories (folders) utilized to segregate distinct record scopes (e.g.,ou=usersandou=groups). - Common Name (
cn) & User ID (uid): The actual leaf files residing within those target structural folders.
ObjectClass Inheritance Design
LDAP leverages object classes as structural blueprints to determine which data attributes a record is permitted (MAY) or mandated (MUST) to contain.
inetOrgPerson: The web standard for identifying personnel entries. Yields standard profile properties such asmail,cn(Full Name),sn(Surname), anduserPassword.posixAccount: Forces Unix operating system compatibility constraints. Requires numeric system coordinates includinguidNumber,gidNumber,homeDirectory, andloginShell.shadowAccount: Extends system layers by offering compliance attributes for tracking password aging, encryption constraints, and account locking rules.
Note: Schema rules are validated per-entry. If an operational change updates an existing objectClass definition by making an evaluation field mandatory (MUST), existing system records will instantly become schema-invalid if they reference that class without declaring the missing field value.
Clean-Slate Directory Setup Script (fresh_setup.ldif)
Create a local file named fresh_setup.ldif. This records package provisions structural organizational folders, generates 10 unique user records with cross-stacked object classes, and defines two core structural groups (admin and developers) utilizing standard POSIX short-username lookups.
# ==============================================================================
# 1. CREATE ORGANIZATIONAL UNITS
# ==============================================================================
dn: ou=users,dc=dev,dc=local
objectClass: organizationalUnit
ou: users
dn: ou=groups,dc=dev,dc=local
objectClass: organizationalUnit
ou: groups
# ==============================================================================
# 2. CREATE ADMIN USERS (USERS 1-5) -> Primary GID 5000
# ==============================================================================
dn: uid=user1,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user1
sn: One
cn: User One
mail: user1@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 5000
homeDirectory: /home/user1
dn: uid=user2,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user2
sn: Two
cn: User Two
mail: user2@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1002
gidNumber: 5000
homeDirectory: /home/user2
dn: uid=user3,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user3
sn: Three
cn: User Three
mail: user3@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1003
gidNumber: 5000
homeDirectory: /home/user3
dn: uid=user4,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user4
sn: Four
cn: User Four
mail: user4@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1004
gidNumber: 5000
homeDirectory: /home/user4
dn: uid=user5,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user5
sn: Five
cn: User Five
mail: user5@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1005
gidNumber: 5000
homeDirectory: /home/user5
# ==============================================================================
# 3. CREATE DEVELOPER USERS (USERS 6-10) -> Primary GID 5001
# ==============================================================================
dn: uid=user6,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user6
sn: Six
cn: User Six
mail: user6@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1006
gidNumber: 5001
homeDirectory: /home/user6
dn: uid=user7,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user7
sn: Seven
cn: User Seven
mail: user7@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1007
gidNumber: 5001
homeDirectory: /home/user7
dn: uid=user8,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user8
sn: Eight
cn: User Eight
mail: user8@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1008
gidNumber: 5001
homeDirectory: /home/user8
dn: uid=user9,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user9
sn: Nine
cn: User Nine
mail: user9@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1009
gidNumber: 5001
homeDirectory: /home/user9
dn: uid=user10,ou=users,dc=dev,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: user10
sn: Ten
cn: User Ten
mail: user10@dev.local
userPassword: password123
loginShell: /bin/bash
uidNumber: 1010
gidNumber: 5001
homeDirectory: /home/user10
# ==============================================================================
# 4. CREATE GROUPS AND DISTRIBUTE USERS (Short Username POSIX Mapping)
# ==============================================================================
dn: cn=admin,ou=groups,dc=dev,dc=local
objectClass: posixGroup
cn: admin
gidNumber: 5000
memberUid: user1
memberUid: user2
memberUid: user3
memberUid: user4
memberUid: user5
dn: cn=developers,ou=groups,dc=dev,dc=local
objectClass: posixGroup
cn: developers
gidNumber: 5001
memberUid: user6
memberUid: user7
memberUid: user8
memberUid: user9
memberUid: user10
To run the configuration file data injection, execute the command below in your shell terminal:
docker exec -i local-ldap ldapadd -x -D "cn=admin,dc=dev,dc=local" -w adminpassword < fresh_setup.ldif
4. Connectivity Verification Matrix
Because custom container operating system footprints strip basic debugging features like ping, network reachability must be confirmed using explicit network connection evaluations:
# Verify connection from inside the Jenkins instance to the LDAP listener
docker exec -it local-jenkins curl -v ldap://openldap:389
# Verify connection from inside the Nexus instance to the LDAP listener
docker exec -it local-nexus curl -v ldap://openldap:389
# Verify DNS resolution routing tables from inside Jenkins
docker exec -it local-jenkins getent hosts openldap
- Successful Network Output Indicators:
* Connected to openldap (172.xx.x.x) port 389 - Failure Network Output Indicators:
curl: (7) Failed to connect to openldap port 389: Connection refusedorCould not resolve host.
5. Jenkins Authentication & Authorization Configuration
Step 1: Security Realm Definition
- Navigate to your Jenkins local portal at
http://localhost:8080. - Access Manage Jenkins > Security.
- Locate Security Realm and check the LDAP radio selector box.
- Expand the Advanced Server Configuration tree. Populate the target entry fields with the parameters defined below:
| Target Configuration Input Box | Target Parameter Entry Mapping Value |
|---|---|
| Server | ldap://openldap:389 |
| root DN | dc=dev,dc=local |
| User search base | ou=users |
| User search filter | uid={0} |
| Group search base | ou=groups |
| Group search filter | cn={0} |
| Manager DN | cn=admin,dc=dev,dc=local |
| Manager Password | adminpassword |
- Click Test LDAP settings. Run an evaluation lookup using
user1alongside credential stringpassword123. Click Save.
Step 2: Group Matrix Role Assignment
- Install the Matrix Authorization Strategy plugin via your Plugin Manager panel.
- Return to Manage Jenkins > Security > Authorization.
- Check the Matrix-based security selection box.
- Click Add user or group…, input the value
admin, and confirm. - Click Add user or group…, input the value
developers, and confirm. - Check the target access matrices as detailed below:
- Group
admin: Select Administer (gives full operational controls across the environment). - Group
developers: Select Overall: Read, Job: Build, Job: Cancel, Job: Discover, Job: Read, and View: Read.
- Group
- Click Save.
6. Nexus Authentication & Authorization Configuration
Step 1: LDAP Data Connection Mapping
- Navigate to your Nexus repository portal at
http://localhost:8081and log in with your administrative account. - Select the top Administration Gear Icon > Security > LDAP.
- Select Create Connection and pick the Generic Ldap Server template configuration option. Populate the target tabs using the specifications detailed below:
Connection Parameters Tab
- Name:
Local LDAP - Protocol:
ldap - Host:
openldap - Port:
389 - Search base:
dc=dev,dc=local - Authentication method:
Simple Authentication - Username or DN:
cn=admin,dc=dev,dc=local - Password:
adminpassword
Click Verify Connection to confirm connectivity before moving to the next tab.
Users and Groups Specification Mapping Form
- User relative DN:
ou=users - Subtree: Unchecked (Disabled because records sit directly on the first sub-directory layer, optimizing performance)
- User object class:
inetOrgPerson - User filter: (Leave completely blank; avoid entering raw attribute identifier texts like “uid”)
- User ID attribute:
uid - Real name attribute:
cn - Email attribute:
mail - Group type:
Static Groups - Group relative DN:
ou=groups - Subtree: Unchecked
- Group object class:
posixGroup - Group ID attribute:
cn - Group member attribute:
memberUid - Group member format:
${username}(Crucial syntax mapping variable required to bind short username string lines against internal group arrays)
Click Verify user mapping, select user6 to run a live directory tree parser check, verify that group allocations read perfectly, and click Save.
Step 2: Authentication Realm Layer Activation
By default, Nexus maintains an internal configuration security scheme called a Realm Chain, evaluating requests sequentially down an active array block.
- Navigate to Security > Realms.
- Click LDAP Realm inside the Available box on the left, and click the arrow button to push it into the Active box on the right.
- Organize the alignment hierarchy so the LDAP Realm sits directly underneath the Local Authenticating Realm.
- Click Save.
Operational Check: If local database storage elements cannot identify a logging-in user, the verification request is passed to the LDAP backend wrapper automatically. If the identity container service crashes, emergency local admin access continues to function because it is verified first in the chain.
Step 3: Bridging External LDAP Groups to Local Target Roles
Nexus enforces explicit group linking requirements. Discovered LDAP groups will not inherit permissions until they are bound to a local role object.
- Navigate to Security > Roles > Create role > Select External Role Mapping.
- Select LDAP inside the Source selector box.
- Select
developersinside the Mapped Role dropdown list selection box. - Set a name description like
LDAP Developers Access. - Locate the Contained Roles table array, select
nx-deployment(or relevant repository push/pull permissions), and assign it into the Given column on the right side. - Click Create role.
- Repeat the steps above for the
admingroup, assigning the global configuration authority role blueprint:nx-admin.
7. Operational Resolution: POSIX vs. Full Distinguished Name (DN) Data Layouts
When integrating external microservices into an OpenLDAP core backend, engineers must choose between the Short Username (POSIX standard) and Full Distinguished Name (DN) mapping paradigms:
The Short Username Scheme (posixGroup)
Used in standard open architectures where records define unique structural keys via numeric bounds (uidNumber, gidNumber).
- Data Representation:
memberUid: user6 - Uniqueness Boundary: Username allocations must be globally unique across the system instance environment (e.g., you cannot have two separate accounts named
jsmithin a single system instance). If two distinct humans are named John Smith, the administrator forces a system conflict resolution directly into the ID naming string value itself (e.g.,jsmithvs.jsmith2).
The Full Distinguished Name Scheme (groupOfNames)
Standard practice across deep corporate environments (e.g., Microsoft Active Directory frameworks) where user structural scopes scale across overlapping geopolitical boundaries.
- Data Representation:
member: uid=user6,ou=users,dc=dev,dc=local - Uniqueness Boundary: Relies on the unique absolute folder path string. If two distinct humans named John Smith work inside separate offices, they are assigned identical system names but isolated by their folder paths (
uid=jsmith,ou=NY,dc=dev,dc=localvs.uid=jsmith,ou=London,dc=dev,dc=local). If they share the exact same location branch folder, structural uniqueness is enforced directly onto the naming attribute within that folder by modifying the string layout itself (e.g.,cn=John M. Smithvs.cn=John T. Smith).
Conclusion & Next Steps
Your unified dev infrastructure stack is now completely configured, validated, and integrated. Any user management modifications executed inside the local-ldap service container will reflect automatically across Jenkins pipelines and Nexus repository storage networks without manual tool reconfigurations.
Good next steps to build on this setup include:
- Repository Creation: Set up a hosted Maven, npm, or Docker repository inside Nexus.
- Jenkins Pipelines: Author a
Jenkinsfileutilizing thewithCredentialsplugin wrapper block to fetch build data dependencies using your new LDAP permissions matrix. - Data Persistence: Configure production storage drivers for your
jenkins_dataandnexus_dataDocker volume mount components to prevent data loss when scaling services up and down.
📁 Understanding LDAP: The File System Analogy
LDAP syntax can be confusing because it reads from right to left, exactly like an inverted file system path. Use this guide to demystify LDAP acronyms, formats, and configurations.
🗺️ Acronym & Path Analogy Breakdown
| LDAP Term | File System Equivalent | Description / Analogy | Your Local Setup Value |
|---|---|---|---|
dc (Domain Component) |
Hard Drive Partition | The root partition of the network (e.g., C:\ or /). |
dc=dev,dc=local (from dev.local) |
| Base DN (Distinguished Name) | Main Project Directory | The absolute full path where the tool starts searching. | dc=dev,dc=local |
Relative DN (ou / cn) |
Sub-folders | A path relative to the Base DN. ou = folder, cn = generic name. |
Users: ou=usersGroups: ou=groups |
Leaf Entry (uid / cn) |
Actual Files | The final records stored inside the sub-folders. | User: uid=user6Group: cn=developers |
| Search Filter | The Search Bar | Query syntax telling the tool how to look up a specific file. | Jenkins: uid={0}Nexus: (uid=username) |
🔍 User & Group Filters Explained
Filters act as search queries when a user attempts to log in:
- The User Filter (
uid={0}oruid): When a human types a username,{0}acts as a dynamic placeholder. The tool instructs LDAP to find a file where theuidattribute exactly matches the input text. - The Group Filter (
cn={0}orcn): Used when the CI/CD pipeline scans for group names (e.g., matching the group foldercn=developers).
👥 Group Member Format: The Two Models
The Group Member Format configuration dictates how a group’s membership attribute text is processed to authenticate a user.
Option 1: The “Short Username” Model (POSIX standard)
- Best Used For: Standard Linux/Unix focused OpenLDAP installations (
objectClass: posixGroup). - Database Record Look: Stored as raw, short login strings inside the
memberUidattribute.dn: cn=developers,ou=groups,dc=dev,dc=local objectClass: posixGroup memberUid: user6 memberUid: user7 - Nexus Configuration Value:
$username(or${username}). - Uniqueness Boundary: Usernames must be globally unique across the server instance (e.g., only one
jsmithcan exist).
Option 2: The “Full DN” Model (Enterprise standard)
- Best Used For: Massive networks or corporate systems like Microsoft Active Directory (
objectClass: groupOfNames). - Database Record Look: Requires the absolute, full folder path to the user file inside the
memberattribute.dn: cn=developers,ou=groups,dc=dev,dc=local objectClass: groupOfNames member: uid=user6,ou=users,dc=dev,dc=local member: uid=user7,ou=users,dc=dev,dc=local - Nexus Configuration Value:
uid=${username},ou=users,dc=dev,dc=local(or choose Full DN dropdown). - Uniqueness Boundary: Uniqueness is held at the folder path level. Multiple people can share the identity
jsmithas long as they live in separate geographical OUs (e.g.,ou=NYvsou=London).
💡 The Universal LDAP Cheat Sheet
No matter what tool you connect to this container in the future (GitLab, SonarQube, Grafana, etc.), use this mapping rulebook:
- Where is the root partition? ➡️
dc=dev,dc=local(Base DN) - Where are the user accounts? ➡️
ou=users(User Relative DN) - What attribute do they log in with? ➡️
uid(User ID Attribute / Filter) - Where are the organizational teams? ➡️
ou=groups(Group Relative DN) - What attribute names the teams? ➡️
cn(Group ID Attribute / Filter) - How do groups hold users? ➡️
memberUid(Group Member Attribute)