diff --git a/README.md b/README.md
index b34f9cf..8f033cf 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@ vi 00-install.sh
| MariaDB | 3306 (MySQL) | N/A | N/A |
| Mifos X | 8012 (HTTP) | 8812 | 8412 |
| Motech | 8013 (HTTP) | 8813 | 8413 |
+| OpenDataKit | 8015 (HTTP) | 8815 | 8415 |
| OpenMapKit | 8007 (HTTP) | 8807 | 8407 |
| Pan.do/ra | 8002 (HTTP) | 8802 | 8402 |
| Postfix | 25 (SMTP) | N/A | N/A |
diff --git a/basic/srv/portal/index.html b/basic/srv/portal/index.html
index 0920dd4..b498a5f 100644
--- a/basic/srv/portal/index.html
+++ b/basic/srv/portal/index.html
@@ -78,7 +78,7 @@
Sběr dat s pomocí smartphone.
diff --git a/opendatakit.sh b/opendatakit.sh
new file mode 100755
index 0000000..0440af8
--- /dev/null
+++ b/opendatakit.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+SOURCE_DIR=$(realpath $(dirname "${0}"))/opendatakit
+
+# Check prerequisites
+docker image ls | grep -q postgres || $(realpath $(dirname "${0}"))/postgres.sh
+docker image ls | grep -q postfix || $(realpath $(dirname "${0}"))/postfix.sh
+docker image ls | grep -q tomcat || $(realpath $(dirname "${0}"))/tomcat.sh
+
+# Build Docker container
+docker build -t opendatakit ${SOURCE_DIR}
+
+# Create databases
+export OPENDATAKIT_PWD=$(head -c 18 /dev/urandom | base64)
+envsubst <${SOURCE_DIR}/createdb.sql | docker exec -i postgres psql
+
+# Configure OpenDataKit
+export OPENDATAKIT_ADMIN_USER=admin
+export OPENDATAKIT_ADMIN_REALM=spotter
+mkdir -p /srv/opendatakit/conf /srv/opendatakit/data
+envsubst <${SOURCE_DIR}/srv/opendatakit/conf/jdbc.properties >/srv/opendatakit/conf/jdbc.properties
+envsubst <${SOURCE_DIR}/srv/opendatakit/conf/security.properties >/srv/opendatakit/conf/security.properties
+cp ${SOURCE_DIR}/srv/opendatakit/conf/server.xml /srv/opendatakit/conf/server.xml
+cp ${SOURCE_DIR}/srv/opendatakit/update-ip.sh /srv/opendatakit/update-ip.sh
+chown -R 8015:8015 /srv/opendatakit/conf /srv/opendatakit/data
+
+# Create OpenDataKit service
+cp ${SOURCE_DIR}/etc/init.d/opendatakit /etc/init.d/opendatakit
+rc-update add opendatakit
+service opendatakit start
+
+# Update admin account
+export OPENDATAKIT_ADMIN_PWD=$(head -c 12 /dev/urandom | base64)
+export OPENDATAKIT_ADMIN_SALT=$(head -c 4 /dev/urandom | hexdump -e '"%x"') # Must be 8 characters
+export OPENDATAKIT_ADMIN_BASIC_HASH=$(echo -n "${OPENDATAKIT_ADMIN_PWD}{${OPENDATAKIT_ADMIN_SALT}}" | sha1sum | tr -d " -")
+export OPENDATAKIT_ADMIN_DIGEST_HASH=$(echo -n "${OPENDATAKIT_ADMIN_USER}:${OPENDATAKIT_ADMIN_REALM}:${OPENDATAKIT_ADMIN_PWD}" | md5sum | tr -d " -")
+until docker logs opendatakit 2>&1 | grep -q 'org.apache.catalina.startup.Catalina.start'; do
+ sleep 1
+done
+envsubst <${SOURCE_DIR}/adminpwd.sql | docker exec -i postgres psql opendatakit
+
+# Create nginx app definition
+cp ${SOURCE_DIR}/etc/nginx/conf.d/opendatakit.conf /etc/nginx/conf.d/opendatakit.conf
+service nginx reload
+
+# Add portal application definition
+portal-app-manager opendatakit "https://{host}:8415/aggregate/" "${OPENDATAKIT_ADMIN_USER}" "${OPENDATAKIT_ADMIN_PWD}"
+portal-app-manager opendatakit-clients -p clienturl "http://{host}:8815/aggregate"
diff --git a/opendatakit/Dockerfile b/opendatakit/Dockerfile
new file mode 100644
index 0000000..29a9279
--- /dev/null
+++ b/opendatakit/Dockerfile
@@ -0,0 +1,29 @@
+FROM tomcat
+MAINTAINER Disassembler
+
+RUN \
+ # Install build dependencies
+ apk --no-cache add --virtual .deps git git-lfs openjdk8 \
+ # Clone ODK aggregate
+ && git clone --depth 1 https://github.com/opendatakit/aggregate.git /srv/odk \
+ # Compile Java web archive
+ && cd /srv/odk \
+ && cp gradle.properties.example gradle.properties \
+ && ./gradlew war \
+ # Deploy web archive
+ && mkdir /srv/tomcat/webapps/aggregate \
+ && unzip build/libs/aggregate-*.war -d /srv/tomcat/webapps/aggregate \
+ # Create OS user
+ && addgroup -S -g 8015 odk \
+ && adduser -S -u 8015 -h /srv/tomcat -s /bin/false -g odk -G odk odk \
+ && chown -R odk:odk /srv/tomcat/conf /srv/tomcat/logs /srv/tomcat/temp /srv/tomcat/webapps /srv/tomcat/work \
+ # Cleanup
+ && apk --no-cache del .deps \
+ && rm -rf /root/.gradle /root/.java /srv/odk
+
+# VOLUME ["/srv/tomcat/.motech"]
+EXPOSE 8015
+
+USER odk
+WORKDIR /srv/tomcat
+CMD ["catalina.sh", "run"]
diff --git a/opendatakit/adminpwd.sql b/opendatakit/adminpwd.sql
new file mode 100644
index 0000000..f5393c0
--- /dev/null
+++ b/opendatakit/adminpwd.sql
@@ -0,0 +1 @@
+UPDATE _registered_users SET "BASIC_AUTH_PASSWORD" = '${OPENDATAKIT_ADMIN_BASIC_HASH}', "BASIC_AUTH_SALT" = '${OPENDATAKIT_ADMIN_SALT}', "DIGEST_AUTH_PASSWORD" = '${OPENDATAKIT_ADMIN_DIGEST_HASH}' WHERE "LOCAL_USERNAME" = '${OPENDATAKIT_ADMIN_USER}';
diff --git a/opendatakit/createdb.sql b/opendatakit/createdb.sql
new file mode 100644
index 0000000..897f489
--- /dev/null
+++ b/opendatakit/createdb.sql
@@ -0,0 +1,4 @@
+CREATE ROLE opendatakit NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT LOGIN ENCRYPTED PASSWORD '${OPENDATAKIT_PWD}';
+CREATE DATABASE opendatakit;
+REVOKE ALL ON DATABASE opendatakit FROM public;
+ALTER DATABASE opendatakit OWNER TO opendatakit;
diff --git a/opendatakit/etc/init.d/opendatakit b/opendatakit/etc/init.d/opendatakit
new file mode 100755
index 0000000..49b037e
--- /dev/null
+++ b/opendatakit/etc/init.d/opendatakit
@@ -0,0 +1,29 @@
+#!/sbin/openrc-run
+
+description="OpenDataKit docker container"
+
+depend() {
+ need docker net postgres
+ use dns logger netmount postfix
+}
+
+start_pre() {
+ /srv/opendatakit/update-ip.sh
+}
+
+start() {
+ /usr/bin/docker run -d --rm \
+ --name opendatakit \
+ -h opendatakit \
+ --link postfix \
+ --link postgres \
+ -p 127.0.0.1:8015:8015 \
+ -v /srv/opendatakit/conf/server.xml:/srv/tomcat/conf/server.xml \
+ -v /srv/opendatakit/conf/jdbc.properties:/srv/tomcat/webapps/aggregate/WEB-INF/classes/jdbc.properties \
+ -v /srv/opendatakit/conf/security.properties:/srv/tomcat/webapps/aggregate/WEB-INF/classes/security.properties \
+ opendatakit
+}
+
+stop() {
+ /usr/bin/docker stop opendatakit
+}
diff --git a/opendatakit/etc/nginx/conf.d/opendatakit.conf b/opendatakit/etc/nginx/conf.d/opendatakit.conf
new file mode 100644
index 0000000..fcba614
--- /dev/null
+++ b/opendatakit/etc/nginx/conf.d/opendatakit.conf
@@ -0,0 +1,11 @@
+server {
+ listen [::]:8815 ipv6only=off;
+ listen [::]:8415 ssl http2 ipv6only=off;
+
+ access_log /var/log/nginx/opendatakit.access.log;
+ error_log /var/log/nginx/opendatakit.error.log;
+
+ location / {
+ proxy_pass http://127.0.0.1:8015;
+ }
+}
diff --git a/opendatakit/srv/opendatakit/conf/jdbc.properties b/opendatakit/srv/opendatakit/conf/jdbc.properties
new file mode 100644
index 0000000..43d8134
--- /dev/null
+++ b/opendatakit/srv/opendatakit/conf/jdbc.properties
@@ -0,0 +1,6 @@
+jdbc.driverClassName=org.postgresql.Driver
+jdbc.resourceName=jdbc/odk_aggregate
+jdbc.url=jdbc:postgresql://postgres/opendatakit?autoDeserialize=true
+jdbc.username=opendatakit
+jdbc.password=${OPENDATAKIT_PWD}
+jdbc.schema=public
diff --git a/opendatakit/srv/opendatakit/conf/security.properties b/opendatakit/srv/opendatakit/conf/security.properties
new file mode 100644
index 0000000..344d973
--- /dev/null
+++ b/opendatakit/srv/opendatakit/conf/security.properties
@@ -0,0 +1,48 @@
+# Either basic or digest
+security.server.deviceAuthentication=basic
+
+# Choose whether to secure everything with https or allow http access.
+#
+# NOTE: changes also needed to:
+# -- server.xml (Tomcat configuration file) to set up the secure channel
+#
+# issue 648 - REQUIRES_INSECURE_CHANNEL is now the default instead of ANY_CHANNEL
+# there are various edge cases that have not been tested in the UI for
+# allowing arbitrary accesses, as the session cookie and authentication
+# do get set for a specific http: or https: scheme and are not transferrable.
+#
+# should be REQUIRES_SECURE_CHANNEL but can't unless SSL is available.
+security.server.secureChannelType=REQUIRES_INSECURE_CHANNEL
+
+#security.server.secureChannelType=REQUIRES_SECURE_CHANNEL
+# either REQUIRES_INSECURE_CHANNEL to secure nothing
+# or REQUIRES_SECURE_CHANNEL to secure everything
+# or perhaps ANY_CHANNEL when running through a proxy server
+security.server.channelType=REQUIRES_INSECURE_CHANNEL
+#security.server.channelType=REQUIRES_SECURE_CHANNEL
+
+
+# When running under Tomcat, you need to set the hostname and port for
+# the server so that the background tasks can generate properly-constructed
+# links in their documents and in their publications to the
+# external services.
+#
+# This is configured during install. If blank, discovers an IP address
+security.server.hostname=
+security.server.port=8815
+security.server.securePort=8415
+
+wink.handlersFactoryClass=org.opendatakit.aggregate.odktables.impl.api.wink.AppEngineHandlersFactory
+
+# e-mail of designated superuser. This must be a user that has an OAuth2
+# login hosted by a remote server (i.e., this must be a gmail account).
+# this should be of the form: 'mailto:user@gmail.com'
+security.server.superUser=
+
+# Define a superUserUsername to insert an ODK Aggregate username that can
+# access the server. The initial password for this username is 'aggregate'
+security.server.superUserUsername=${OPENDATAKIT_ADMIN_USER}
+
+# realm definition
+# realmString -- what should be sent to users when BasicAuth or DigestAuth is done
+security.server.realm.realmString=${OPENDATAKIT_ADMIN_REALM}
diff --git a/opendatakit/srv/opendatakit/conf/server.xml b/opendatakit/srv/opendatakit/conf/server.xml
new file mode 100644
index 0000000..ebd8590
--- /dev/null
+++ b/opendatakit/srv/opendatakit/conf/server.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/opendatakit/srv/opendatakit/update-ip.sh b/opendatakit/srv/opendatakit/update-ip.sh
new file mode 100755
index 0000000..a268d6b
--- /dev/null
+++ b/opendatakit/srv/opendatakit/update-ip.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+URL=$(ip route get 1 | awk '{print $NF;exit}')
+sed -i "s|\(^\s\+proxyName\).*|\1=\"${URL}\"|" /srv/opendatakit/conf/server.xml