diff --git a/modules/Dockerfile b/modules/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9ea6df85a079979a74ae26759d9f4c8e963d7ced
--- /dev/null
+++ b/modules/Dockerfile
@@ -0,0 +1,67 @@
+FROM nginx:mainline as builder
+
+ARG ENABLED_MODULES
+
+RUN set -ex \
+    && if [ "$ENABLED_MODULES" = "" ]; then \
+        echo "No additional modules enabled, exiting"; \
+        exit 1; \
+    fi
+
+COPY ./ /modules/
+
+RUN set -ex \
+    && apt update \
+    && apt install -y --no-install-suggests --no-install-recommends \
+                patch make wget mercurial devscripts debhelper dpkg-dev \
+                quilt lsb-release build-essential libxml2-utils xsltproc \
+                equivs git g++ \
+    && hg clone https://hg.nginx.org/pkg-oss/ \
+    && cd pkg-oss \
+    && mkdir /tmp/packages \
+    && for module in $ENABLED_MODULES; do \
+        echo "Building $module for nginx-$NGINX_VERSION"; \
+        if [ -d /modules/$module ]; then \
+            echo "Building $module from user-supplied sources"; \
+            # check if module sources file is there and not empty
+            if [ ! -s /modules/$module/source ]; then \
+                echo "No source file for $module in modules/$module/source, exiting"; \
+                exit 1; \
+            fi; \
+            # some modules require build dependencies
+            if [ -f /modules/$module/build-deps ]; then \
+                echo "Installing $module build dependencies"; \
+                apt update && apt install -y --no-install-suggests --no-install-recommends $(cat /modules/$module/build-deps | xargs); \
+            fi; \
+            # if a module has a build dependency that is not in a distro, provide a
+            # shell script to fetch/build/install those
+            # note that shared libraries produced as a result of this script will
+            # not be copied from the builder image to the main one so build static
+            if [ -x /modules/$module/prebuild ]; then \
+                echo "Running prebuild script for $module"; \
+                /modules/$module/prebuild; \
+            fi; \
+            /pkg-oss/build_module.sh -v $NGINX_VERSION -f -y -o /tmp/packages -n $module $(cat /modules/$module/source); \
+        elif make -C /pkg-oss/debian list | grep -P "^$module\s+\d" > /dev/null; then \
+            echo "Building $module from pkg-oss sources"; \
+            cd /pkg-oss/debian; \
+            make rules-module-$module BASE_VERSION=$NGINX_VERSION; \
+            mk-build-deps --install --tool="apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes" debuild-module-$module/nginx-$NGINX_VERSION/debian/control; \
+            make module-$module BASE_VERSION=$NGINX_VERSION; \
+            find ../../ -maxdepth 1 -mindepth 1 -type f -name "*.deb" -exec mv -v {} /tmp/packages/ \;; \
+        else \
+            echo "Don't know how to build $module module, exiting"; \
+            exit 1; \
+        fi; \
+    done
+
+FROM nginx:mainline
+ARG ENABLED_MODULES
+COPY --from=builder /tmp/packages /tmp/packages
+RUN set -ex \
+    && apt update \
+    && for module in $ENABLED_MODULES; do \
+           apt install --no-install-suggests --no-install-recommends -y /tmp/packages/nginx-module-${module}_${NGINX_VERSION}*.deb; \
+       done \
+    && rm -rf /tmp/packages \
+    && rm -rf /var/lib/apt/lists/
diff --git a/modules/Dockerfile.alpine b/modules/Dockerfile.alpine
new file mode 100644
index 0000000000000000000000000000000000000000..b2b8b3e3f68d9dc94b4935db87af74bd61e0d1e0
--- /dev/null
+++ b/modules/Dockerfile.alpine
@@ -0,0 +1,67 @@
+FROM nginx:mainline-alpine as builder
+
+ARG ENABLED_MODULES
+
+RUN set -ex \
+    && if [ "$ENABLED_MODULES" = "" ]; then \
+        echo "No additional modules enabled, exiting"; \
+        exit 1; \
+    fi
+
+COPY ./ /modules/
+
+RUN set -ex \
+    && apk update \
+    && apk add linux-headers openssl-dev pcre-dev zlib-dev openssl abuild \
+               musl-dev libxslt libxml2-utils make mercurial gcc unzip git \
+               xz g++ \
+    # allow abuild as a root user \
+    && printf "#!/bin/sh\\n/usr/bin/abuild -F \"\$@\"\\n" > /usr/local/bin/abuild \
+    && chmod +x /usr/local/bin/abuild \
+    && hg clone https://hg.nginx.org/pkg-oss/ \
+    && cd pkg-oss \
+    && mkdir /tmp/packages \
+    && for module in $ENABLED_MODULES; do \
+        echo "Building $module for nginx-$NGINX_VERSION"; \
+        if [ -d /modules/$module ]; then \
+            echo "Building $module from user-supplied sources"; \
+            # check if module sources file is there and not empty
+            if [ ! -s /modules/$module/source ]; then \
+                echo "No source file for $module in modules/$module/source, exiting"; \
+                exit 1; \
+            fi; \
+            # some modules require build dependencies
+            if [ -f /modules/$module/build-deps ]; then \
+                echo "Installing $module build dependencies"; \
+                apk update && apk add $(cat /modules/$module/build-deps | xargs); \
+            fi; \
+            # if a module has a build dependency that is not in a distro, provide a
+            # shell script to fetch/build/install those
+            # note that shared libraries produced as a result of this script will
+            # not be copied from the builder image to the main one so build static
+            if [ -x /modules/$module/prebuild ]; then \
+                echo "Running prebuild script for $module"; \
+                /modules/$module/prebuild; \
+            fi; \
+            /pkg-oss/build_module.sh -v $NGINX_VERSION -f -y -o /tmp/packages -n $module $(cat /modules/$module/source); \
+        elif make -C /pkg-oss/alpine list | grep -E "^$module\s+\d+" > /dev/null; then \
+            echo "Building $module from pkg-oss sources"; \
+            cd /pkg-oss/alpine; \
+            make abuild-module-$module BASE_VERSION=$NGINX_VERSION; \
+            apk add $(. ./abuild-module-$module/APKBUILD; echo $makedepends;); \
+            make module-$module BASE_VERSION=$NGINX_VERSION; \
+            find ~/packages -type f -name "*.apk" -exec mv -v {} /tmp/packages/ \;; \
+        else \
+            echo "Don't know how to build $module module, exiting"; \
+            exit 1; \
+        fi; \
+    done
+
+FROM nginx:mainline-alpine
+ARG ENABLED_MODULES
+COPY --from=builder /tmp/packages /tmp/packages
+RUN set -ex \
+    && for module in $ENABLED_MODULES; do \
+           apk add --no-cache --allow-untrusted /tmp/packages/nginx-module-${module}-${NGINX_VERSION}*.apk; \
+       done \
+    && rm -rf /tmp/packages
diff --git a/modules/README.md b/modules/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8680022eb4e11401e2f50f3fab4a67b0ee99ef29
--- /dev/null
+++ b/modules/README.md
@@ -0,0 +1,83 @@
+# Adding third-party modules to nginx official image
+
+It's possible to extend a mainline image with third-party modules either from
+your own instuctions following a simple filesystem layout/syntax using
+`build_module.sh` helper script, or failing back to package sources from
+`https://hg.nginx.org/pkg-oss`.
+
+## Usage
+
+```
+$ docker build --build-arg ENABLED_MODULES="ndk lua" -t my-nginx-with-lua .
+```
+This command will attempt to build an image called `my-nginx-with-lua` based on
+official nginx docker hub image with two modules: `ndk` and `lua`.
+By default, a Debian-based image will be used.  If you wish to use Alpine
+instead, add `-f Dockerfile.alpine` to the command line.
+
+The build script will look for module build definition files on filesystem
+directory under the same name as the module (and resulting package) and if
+those are not found will try to look up requested modules in the pkg-oss
+repository.
+
+For well-known modules we maintain a set of build sources packages over at
+`pkg-oss`, so it's probably a good idea to rely on those instead of providing
+your own implementation.
+
+As of the time of writing this README, the following modules and their versions
+are available from `pkg-oss` repository:
+
+```
+/pkg-oss $ LC_ALL=C make -C debian list-all-modules
+make: Entering directory '/pkg-oss/debian'
+brotli                  1.0.0-1
+encrypted-session       0.08-1
+geoip                   1.19.6-1
+geoip2                  3.3-1
+headers-more            0.33-1
+image-filter            1.19.6-1
+lua                     0.10.19-1
+modsecurity             1.0.1-1
+ndk                     0.3.1-1
+njs                     0.5.0-1
+opentracing             0.10.0-1
+passenger               6.0.6-1
+perl                    1.19.6-1
+rtmp                    1.2.1-1
+set-misc                0.32-1
+subs-filter             0.6.4-1
+xslt                    1.19.6-1
+make: Leaving directory '/pkg-oss/debian'
+```
+
+If you still want to provide your own instructions for a specific module,
+organize the build directory in a following way, e.g. for `echo` module:
+
+```
+docker-nginx/modules $ tree echo
+echo
+├── build-deps
+├── prebuild
+└── source
+
+0 directories, 3 files
+```
+
+The scripts expect one file to always exist for a module you wish to build
+manually: `source`.  It should contain a link to a zip/tarball source code of a
+module you want to build.  In `build-deps` you can specify build dependencies
+for a module as found in Debian or Alpine repositories.  `prebuild` is a shell
+script (make it `chmod +x prebuild`!) that will be executed prior to building
+the module but after installing the dependencies, so it can be used to install
+additional build dependencies if they are not available from Debian or Alpine.
+Keep in mind that those dependencies wont be automatically copied to the
+resulting image and if you're building a library, build it statically.
+
+Once the build is done in the builder image, the built packages are copied over
+to resulting image and installed via apt/apk.  The resulting image will be
+tagged and can be used the same way as an official docker hub image.
+
+Note that we can not provide any support for those modifications and in no way
+guarantee they will work as nice as a build without third-party modules.  If
+you encounter any issues running your image with the modules enabled, please
+reproduce with a vanilla image first.
diff --git a/modules/echo/build-deps b/modules/echo/build-deps
new file mode 100644
index 0000000000000000000000000000000000000000..1ccfbc2f47dcc52f33efec2c92339ebca0e6efbc
--- /dev/null
+++ b/modules/echo/build-deps
@@ -0,0 +1 @@
+make gcc
diff --git a/modules/echo/prebuild b/modules/echo/prebuild
new file mode 100755
index 0000000000000000000000000000000000000000..cd2864b05d58973bc07472728f0d740570889574
--- /dev/null
+++ b/modules/echo/prebuild
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# if a module has a build dependency that is not in debian/alpine
+# use this script to fetch/build/install them
+#
+# note that shared libraries produced as a result of this script will
+# not be copied from the builder image to the resulting one, so you need to
+# build them statically
+
+echo "No prebuild stage required - all dependencies are satisfied already!"
+
+exit 0
diff --git a/modules/echo/source b/modules/echo/source
new file mode 100644
index 0000000000000000000000000000000000000000..3a6ad274d57a580175bf9386bab4b0612e5bec9d
--- /dev/null
+++ b/modules/echo/source
@@ -0,0 +1 @@
+https://github.com/openresty/echo-nginx-module/archive/v0.62.tar.gz