diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3e4e359
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,43 @@
+# from https://github.com/alexkaratarakis/gitattributes/blob/master/Java.gitattributes
+
+# Handle line endings automatically for files detected as text
+# and leave all files detected as binary untouched.
+* text=auto
+
+#
+# The above will handle all files NOT found below
+#
+# These files are text and should be normalized (Convert crlf => lf)
+*.css text
+*.df text
+*.htm text
+*.html text
+*.java text
+*.js text
+*.json text
+*.jsp text
+*.jspf text
+*.jspx text
+*.properties text
+*.sh text
+*.tld text
+*.txt text
+*.tag text
+*.tagx text
+*.xml text
+*.yml text
+
+# These files are binary and should be left untouched
+# (binary is a macro for -text -diff)
+*.class binary
+*.dll binary
+*.ear binary
+*.gif binary
+*.ico binary
+*.jar binary
+*.jpg binary
+*.jpeg binary
+*.png binary
+*.so binary
+*.war binary
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e5f50ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/.project
+/.settings
+/.svn
+**/.*.md.html
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..ebe8e47
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,39 @@
+# https://github.com/travis-ci/travis-yml/blob/master/schema.json
+language: bash # using 'bash' instead of 'minimal' to prevent https://travis-ci.community/t/job-stuck-booting/4976
+
+sudo: true
+
+services:
+ - docker
+
+cache:
+ directories:
+ - $HOME/.trivy/cache
+
+env:
+ global:
+ DOCKER_REGISTRY=docker.io
+ DOCKER_REPO=vegardit/openldap
+ TRIVY_CACHE_DIR=$HOME/.trivy/cache
+
+
+before_script:
+ # workaround for
+ # - https://stackoverflow.com/questions/34130613/how-to-make-travis-ci-work-with-a-local-git-branch-instead-of-a-specific-commit
+ # - https://unhashable.com/getting-the-current-branch-name-during-a-pull-request-in-travis-ci/
+ - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
+ export GIT_BRANCH="$TRAVIS_BRANCH";
+ else
+ export GIT_BRANCH="$TRAVIS_PULL_REQUEST_BRANCH";
+ fi
+ #- printenv | sort
+ - mkdir -p "$TRIVY_CACHE_DIR"
+
+script:
+ - echo "$DOCKER_PASSWORD" | docker login -u="$DOCKER_USERNAME" "$DOCKER_REGISTRY" --password-stdin &&
+ if [[ "$TRAVIS_BRANCH" == "master" ]]; then
+ DOCKER_PUSH=1 bash ./build-image.sh;
+ else
+ bash ./build-image.sh;
+ fi
+ - ls -l $TRIVY_CACHE_DIR/trivy
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2fc2fbc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,7 @@
+# Contributing to docker-openldap
+
+Thanks for contributing to docker-openldap!
+
+## License
+
+By contributing your code, you agree to license your contribution under the [Apache License 2.0](LICENSE.txt).
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 261eeb9..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
index 9dfdda9..8b5da68 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,199 @@
-# docker-openldap
-Opinionated docker image based on minideb (Debian 10 "buster") to run an OpenLDAP 2.4 server.
+# vegardit/openldap
+
+[](https://travis-ci.com/vegardit/docker-openldap)
+[](#license)
+[](https://hub.docker.com/r/vegardit/openldap)
+[](https://hub.docker.com/r/vegardit/openldap)
+[](https://hub.docker.com/r/vegardit/openldap)
+
+1. [What is it?](#what-is-it)
+1. [Configuration](#config)
+ 1. [Initial configuration](#initial-config)
+ 1. [Customizing the Password Policy](#ppolicy)
+ 1. [Changing UID/GID of OpenLDAP service user](#uidgid)
+ 1. [Periodic LDAP Backup](#backup)
+ 1. [Synchronizing timezone/time with docker host](#timesync)
+ 1. [Performance tuning](#performance-tuning)
+ 1. [Troubleshooting](#troubleshooting)
+1. [References](#references)
+1. [License](#license)
+
+
+## What is it?
+
+Opinionated docker image based on [minideb](https://github.com/bitnami/minideb) (Debian 10 "buster") to run an [OpenLDAP 2.4](https://www.openldap.org/doc/admin24/) server.
+
+To keep the image light and simple, it does not configure TLS. Instead we recommend configuring a [Traefik 2.x](https://traefik.io) [TCP service](https://docs.traefik.io/routing/services/#configuring-tcp-services) with e.g. an auto-renewing [Let's Encrypt configuration](https://docs.traefik.io/https/acme/) in front of the OpenLDAP service.
+
+
+## Configuration
+
+### Initial configuration
+
+Various parts of the LDAP server can be configured via environment variables. All environment variables starting with `LDAP_INIT_`
+are only evaluated on the **first** container launch. Changing their values later has no effect when restarting or updating the container.
+
+To customize the initial configuration you can set the following environment variables:
+
+```sh
+LDAP_INIT_ORG_DN='dc=example,dc=com'
+LDAP_INIT_ORG_NAME='Example Corporation'
+LDAP_INIT_ADMIN_GROUP_DN='cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN}'
+LDAP_INIT_ROOT_USER_DN='uid=admin,${LDAP_INIT_ORG_DN}'
+LDAP_INIT_ROOT_USER_PW='changeit'
+LDAP_INIT_RFC2307BIS_SCHEMA=0 # 0=use NIS (RFC2307) schema, 1=use RFC2307bis schema
+```
+
+Environment variables can for example be set using `docker run` with `-e`, e.g.
+
+```sh
+docker run -itd \
+ -e LDAP_INIT_ORG_DN='o=yourorg' \
+ -e LDAP_INIT_ROOT_USER_PW='newpassword' \
+ -e LDAP_INIT_ORG_NAME='Company Inc' \
+ -e LDAP_INIT_PPOLICY_PW_MIN_LENGTH='12' \
+ vegardit/openldap
+```
+
+Alternatively you can use an [env-file](https://docs.docker.com/compose/env-file/) to store all changed variables and use the option `--env-file` with `docker run`, e.g.:
+
+```sh
+docker run -itd --env-file environment vegardit/openldap
+```
+
+In environment file values must not be enclosed using quotes (`'` or `"`), please remove them. See this example file: [example/docker/example.env].
+
+The initial LDAP tree structure is imported from [/opt/ldifs/init_org_tree.ldif](ldifs/init_org_tree.ldif).
+You can mount a custom file at `/opt/ldifs/init_org_tree.ldif` if you require changes.
+
+LDAP entries (users, groups) are imported from [/opt/ldifs/init_org_entries.ldif](ldifs/init_org_entries.ldif).
+You can mount a custom file at `/opt/ldifs/init_org_entries.ldif` if you require changes.
+
+### Customizing the Password Policy
+
+On **initial** container launch, the [password policy](https://www.openldap.org/doc/admin24/overlays.html#Password%20Policies) is imported from [/opt/ldifs/init_org_ppolicy.ldif](ldifs/init_org_ppolicy.ldif)
+
+The following parameters can be modified via environment variables **before** initial container launch:
+
+```sh
+LDAP_INIT_PPOLICY_DEFAULT_DN='cn=DefaultPasswordPolicy,ou=Policies,${LDAP_INIT_ORG_DN}'
+LDAP_INIT_PPOLICY_PW_MIN_LENGTH=8
+LDAP_INIT_PPOLICY_MAX_FAILURES=3
+LDAP_INIT_PPOLICY_LOCKOUT_DURATION=300
+```
+
+If more customizations are required, simply mount a custom policy file at `/opt/ldifs/init_org_ppolicy.ldif` **before** initial container launch.
+
+**Password Quality Checker:**
+
+[pqChecker](https://www.meddeb.net/pqchecker/) is configured as default password quality checker using the rule `0|01010101` with
+the following meaning:
+
+|Pos. |Value |Effective Rule
+|----:|:-----:|:----------
+|0-1 | `0\|`|Don't broadcast passwords.
+|2-4 | `01` |Minimum 1 uppercase character.
+|5-6 | `01` |Minimum 1 lowercase character.
+|7-8 | `01` |Minimum 1 digit.
+|9-10 | `01` |Minimum 1 special character.
+|11-..| empty | No characters are disallowed in passwords.
+
+The pqChecker rule syntax is explained here in more detail: https://www.meddeb.net/pqchecker/?Idx=2
+
+A custom rule can be provided via an environment variable, e.g.:
+
+```sh
+LDAP_PPOLICY_PQCHECKER_RULE='0|01020101@!+-#'
+```
+
+### Changing UID/GID of OpenLDAP service user
+
+The UID/GID of the user running the OpenLDAP service can be aligned with the docker host, using the environment variables
+`LDAP_OPENLDAP_UID` and `LDAP_OPENLDAP_GID`.
+
+During each container start it is verified that the given UID/GID matches the currently effective UID/GID. If not, the UID/GID
+of the `openldap` user are changed accordingly and `chown` on `/etc/ldap` and `/var/lib/slapd` is executed before the OpenLDAP service is started.
+
+### Periodic LDAP Backup
+
+This image automatically generates a daily LDIF export at `2 a.m.` to `/var/lib/ldap/data.ldif`.
+
+The following environment variables can be used to configure the automatic LDAP backup:
+```bash
+LDAP_BACKUP_TIME='02:00' # Format is "HH:MM", i.e. 24-hour format with minute precision
+LDAP_BACKUP_FILE='/var/lib/ldap/data.ldif'
+```
+
+To disable automatic backup set an empty value for the environment variable `LDAP_BACKUP_TIME`.
+
+### Synchronizing timezone/time with docker host
+
+To use the same timezone and/or time of the docker host you can run the docker image with `--volume /etc/localtime:/etc/localtime:ro --volume /etc/timezone:/etc/timezone:ro`
+
+Docker compose file example:
+```yaml
+version: '3.7'
+services:
+ openldap:
+ image: vegardit/openldap:latest
+ volumes:
+ - /etc/localtime:/etc/localtime:ro
+ - /etc/timezone:/etc/timezone:ro
+```
+
+### Performance tuning
+
+#### DB Indexes
+
+The database indexes that are configured during initial container launch are imported from [/opt/ldifs/init_backend_indexes.ldif](ldifs/init_mdb_indexes.ldif)
+
+To use other indexes, simply mount a custom file at `/opt/ldifs/init_backend_indexes.ldif` **before** initial container launch.
+
+#### Memory usage
+
+The maximum number of open files is set to `1024` by default to prevent excessive RAM consumption as reported [here](https://github.com/docker/docker/issues/8231).
+
+The following environment variable can be used to increase this limit:
+
+```sh
+LDAP_NOFILE_LIMIT=2048
+```
+
+### Troubleshooting
+
+The slapd service logs to stdout. You can change the active log levels by setting this environment variable:
+
+```sh
+LDAP_LOG_LEVELS='Config Stats'
+```
+
+The following [log levels](https://www.openldap.org/doc/admin24/slapdconfig.html#loglevel%20%3Clevel%3E) are available:
+```
+Any (-1) enable all debugging
+Trace (1) trace function calls
+Packets (2) debug packet handling
+Args (4) heavy trace debugging
+Conns (8) connection management
+BER (16) print out packets sent and received
+Filter (32) search filter processing
+Config (64) configuration processing
+ACL (128) access control list processing
+Stats (256) stats log connections/operations/results
+Stats2 (512) stats log entries sent
+Shell (1024) print communication with shell backends
+Parse (2048) print entry parsing debugging
+Sync (16384) syncrepl consumer processing
+None (32768) only messages that get logged whatever log level is set
+```
+
+
+## References
+
+- OpenLDAP Software 2.4 Administrator's Guide https://www.openldap.org/doc/admin24/guide.html
+- OpenLDAP Online Configuration Reference https://tylersguides.com/guides/openldap-online-configuration-reference/
+- slapd-config(5) - Linux man page https://linux.die.net/man/5/slapd-config
+
+
+## License
+
+All files in this repository are released under the [Apache License 2.0](LICENSE.txt).
diff --git a/build-debug-image.sh b/build-debug-image.sh
new file mode 100644
index 0000000..2255e72
--- /dev/null
+++ b/build-debug-image.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+#
+# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com
+# SPDX-License-Identifier: Apache-2.0
+#
+# @author Sebastian Thomschke, Vegard IT GmbH
+#
+# https://github.com/vegardit/docker-openldap
+#
+
+bash "$(dirname "$0")/build-image.sh" --build-arg DEBUG_BUILD=1
diff --git a/build-image.sh b/build-image.sh
new file mode 100644
index 0000000..219c66c
--- /dev/null
+++ b/build-image.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+#
+# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com
+# SPDX-License-Identifier: Apache-2.0
+#
+# @author Sebastian Thomschke, Vegard IT GmbH
+#
+# https://github.com/vegardit/docker-openldap
+#
+
+set -e -x
+if [ ! -n "$BASH" ]; then /usr/bin/env bash "$0" "$@"; exit; fi
+
+DOCKER_REGISTRY=${DOCKER_REGISTRY:-docker.io}
+DOCKER_REPO=${DOCKER_REPO:-vegardit/openldap}
+
+last_commit_date=$(date -d @$(git log -1 --format="%at") --utc +"%Y%m%d_%H%M%S")
+
+docker build $(dirname $0)/image \
+ --compress \
+ --build-arg GIT_BRANCH="${GIT_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" \
+ --build-arg GIT_COMMIT_DATE="$(date -d @$(git log -1 --format='%at') --utc +'%Y-%m-%d %H:%M:%S UTC')" \
+ --build-arg GIT_COMMIT_HASH="$(git rev-parse --short HEAD)" \
+ --build-arg GIT_REPO_URL="$(git config --get remote.origin.url)" \
+ `# using the current date as value for BASE_LAYER_CACHE_KEY, i.e. the base layer cache (that holds system packages with security updates) will be invalidate once per day` \
+ --build-arg BASE_LAYER_CACHE_KEY=$(date +%Y%m%d) \
+ -t $DOCKER_REPO:latest \
+ -t $DOCKER_REPO:latest-buster \
+ -t $DOCKER_REPO:${last_commit_date} \
+ -t $DOCKER_REPO:${last_commit_date}-buster \
+ "$@"
+
+#
+# perform security audit using https://github.com/aquasecurity/trivy
+#
+mkdir -p ${TRIVY_CACHE_DIR:-$HOME/.trivy/cache}
+docker run --rm \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v ${TRIVY_CACHE_DIR:-$HOME/.trivy/cache}:/root/.cache/ \
+ aquasec/trivy --no-progress --exit-code 0 --severity HIGH,CRITICAL $DOCKER_REPO:${last_commit_date}
+docker run --rm \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v ${TRIVY_CACHE_DIR:-$HOME/.trivy/cache}:/root/.cache/ \
+ aquasec/trivy --no-progress --ignore-unfixed --exit-code 1 --severity HIGH,CRITICAL $DOCKER_REPO:${last_commit_date}
+sudo chown -R $USER:$(id -gn) $TRIVY_CACHE_DIR || true
+
+#
+# determine effective LDAP version and apply tags
+#
+ldap_version=$(docker run $DOCKER_REPO:${last_commit_date} "dpkg -s slapd | grep 'Version:' | grep -oP 'Version: \K\d+\.\d+\.\d+'")
+docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%.*}.x #2.4.x
+docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%.*}.x-buster #2.4.x
+docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%%.*}.x #2.x
+docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%%.*}.x-buster #2.x
+
+#
+# push image with tags to remote docker registry
+#
+if [[ "${DOCKER_PUSH:-0}" == "1" ]]; then
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:latest
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:latest-buster
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version} #2.4.47
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version}-buster #2.4.47
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x #2.4.x
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x-buster #2.4.x
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x #2.x
+ docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x-buster #2.x
+
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:latest
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:latest-buster
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version} #2.4.47
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version}-buster #2.4.47
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x #2.4.x
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x-buster #2.4.x
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x #2.x
+ docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x-buster #2.x
+fi
diff --git a/example/docker-compose/docker-compose.yml b/example/docker-compose/docker-compose.yml
new file mode 100644
index 0000000..e6a63b6
--- /dev/null
+++ b/example/docker-compose/docker-compose.yml
@@ -0,0 +1,66 @@
+#############################
+# @author Sebastian Thomschke, Vegard IT GmbH
+#############################
+
+version: '3.7'
+
+# https://docs.docker.com/compose/compose-file/
+
+services:
+
+ ldap_service:
+ # https://github.com/vegardit/docker-openldap
+ image: vegardit/openldap:latest # https://hub.docker.com/r/vegardit/openldap
+ environment:
+ #DEBUG_RUN_SH: 1
+ LDAP_INIT_ORG_DN: o=example.com
+ LDAP_INIT_ORG_NAME: Example Inc.
+ LDAP_INIT_ROOT_USER_DN: uid=admin,o=example.com
+ LDAP_INIT_ROOT_USER_PW: CHANGE@it
+ LDAP_BACKUP_TIME: # disable backup by setting to empty string
+ ports:
+ - 389:389
+ volumes:
+ - ./ldifs/init_org_entries.ldif:/opt/ldifs/init_org_entries.ldif:ro
+ - ./ldifs/init_org_tree.ldif:/opt/ldifs/init_org_tree.ldif:ro
+
+ ldap_ui:
+ # https://github.com/alekseydemidov/pla-ssp
+ image: netflyer/openldap-ui:latest # https://hub.docker.com/r/netflyer/openldap-ui
+ ports:
+ - 8080:8080
+ - 8081:8081
+ environment:
+ ###############################
+ # PHP LDAP Admin (Port 8080)
+ ###############################
+ PHPLDAPADMIN_LDAP_BASE: o=example.com
+ PHPLDAPADMIN_LDAP_CLIENT_TLS: 'false'
+ PHPLDAPADMIN_LDAP_HOSTS: ldap_service
+ ###############################
+ # SelfService Password (Port 8081) https://github.com/ltb-project/self-service-password
+ ###############################
+ DEBUG_MODE: 'false'
+ #LDAP connection https://ltb-project.org/documentation/self-service-password/1.3/config_ldap
+ LDAP_URL: ldap://ldap_service
+ LDAP_BASE_DN: ou=Users,o=example.com
+ LDAP_USER_DN: uid=ldapbind,ou=TechnicalAccounts,ou=Users,o=example.com
+ LDAP_USER_PASSWORD: CHANGE@it
+ LDAP_FILTER: '(\&(objectClass=inetOrgPerson)(uid={login}))' # backslash is mandatory!
+ LDAP_LOGIN_ATTRIBUTE: 'uid'
+ LDAP_FULLNAME_ATTRIBUTE: 'cn'
+ CHANGE_SSHKEY: 'true'
+ MAIL_ADDRESS_USE_LDAP: 'true'
+ USE_TOKENS: 'false' # don't use "forgot my password" links
+ USE_QUESTIONS: 'false' # https://ltb-project.org/documentation/self-service-password/latest/config_questions
+ # Password policy https://ltb-project.org/documentation/self-service-password/1.3/config_ppolicy
+ PWD_MIN_LENGTH: 8
+ PWD_MAX_LENGTH: 32
+ PWD_MIN_LOWER: 1
+ PWD_MIN_UPPER: 1
+ PWD_MIN_DIGIT: 1
+ PWD_MIN_SPECIAL: 1
+ PWD_NO_REUSE: 'false'
+ PWD_SHOW_POLICY: 'always'
+ PWD_SHOW_POLICY_POS: 'below'
+ USE_PWNEDPASSWORD: 'false'
diff --git a/example/docker-compose/ldifs/init_org_entries.ldif b/example/docker-compose/ldifs/init_org_entries.ldif
new file mode 100644
index 0000000..fa5ca06
--- /dev/null
+++ b/example/docker-compose/ldifs/init_org_entries.ldif
@@ -0,0 +1,68 @@
+version: 1
+
+##############################
+# Employees
+# slappasswd -h {SHA} -s changeit
+##############################
+dn: uid=alice,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN}
+ou: Employees
+ou: Users
+description: Alice Young
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: ldapPublicKey
+uid: alice
+cn: Alice Young
+sn: Young
+givenName: Alice
+mail: alice@example.com
+userPassword: CHANGE@it
+sshPublicKey:
+
+dn: uid=bob,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN}
+ou: Employees
+ou: Users
+description: Bob Old
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: ldapPublicKey
+uid: bob
+cn: Bob Old
+sn: Old
+givenName: Bob
+mail: bob@example.com
+userPassword: CHANGE@it
+sshPublicKey:
+
+
+##############################
+# Technical Accounts
+##############################
+dn: uid=ldapbind,ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN}
+ou: TechnicalAccounts
+ou: Users
+description: ldapbind user
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: ldapbind
+cn: ldapbind
+sn: ldapbind
+userPassword: CHANGE@it
+
+##############################
+# Groups
+##############################
+dn: cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+description: LDAP Administrators Group
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: ldapadmins
+uniqueMember: uid=alice,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN}
+uniqueMember: uid=bob,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN}
diff --git a/example/docker-compose/ldifs/init_org_tree.ldif b/example/docker-compose/ldifs/init_org_tree.ldif
new file mode 100644
index 0000000..9447714
--- /dev/null
+++ b/example/docker-compose/ldifs/init_org_tree.ldif
@@ -0,0 +1,43 @@
+version: 1
+
+##############################
+# LDAP tree definition
+##############################
+dn: ${LDAP_INIT_ORG_DN}
+description: ${LDAP_INIT_ORG_NAME}
+objectClass: top
+objectClass: organization
+${LDAP_INIT_ORG_DN_ATTR}
+
+dn: ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+description: Groups
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=Users,${LDAP_INIT_ORG_DN}
+ou: Users
+description: Users Accounts
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=Employees,ou=Users,${LDAP_INIT_ORG_DN}
+ou: Employees
+ou: Users
+description: Internal Users (Employees)
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=External,ou=Users,${LDAP_INIT_ORG_DN}
+ou: External
+ou: Users
+description: External Users (Contractors, Customers)
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN}
+ou: TechnicalAccounts
+ou: Users
+description: Technical Accounts
+objectClass: top
+objectClass: organizationalUnit
diff --git a/example/docker/example.env b/example/docker/example.env
new file mode 100644
index 0000000..6bcca4d
--- /dev/null
+++ b/example/docker/example.env
@@ -0,0 +1,24 @@
+#See https://docs.docker.com/compose/env-file/
+
+#DEBUG_RUN_SH=0
+#INIT_SH_FILE=
+
+LDAP_INIT_ORG_DN=o=yourorg
+LDAP_INIT_ORG_NAME=Example Inc
+#LDAP_INIT_ADMIN_GROUP_DN=cn=ldapadmins,ou=Groups,o=yourorg
+#LDAP_INIT_ROOT_USER_DN=uid=admin,o=yourorg
+LDAP_INIT_ROOT_USER_PW=password
+
+#LDAP_INIT_PPOLICY_DEFAULT_DN=cn=DefaultPasswordPolicy,ou=Policies,o=yourorg
+LDAP_INIT_PPOLICY_PW_MIN_LENGTH=8
+#LDAP_INIT_PPOLICY_MAX_FAILURES=3
+#LDAP_INIT_PPOLICY_LOCKOUT_DURATION=300
+#LDAP_INIT_RFC2307BIS_SCHEMA=0
+#LDAP_PPOLICY_PQCHECKER_RULE=0|01010101
+
+#LDAP_NOFILE_LIMIT=1024
+#LDAP_LOG_LEVELS=Config Stats
+
+# Format is "HH:MM", i.e. 24-hour format with minute precision
+#LDAP_BACKUP_TIME=02:00
+#LDAP_BACKUP_FILE=/var/lib/ldap/data.ldif
diff --git a/image/Dockerfile b/image/Dockerfile
new file mode 100644
index 0000000..043766c
--- /dev/null
+++ b/image/Dockerfile
@@ -0,0 +1,131 @@
+# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com
+# SPDX-License-Identifier: Apache-2.0
+#
+# @author Sebastian Thomschke, Vegard IT GmbH
+#
+# https://github.com/vegardit/docker-openldap
+#
+
+# built daily:
+# https://hub.docker.com/r/bitnami/minideb/tags?page=1&name=buster
+FROM bitnami/minideb:buster
+
+# built monthly:
+# https://hub.docker.com/_/debian?tab=tags&page=1&name=buster-slim
+# FROM debian:buster-slim
+
+LABEL maintainer="Vegard IT GmbH (vegardit.com)"
+
+# if set to 1 debug tools are added to the image (htop,less,mc,vim)
+ARG DEBUG_BUILD=0
+
+ARG PQCHECKER_URL=https://www.meddeb.net/pub/pqchecker/deb/8/pqchecker_2.0.0_amd64.deb
+ARG PQCHECKER_MD5=c005ce596e97d13e39485e711dcbc7e1
+
+ARG DEBIAN_FRONTEND=noninteractive
+ARG LC_ALL=C
+
+ARG BASE_LAYER_CACHE_KEY
+
+SHELL ["/bin/bash", "-c"]
+
+RUN \
+ apt-get update -y && \
+ # https://github.com/phusion/baseimage-docker/issues/319
+ apt-get install -y apt-utils 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) && \
+ ############################################################
+ if [ "${DEBUG_BUILD}" = "1" ]; then \
+ echo "Installing debugging tools..." && \
+ apt-get update -y && \
+ apt-get install --no-install-recommends -y libcomerr2 mc && \
+ apt-get install --no-install-recommends -y htop less procps vim && \
+ echo 'set ignorecase\n\
+set showmatch\n\
+set novisualbell\n\
+set noerrorbells\n\
+syntax enable\n\
+set mouse-=a' > ~/.vimrc; \
+ fi && \
+ ############################################################
+ echo "Installing slapd..." && \
+ echo 'slapd slapd/root_password password whatever' | debconf-set-selections && \
+ echo 'slapd slapd/root_password_again password whatever' | debconf-set-selections && \
+ apt-get install --no-install-recommends -y slapd ldap-utils && \
+ echo "OpenLDAP $(apt-cache show slapd | grep Version)" >> /opt/build_info && \
+ # workaround for 'service slapd stop' not working, see https://stackoverflow.com/a/58792698/5116073
+ sed -i 's/--exec $SLAPD 2/--name slapd 2/' /etc/init.d/slapd && \
+ ############################################################
+ echo "Installing pqChecker password quality checker module..." && \
+ # https://www.meddeb.net/pqchecker/
+ apt-get install --no-install-recommends -y curl && \
+ curl -k -o /tmp/pqchecker.deb -SL "${PQCHECKER_URL}" && \
+ echo "${PQCHECKER_MD5} /tmp/pqchecker.deb" | md5sum -c - && \
+ dpkg -i /tmp/pqchecker.deb && \
+ rm /tmp/pqchecker.deb && \
+ apt-get remove --auto-remove -y curl && \
+ ############################################################
+ echo "Moving config and data directories..." && \
+ mv /etc/ldap/slapd.d /etc/ldap/slapd.d_orig && \
+ mkdir /etc/ldap/slapd.d && \
+ mv /var/lib/ldap /var/lib/ldap_orig && \
+ mkdir /var/lib/ldap && \
+ ############################################################
+ echo "apt-get clean up..." && \
+ apt-get clean autoclean && \
+ apt-get autoremove --purge -y && \
+ ############################################################
+ echo "Removing logs, caches and temp files..." && \
+ rm -rf /var/cache/{apt,debconf} \
+ /var/lib/apt/lists/* \
+ /var/log/{apt,alternatives.log,bootstrap.log,dpkg.log} \
+ /tmp/* /var/tmp/*
+
+VOLUME ["/etc/ldap/slapd.d", "/var/lib/ldap"]
+
+ARG GIT_REPO_URL
+ARG GIT_BRANCH
+ARG GIT_COMMIT_HASH
+ARG GIT_COMMIT_DATE
+
+LABEL \
+ org.label-schema.vcs-ref=$GIT_COMMIT_HASH \
+ org.label-schema.vcs-url="https://github.com/vegardit/docker-openldap"
+
+RUN \
+ echo "GIT_REPO: $GIT_REPO_URL" > /opt/build_info && \
+ echo "GIT_BRANCH: $GIT_BRANCH" >> /opt/build_info && \
+ echo "GIT_COMMIT: $GIT_COMMIT_HASH @ $GIT_COMMIT_DATE" >> /opt/build_info && \
+ cat /opt/build_info
+
+COPY ldifs /opt/ldifs
+COPY run.sh /opt/run.sh
+
+# Default configuration: can be overridden at the docker command line
+ENV \
+ DEBUG_RUN_SH=0 \
+ INIT_SH_FILE='' \
+ LDAP_INIT_ORG_DN='o=example.com' \
+ LDAP_INIT_ORG_NAME='Example Corporation' \
+ LDAP_INIT_ADMIN_GROUP_DN='cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN}' \
+ LDAP_INIT_ROOT_USER_DN='uid=admin,${LDAP_INIT_ORG_DN}' \
+ LDAP_INIT_ROOT_USER_PW='changeit' \
+ LDAP_INIT_PPOLICY_DEFAULT_DN='cn=DefaultPasswordPolicy,ou=Policies,${LDAP_INIT_ORG_DN}' \
+ LDAP_INIT_PPOLICY_PW_MIN_LENGTH=8 \
+ LDAP_INIT_PPOLICY_MAX_FAILURES=3 \
+ LDAP_INIT_PPOLICY_LOCKOUT_DURATION=300 \
+ LDAP_INIT_RFC2307BIS_SCHEMA=0 \
+ LDAP_PPOLICY_PQCHECKER_RULE='0|01010101' \
+ LDAP_NOFILE_LIMIT=1024 \
+ LDAP_LOG_LEVELS='Config Stats' \
+ # Format is "HH:MM", i.e. 24-hour format with minute precision
+ LDAP_BACKUP_TIME='02:00' \
+ LDAP_BACKUP_FILE='/var/lib/ldap/data.ldif' \
+ LDAP_OPENLDAP_UID='' \
+ LDAP_OPENLDAP_GID=''
+
+EXPOSE 389
+
+ENTRYPOINT ["/bin/sh", "-c"]
+
+# exec is required for propagating SIGTERM from docker to child process
+CMD ["exec /bin/bash /opt/run.sh"]
\ No newline at end of file
diff --git a/image/ldifs/init_frontend.ldif b/image/ldifs/init_frontend.ldif
new file mode 100644
index 0000000..f1ad0e1
--- /dev/null
+++ b/image/ldifs/init_frontend.ldif
@@ -0,0 +1,12 @@
+version: 1
+
+# Global Database Options @ https://linux.die.net/man/5/slapd-config
+
+dn: olcDatabase={-1}frontend,cn=config
+changetype: modify
+replace: olcPasswordHash
+olcPasswordHash: {SSHA}
+-
+# maximum number of seconds (in real time) slapd will spend answering a search request
+replace: olcTimeLimit
+olcTimeLimit: 60
diff --git a/image/ldifs/init_mdb.ldif b/image/ldifs/init_mdb.ldif
new file mode 100644
index 0000000..704e62f
--- /dev/null
+++ b/image/ldifs/init_mdb.ldif
@@ -0,0 +1,20 @@
+version: 1
+
+# General Database Options @ https://linux.die.net/man/5/slapd-config
+
+dn: olcDatabase={1}mdb,cn=config
+changetype: modify
+########################
+# configure Base DN
+########################
+replace: olcSuffix
+olcSuffix: ${LDAP_INIT_ORG_DN}
+-
+########################
+# configure root user
+########################
+replace: olcRootDN
+olcRootDN: ${LDAP_INIT_ROOT_USER_DN}
+-
+replace: olcRootPW
+olcRootPW: ${LDAP_INIT_ROOT_USER_PW_HASHED}
diff --git a/image/ldifs/init_mdb_acls.ldif b/image/ldifs/init_mdb_acls.ldif
new file mode 100644
index 0000000..d8cf7e1
--- /dev/null
+++ b/image/ldifs/init_mdb_acls.ldif
@@ -0,0 +1,37 @@
+version: 1
+
+# General Database Options @ https://linux.die.net/man/5/slapd-config
+
+dn: olcDatabase={1}mdb,cn=config
+changetype: modify
+########################
+# configure ACLs
+########################
+replace: olcAccess
+olcAccess: {0}to attrs=userPassword
+ by dn="${LDAP_INIT_ROOT_USER_DN}" write
+ by group/groupOfUniqueNames/uniqueMember="${LDAP_INIT_ADMIN_GROUP_DN}" write
+ by self write
+ by anonymous auth
+ by * none
+olcAccess: {1}to attrs=shadowLastChange
+ by self write
+ by users read
+ by * none
+olcAccess: {2}to attrs=sshPublicKey
+ by dn="${LDAP_INIT_ROOT_USER_DN}" write
+ by group/groupOfUniqueNames/uniqueMember="${LDAP_INIT_ADMIN_GROUP_DN}" write
+ by self write
+ by users read
+ by * none
+# This ACL gives incoming connections the ability to read the rootDSE.
+# It is important to allow this, as clients may need to obtain information from the base level (such as your supported SASL Mechanisms).
+# The ".base" portion of the ACL restricts clients to querying only the top level
+olcAccess: {3}to dn.base=""
+ by * read
+olcAccess: {4}to *
+ by dn="${LDAP_INIT_ROOT_USER_DN}" write
+ by group/groupOfUniqueNames/uniqueMember="${LDAP_INIT_ADMIN_GROUP_DN}" write
+ by users read
+ by anonymous auth
+ by * none
diff --git a/image/ldifs/init_mdb_indexes.ldif b/image/ldifs/init_mdb_indexes.ldif
new file mode 100644
index 0000000..ae7d7f0
--- /dev/null
+++ b/image/ldifs/init_mdb_indexes.ldif
@@ -0,0 +1,15 @@
+version: 1
+
+# General Database Options @ https://linux.die.net/man/5/slapd-config
+
+dn: olcDatabase={1}mdb,cn=config
+changetype: modify
+########################
+# configure DB indexes
+########################
+replace: olcDbIndex
+olcDbIndex: objectClass pres,eq
+olcDbIndex: uid,mail pres,eq
+olcDbIndex: uidNumber,gidNumber eq
+olcDbIndex: cn,givenName,sn,ou pres,eq,approx,sub
+olcDbIndex: member,memberUid,memberOf,uniqueMember pres,eq
diff --git a/image/ldifs/init_module_memberof.ldif b/image/ldifs/init_module_memberof.ldif
new file mode 100644
index 0000000..dbe304d
--- /dev/null
+++ b/image/ldifs/init_module_memberof.ldif
@@ -0,0 +1,31 @@
+version: 1
+
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: memberof
+olcModuleLoad: refint
+
+dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
+objectClass: top
+objectClass: olcConfig
+objectClass: olcOverlayConfig
+objectClass: olcMemberOf
+olcOverlay: memberof
+olcMemberOfDangling: ignore
+olcMemberOfRefInt: TRUE
+olcMemberOfGroupOC: groupOfUniqueNames
+olcMemberOfMemberAD: uniqueMember
+olcMemberOfMemberOfAD: memberOf
+
+dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config
+objectClass: top
+objectClass: olcConfig
+objectClass: olcOverlayConfig
+objectClass: olcRefintConfig
+olcOverlay: refint
+olcRefintAttribute: owner
+olcRefintAttribute: manager
+olcRefintAttribute: member
+olcRefintAttribute: memberOf
+olcRefintAttribute: uniqueMember
diff --git a/image/ldifs/init_module_ppolicy.ldif b/image/ldifs/init_module_ppolicy.ldif
new file mode 100644
index 0000000..da2409b
--- /dev/null
+++ b/image/ldifs/init_module_ppolicy.ldif
@@ -0,0 +1,18 @@
+version: 1
+
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: ppolicy
+
+dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
+objectClass: top
+objectClass: olcConfig
+objectClass: olcOverlayConfig
+objectClass: olcPPolicyConfig
+olcOverlay: ppolicy
+olcPPolicyDefault: ${LDAP_INIT_PPOLICY_DEFAULT_DN}
+olcPPolicyHashCleartext: TRUE
+# return AccountLocked instead of InvalidCredentials error:
+olcPPolicyUseLockout: TRUE
+olcPPolicyForwardUpdates: FALSE
diff --git a/image/ldifs/init_module_unique.ldif b/image/ldifs/init_module_unique.ldif
new file mode 100644
index 0000000..fe6293a
--- /dev/null
+++ b/image/ldifs/init_module_unique.ldif
@@ -0,0 +1,18 @@
+version: 1
+
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: unique
+
+dn: olcOverlay=unique,olcDatabase={1}mdb,cn=config
+objectClass: top
+objectClass: olcConfig
+objectClass: olcOverlayConfig
+objectClass: olcUniqueConfig
+olcOverlay: unique
+olcUniqueURI: ldap:///${LDAP_INIT_ORG_DN}?cn?sub
+olcUniqueURI: ldap:///${LDAP_INIT_ORG_DN}?uid?sub
+olcUniqueURI: ldap:///ou=Users,${LDAP_INIT_ORG_DN}?mail?sub
+olcUniqueURI: ldap:///ou=Users,${LDAP_INIT_ORG_DN}?gidNumber?sub
+olcUniqueURI: ldap:///ou=Users,${LDAP_INIT_ORG_DN}?uidNumber?sub
diff --git a/image/ldifs/init_org_entries.ldif b/image/ldifs/init_org_entries.ldif
new file mode 100644
index 0000000..0236c2c
--- /dev/null
+++ b/image/ldifs/init_org_entries.ldif
@@ -0,0 +1,95 @@
+version: 1
+
+##############################
+# Internal Users
+##############################
+dn: uid=employee1,ou=Internal,ou=Users,${LDAP_INIT_ORG_DN}
+ou: Internal
+ou: Users
+description: Employee 1
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: ldapPublicKey
+uid: employee1
+cn: Employee1
+sn: Employee1
+givenName: Employee1
+mail: employee1@example.com
+userPassword: changeit
+
+
+##############################
+# External Users
+##############################
+dn: uid=guest1,ou=External,ou=Users,${LDAP_INIT_ORG_DN}
+ou: External
+ou: Users
+description: Guest 1
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: guest1
+cn: Guest1
+sn: Guest1
+givenName: Guest1
+mail: guest1@example.com
+userPassword: changeit
+
+
+##############################
+# Technical Users
+##############################
+dn: uid=machine1,ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN}
+ou: External
+ou: Users
+description: Machine 1
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: ldapPublicKey
+uid: machine1
+cn: Machine1
+sn: Machine1
+givenName: Machine1
+mail: machine1@example.com
+userPassword: changeit
+
+
+##############################
+# Groups
+##############################
+dn: cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: ldapadmins
+description: Ldap Admin Group
+uniqueMember: uid=employee1,ou=External,ou=Users,${LDAP_INIT_ORG_DN}
+
+dn: cn=employees,ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: employees
+description: Employees Group
+uniqueMember: uid=employee1,ou=External,ou=Users,${LDAP_INIT_ORG_DN}
+
+dn: cn=guests,ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: guests
+description: Guests Group
+uniqueMember: uid=guest1,ou=External,ou=Users,${LDAP_INIT_ORG_DN}
+
+dn: cn=machines,ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: machines
+description: Machines Group
+uniqueMember: uid=machine1,ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN}
diff --git a/image/ldifs/init_org_ppolicy.ldif b/image/ldifs/init_org_ppolicy.ldif
new file mode 100644
index 0000000..32bd152
--- /dev/null
+++ b/image/ldifs/init_org_ppolicy.ldif
@@ -0,0 +1,40 @@
+version: 1
+
+##############################
+# Password Policy
+##############################
+dn: ou=Policies,${LDAP_INIT_ORG_DN}
+ou: Policies
+objectClass: top
+objectClass: organizationalUnit
+
+dn: cn=DefaultPasswordPolicy,ou=Policies,${LDAP_INIT_ORG_DN}
+objectClass: top
+objectClass: device
+objectClass: pwdPolicy
+objectClass: pwdPolicyChecker
+cn: DefaultPasswordPolicy
+pwdAttribute: userPassword
+# 0=reset count of consecutive password failures only on successful authentication
+pwdFailureCountInterval: 0
+# maximum number of consecutive password failures locking the account
+pwdMaxFailure: ${LDAP_INIT_PPOLICY_MAX_FAILURES:-3}
+pwdMinAge: 0
+pwdMustChange: TRUE
+# if TRUE user must send current password during password modification operation
+pwdSafeModify: FALSE
+# number of passwords maintained in a list of previously used passwords
+pwdInHistory: 0
+# number of further bind operations after a password has expired
+pwdGraceAuthNLimit: 0
+# account lock duration in seconds (300=5min)
+pwdLockoutDuration: ${LDAP_INIT_PPOLICY_LOCKOUT_DURATION:-300}
+pwdAllowUserChange: TRUE
+#0=do not show expiration warning on bind
+pwdExpireWarning: 0
+pwdLockout: TRUE
+pwdMaxAge: 0
+# Password Quality
+pwdMinLength: ${LDAP_INIT_PPOLICY_PW_MIN_LENGTH:-8}
+pwdCheckQuality: 2
+pwdCheckModule: /usr/lib/ldap/pqchecker.so
diff --git a/image/ldifs/init_org_tree.ldif b/image/ldifs/init_org_tree.ldif
new file mode 100644
index 0000000..88692c1
--- /dev/null
+++ b/image/ldifs/init_org_tree.ldif
@@ -0,0 +1,43 @@
+version: 1
+
+##############################
+# LDAP tree definition
+##############################
+dn: ${LDAP_INIT_ORG_DN}
+description: ${LDAP_INIT_ORG_NAME}
+objectClass: top
+objectClass: organization
+${LDAP_INIT_ORG_DN_ATTR}
+
+dn: ou=Groups,${LDAP_INIT_ORG_DN}
+ou: Groups
+description: Groups
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=Users,${LDAP_INIT_ORG_DN}
+ou: Users
+description: Users Accounts
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=Internal,ou=Users,${LDAP_INIT_ORG_DN}
+ou: Internal
+ou: Users
+description: Internal Users (Employees)
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=External,ou=Users,${LDAP_INIT_ORG_DN}
+ou: External
+ou: Users
+description: External Users (Contractors, Customers)
+objectClass: top
+objectClass: organizationalUnit
+
+dn: ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN}
+ou: TechnicalAccounts
+ou: Users
+description: Technical Accounts
+objectClass: top
+objectClass: organizationalUnit
diff --git a/image/ldifs/schema_ldapPublicKey.ldif b/image/ldifs/schema_ldapPublicKey.ldif
new file mode 100644
index 0000000..fd55679
--- /dev/null
+++ b/image/ldifs/schema_ldapPublicKey.ldif
@@ -0,0 +1,15 @@
+dn: cn=openssh-lpk,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: openssh-lpk
+olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13
+ NAME 'sshPublicKey'
+ DESC 'MANDATORY: OpenSSH Public key'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+ )
+olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0
+ NAME 'ldapPublicKey'
+ DESC 'MANDATORY: OpenSSH LPK objectclass'
+ SUP top
+ AUXILIARY MAY ( sshPublicKey $ uid )
+ )
diff --git a/image/ldifs/schema_rfc2307bis02.ldif b/image/ldifs/schema_rfc2307bis02.ldif
new file mode 100644
index 0000000..67ebdc0
--- /dev/null
+++ b/image/ldifs/schema_rfc2307bis02.ldif
@@ -0,0 +1,53 @@
+# https://tools.ietf.org/html/draft-howard-rfc2307bis-02
+dn: cn=rfc2307bis,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: rfc2307bis
+olcAttributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgroup triple' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' DESC 'Service port number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' DESC 'Service protocol name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' DESC 'IP protocol number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' DESC 'ONC RPC number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IPv4 addresses as a dotted decimal omitting leading zeros or IPv6 addresses as defined in RFC2373' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP network omitting leading zeros, eg. 192.168' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netmask omitting leading zeros, eg. 255.255.255.0' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address in maximal, colon separated hex notation, eg. 00:00:92:90:ee:e2' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootparamd parameter' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image name' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' DESC 'Name of a generic NIS map' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{64} )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' DESC 'A generic NIS entry' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.28 NAME 'nisPublicKey' DESC 'NIS public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.29 NAME 'nisSecretKey' DESC 'NIS secret key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.30 NAME 'nisDomain' DESC 'NIS domain' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.31 NAME 'automountMapName' DESC 'Automount Map Name' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.32 NAME 'automountKey' DESC 'Automount Key value' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.33 NAME 'automountInformation' DESC 'Automount information' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcObjectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword $ description $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top AUXILIARY MUST gidNumber MAY ( userPassword $ memberUid $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an Internet Protocol service. Maps an IP port and protocol (such as tcp or udp) to one or more names; the distinguished value of the cn attribute denotes the services canonical name' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipServiceProtocol ) MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of an IP protocol. Maps a protocol number to one or more names. The distinguished value of the cn attribute denotes the protocol canonical name' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber ) MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an Open Network Computing (ONC) [RFC1057] Remote Procedure Call (RPC) binding. This class maps an ONC RPC number to a name. The distinguished value of the cn attribute denotes the RPC service canonical name' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber ) MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a host, an IP device. The distinguished value of the cn attribute denotes the hosts canonical name. Device SHOULD be used as a structural class' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( userPassword $ l $ description $ manager ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a network. The distinguished value of the cn attribute denotes the network canonical name' SUP top STRUCTURAL MUST ipNetworkNumber MAY ( cn $ ipNetmaskNumber $ l $ description $ manager ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of a netgroup. May refer to other netgroups' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstraction of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device with a MAC address; device SHOULD be used as a structural class' SUP top AUXILIARY MAY macAddress )
+olcObjectClasses: ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device with boot parameters; device SHOULD be used as a structural class' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject' DESC 'An object with a public and secret key' SUP top AUXILIARY MUST ( cn $ nisPublicKey $ nisSecretKey ) MAY ( uidNumber $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.15 NAME 'nisDomainObject' DESC 'Associates a NIS domain with a naming context' SUP top AUXILIARY MUST nisDomain )
+olcObjectClasses: ( 1.3.6.1.1.1.2.16 NAME 'automountMap' SUP top STRUCTURAL MUST automountMapName MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.17 NAME 'automount' DESC 'Automount information' SUP top STRUCTURAL MUST ( automountKey $ automountInformation ) MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.18 NAME 'groupOfMembers' DESC 'A group with members (DNs)' SUP top STRUCTURAL MUST cn MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description $ member ) )
diff --git a/image/ldifs/schema_sudo.ldif b/image/ldifs/schema_sudo.ldif
new file mode 100644
index 0000000..3729e62
--- /dev/null
+++ b/image/ldifs/schema_sudo.ldif
@@ -0,0 +1,11 @@
+dn: cn=sudo,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: sudo
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.1 NAME 'sudoUser' DESC 'User(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.2 NAME 'sudoHost' DESC 'Host(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.3 NAME 'sudoCommand' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.4 NAME 'sudoRunAs' DESC 'User(s) impersonated by sudo (deprecated)' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.5 NAME 'sudoOption' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.6 NAME 'sudoRunAsUser' DESC 'User(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.7 NAME 'sudoRunAsGroup' DESC 'Group(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcObjectClasses: ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' DESC 'Sudoer Entries' SUP top STRUCTURAL MUST cn MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ description ) )
diff --git a/image/run.sh b/image/run.sh
new file mode 100644
index 0000000..095be59
--- /dev/null
+++ b/image/run.sh
@@ -0,0 +1,238 @@
+#!/usr/bin/env bash
+#
+# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com
+# SPDX-License-Identifier: Apache-2.0
+#
+# @author Sebastian Thomschke, Vegard IT GmbH
+#
+# https://github.com/vegardit/docker-openldap
+#
+
+set -e -u
+if [ -z "${BASH_VERSINFO:-}" ]; then /usr/bin/env bash "$0" "$@"; exit; fi
+set -o pipefail
+trap 'echo >&2 "$(date +%H:%M:%S) Error - exited with status $? at line $LINENO:"; pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR
+
+if [ "${DEBUG_RUN_SH:-}" == "1" ]; then
+ set -x
+fi
+
+cat <<'EOF'
+ _ __ __ __________
+| | / /__ ____ _____ __________/ / / _/_ __/
+| | / / _ \/ __ `/ __ `/ ___/ __ / / / / /
+| |/ / __/ /_/ / /_/ / / / /_/ / _/ / / /
+|___/\___/\__, /\__,_/_/ \__,_/ /___/ /_/
+ /____/
+
+EOF
+
+cat /opt/build_info
+echo
+
+if [ -f "$INIT_SH_FILE" ]; then
+ source "$INIT_SH_FILE"
+fi
+
+
+function log() {
+ if [ -p /dev/stdin ]; then
+ while read line; do
+ echo "[$(date "+%Y-%m-%d %H:%M:%S") ${BASH_SOURCE}:${BASH_LINENO}] $line"
+ done
+ else
+ echo "[$(date "+%Y-%m-%d %H:%M:%S") ${BASH_SOURCE}:${BASH_LINENO}] ${@}"
+ fi
+}
+
+
+# display slapd build info
+slapd -VVV 2>&1 | log || true
+
+
+# Limit maximum number of open file descriptors otherwise slapd consumes two
+# orders of magnitude more of RAM, see https://github.com/docker/docker/issues/8231
+ulimit -n $LDAP_NOFILE_LIMIT
+
+
+#################################################################
+# Adjust UID/GID and file permissions based on env var config
+#################################################################
+if [ -n "${LDAP_OPENLDAP_UID:-}" ]; then
+ effective_uid=$(id -u openldap)
+ if [ "$LDAP_OPENLDAP_UID" != "$effective_uid" ]; then
+ log "Changing UID of openldap user from $effective_uid to $LDAP_OPENLDAP_UID..."
+ usermod -o -u "$LDAP_OPENLDAP_UID" openldap
+ fi
+fi
+if [ -n "${LDAP_OPENLDAP_GID:-}" ]; then
+ effective_gid=$(id -g openldap)
+ if [ "$LDAP_OPENLDAP_GID" != "$effective_gid" ]; then
+ log "Changing GID of openldap user from $effective_gid to $LDAP_OPENLDAP_GID..."
+ usermod -o -u "$LDAP_OPENLDAP_GID" openldap
+ fi
+fi
+chown -R openldap:openldap /etc/ldap
+chown -R openldap:openldap /var/lib/ldap
+chown -R openldap:openldap /var/lib/ldap_orig
+chown -R openldap:openldap /var/run/slapd
+
+
+#################################################################
+# Configure LDAP server on initial container launch
+#################################################################
+if [ ! -e /etc/ldap/slapd.d/initialized ]; then
+
+ function interpolate_vars() {
+ # https://stackoverflow.com/a/40167919
+ local line lineEscaped
+ while IFS= read -r line || [ -n "$line" ]; do # the `||` clause ensures that the last line is read even if it doesn't end with \n
+ # escape all chars that could trigger an expansion
+ IFS= read -r lineEscaped < <(echo "$line" | tr '`([$' '\1\2\3\4')
+ # selectively re-enable ${ references
+ lineEscaped=${lineEscaped//$'\4'{/\${}
+ # escape embedded double quotes to preserve them
+ lineEscaped=${lineEscaped//\"/\\\"}
+ eval "printf '%s\n' \"$lineEscaped\"" | tr '\1\2\3\4' '`([$'
+ done
+ }
+
+ function substr_before() {
+ echo "${1%%$2*}"
+ }
+
+ function str_replace() {
+ IFS= read -r -d $'\0' str
+ echo "${str/$1/$2}"
+ }
+
+ function ldif() {
+ log "--------------------------------------------"
+ local action=$1 && shift
+ local file=${!#}
+ log "Loading [$file]..."
+ interpolate_vars < $file > /tmp/$(basename $file)
+ ldap$action -H ldapi:/// "${@:1:${#}-1}" -f /tmp/$(basename $file)
+ }
+
+ # interpolate variable placeholders in env vars starting with "LDAP_INIT_"
+ for name in ${!LDAP_INIT_*}; do
+ declare "${name}=$(echo "${!name}" | interpolate_vars)"
+ done
+
+ # pre-populate folders in case they are empty
+ for folder in "/var/lib/ldap" "/etc/ldap/slapd.d"; do
+ if [ "$folder" -ef "${folder}_orig" ]; then
+ continue
+ fi
+ if [ -z "$(ls $folder)" ]; then
+ log "Initializing [$folder]..."
+ cp -r --preserve=all ${folder}_orig/. $folder
+ fi
+ done
+
+ LDAP_INIT_ROOT_USER_PW_HASHED=$(slappasswd -s "${LDAP_INIT_ROOT_USER_PW}")
+ /etc/init.d/slapd start
+ sleep 3
+
+ if [ "${LDAP_INIT_RFC2307BIS_SCHEMA:-}" == "1" ]; then
+ log "Replacing NIS (RFC2307) schema with RFC2307bis schema..."
+ ldapdelete -Y EXTERNAL cn={2}nis,cn=schema,cn=config
+ ldif add -Y EXTERNAL /opt/ldifs/schema_rfc2307bis02.ldif
+ fi
+
+ ldif add -Y EXTERNAL /etc/ldap/schema/ppolicy.ldif
+ ldif add -Y EXTERNAL /opt/ldifs/schema_sudo.ldif
+ ldif add -Y EXTERNAL /opt/ldifs/schema_ldapPublicKey.ldif
+
+ ldif modify -Y EXTERNAL /opt/ldifs/init_frontend.ldif
+ ldif add -Y EXTERNAL /opt/ldifs/init_module_memberof.ldif
+ ldif modify -Y EXTERNAL /opt/ldifs/init_mdb.ldif
+ ldif modify -Y EXTERNAL /opt/ldifs/init_mdb_acls.ldif
+ ldif modify -Y EXTERNAL /opt/ldifs/init_mdb_indexes.ldif
+ ldif add -Y EXTERNAL /opt/ldifs/init_module_unique.ldif
+ ldif add -Y EXTERNAL /opt/ldifs/init_module_ppolicy.ldif
+
+ LDAP_INIT_ORG_DN_ATTR=$(substr_before $LDAP_INIT_ORG_DN "," | str_replace "=" ": ") # referenced by init_org_tree.ldif
+ ldif add -x -D "$LDAP_INIT_ROOT_USER_DN" -w "$LDAP_INIT_ROOT_USER_PW" /opt/ldifs/init_org_tree.ldif
+ ldif add -x -D "$LDAP_INIT_ROOT_USER_DN" -w "$LDAP_INIT_ROOT_USER_PW" /opt/ldifs/init_org_ppolicy.ldif
+ ldif add -x -D "$LDAP_INIT_ROOT_USER_DN" -w "$LDAP_INIT_ROOT_USER_PW" /opt/ldifs/init_org_entries.ldif
+
+ log "--------------------------------------------"
+
+ echo "1" > /etc/ldap/slapd.d/initialized
+ rm -f /tmp/*.ldif
+
+ log "Creating periodic LDAP backup at [$LDAP_BACKUP_FILE]..."
+ slapcat -n 1 -l $LDAP_BACKUP_FILE || true
+
+ /etc/init.d/slapd stop
+ sleep 3
+fi
+
+echo "$LDAP_PPOLICY_PQCHECKER_RULE" > /etc/ldap/pqchecker/pqparams.dat
+
+
+#################################################################
+# Configure LDAP backup task
+#################################################################
+if [ -n "${LDAP_BACKUP_TIME:-}" ]; then
+ log "--------------------------------------------"
+ log "Configuring LDAP backup task to run daily: time=[${LDAP_BACKUP_TIME}] file=[$LDAP_BACKUP_FILE]..."
+ function backup_ldap() {
+ while true; do
+ while [ "$(date +%H:%M)" != "${LDAP_BACKUP_TIME}" ]; do
+ sleep 10s
+ done
+ log "Creating periodic LDAP backup at [$LDAP_BACKUP_FILE]..."
+ slapcat -n 1 -l $LDAP_BACKUP_FILE || true
+ sleep 23h
+ done
+ }
+
+ backup_ldap &
+fi
+
+
+#################################################################
+# Start LDAP service
+#################################################################
+log "--------------------------------------------"
+log "Starting OpenLDAP: slapd..."
+
+#required for propagating SIGTERM from docker to service process
+#https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash/444676#444676
+function trap_handler() {
+ local signal=$1
+ if [ -n "${service_process_pid:-}" ]; then
+ log "Sending [$signal] to PID [$service_process_pid] ..."
+ kill -s $signal $service_process_pid 2>/dev/null
+ else
+ log "Received [$signal] before service started."
+ sig_received_before_service_started=$signal
+ fi
+}
+
+trap 'trap_handler TERM' SIGTERM SIGINT SIGHUP # https://github.com/openldap/openldap/search?q=SIGTERM
+trap 'trap_handler QUIT' SIGQUIT
+trap 'trap_handler USR1' SIGUSR1 # https://github.com/openldap/openldap/search?q=SIGUSR1
+trap 'trap_handler USR2' SIGUSR2 # https://github.com/openldap/openldap/search?q=SIGUSR2
+
+/usr/sbin/slapd \
+ $(for logLevel in ${LDAP_LOG_LEVELS:-}; do echo -n "-d $logLevel "; done) \
+ -h "ldap:/// ldapi:///" \
+ -u openldap -g openldap \
+ -F /etc/ldap/slapd.d 2>&1 | log &
+
+service_process_pid=$(jobs -p | tail -1)
+log "OpenLDAP PID: $service_process_pid"
+
+if [ -n "${sig_received_before_service_started:-}" ]; then
+ kill -s $sig_received_before_service_started $service_process_pid 2>/dev/null
+fi
+
+wait $service_process_pid
+trap - TERM INT
+wait $service_process_pid
+exit_status=$?
+exit $exit_status