diff --git a/.env_example b/.env_example
new file mode 100644
index 0000000000000000000000000000000000000000..f328a3ed246062bbb393b64f6aff72a80b68a311
--- /dev/null
+++ b/.env_example
@@ -0,0 +1,17 @@
+### Flask ###
+FLASK_CONFIG=production
+# SECRET_KEY=
+
+### Flask-Mail ###
+MAIL_SERVER=smtp.example.com
+MAIL_PORT=587
+MAIL_USE_TLS=true
+MAIL_USERNAME=username@example.com
+MAIL_PASSWORD=password
+MAIL_DEFAULT_SENDER=username@example.com
+
+### Opaque ###
+OPAQUE_ADMIN=admin.opaque@example.com
+
+### Flask-SQLAlchemy ###
+# SQLALCHEMY_DATABASE_URI=
diff --git a/.flaskenv b/.flaskenv
index f88b6e5b9015d74cef7f8cb429bbf93297cb1d75..47093cdaf93d07580a062a158c87bbed2c7fa72e 100644
--- a/.flaskenv
+++ b/.flaskenv
@@ -1,2 +1,2 @@
+### Flask ###
 FLASK_APP=opaque.py
-FLASK_ENV=development
diff --git a/Dockerfile b/Dockerfile
index e6a7ea0b148347395aa0c5c1f4ba7c78f959a6e6..e588a372cde57808b9dd455fc902ae487e09b248 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,18 +1,30 @@
-# pull official base image
-FROM python:3.6.9
+FROM python:3.6-alpine
 
-# set environment varibles
-ENV PYTHONDONTWRITEBYTECODE 1
-ENV PYTHONUNBUFFERED 1
 
-# set work directory
-WORKDIR /opaque
+RUN apk add build-base
 
-# Copy the current directory contents into the container at /daemon
-COPY . /opaque
 
-# Install requirements
-RUN pip install --trusted-host pypi.python.org -r requirements.txt
+RUN adduser -D opaque
+USER opaque
 
-# set permissions for entrypoint
-RUN chmod a+x flask-entrypoint.sh
+
+WORKDIR /home/opaque
+
+
+COPY app app
+COPY migrations migrations
+COPY opaque.py config.py ./
+COPY requirements.txt requirements.txt
+
+
+RUN python -m venv venv && \
+    venv/bin/pip install -r requirements.txt
+
+
+COPY docker-entrypoint.sh /usr/local/bin/
+
+
+EXPOSE 5000
+
+
+ENTRYPOINT ["docker-entrypoint.sh"]
diff --git a/app/email.py b/app/email.py
index 1e3fb3abc49e28784e2673937bfd1a27b415a6c9..072b7bc37aabf70f94b0b7a17154e680d29ece9c 100644
--- a/app/email.py
+++ b/app/email.py
@@ -10,11 +10,7 @@ def send_async_email(app, msg):
 
 
 def send_email(to, subject, template, **kwargs):
-    subject = '{} {}'.format(current_app.config['OPAQUE_MAIL_SUBJECT_PREFIX'],
-                             subject)
-    msg = Message(subject,
-                  sender=current_app.config['OPAQUE_MAIL_SENDER'],
-                  recipients=[to])
+    msg = Message('[Opaque] {}'.format(subject), recipients=[to])
     msg.body = render_template(template + '.txt.j2', **kwargs)
     msg.html = render_template(template + '.html.j2', **kwargs)
     thr = Thread(target=send_async_email,
diff --git a/app/main/views.py b/app/main/views.py
index efb2b0bd349cf750ada2c7ccd2ee28d82ec25d19..9acc9061f80c39e76307583b957aa3620274cc88 100644
--- a/app/main/views.py
+++ b/app/main/views.py
@@ -21,7 +21,7 @@ def corpus(corpus_id):
         print('Corpus not found.')
         abort(404)
 
-    dir = os.path.join(current_app.config['OPAQUE_STORAGE'],
+    dir = os.path.join(current_app.config['OPAQUE_STORAGE_DIRECTORY'],
                        str(current_user.id),
                        'corpora',
                        str(corpus.id))
@@ -44,7 +44,7 @@ def corpus_download(corpus_id):
     if not file or not corpus:
         print('File not found.')
         abort(404)
-    dir = os.path.join(current_app.config['OPAQUE_STORAGE'],
+    dir = os.path.join(current_app.config['OPAQUE_STORAGE_DIRECTORY'],
                        str(current_user.id),
                        'corpora',
                        str(corpus.id))
@@ -66,7 +66,7 @@ def dashboard():
         db.session.add(corpus)
         db.session.commit()
 
-        dir = os.path.join(app.config['OPAQUE_STORAGE'],
+        dir = os.path.join(app.config['OPAQUE_STORAGE_DIRECTORY'],
                            str(corpus.user_id),
                            'corpora',
                            str(corpus.id))
@@ -96,7 +96,7 @@ def job(job_id):
         print('Job not found.')
         abort(404)
 
-    dir = os.path.join(current_app.config['OPAQUE_STORAGE'],
+    dir = os.path.join(current_app.config['OPAQUE_STORAGE_DIRECTORY'],
                        str(current_user.id),
                        'jobs',
                        str(job.id))
@@ -130,7 +130,7 @@ def job_download(job_id):
     if not file or not job:
         print('File not found.')
         abort(404)
-    dir = os.path.join(current_app.config['OPAQUE_STORAGE'],
+    dir = os.path.join(current_app.config['OPAQUE_STORAGE_DIRECTORY'],
                        str(current_user.id),
                        'jobs',
                        str(job.id))
diff --git a/app/services/views.py b/app/services/views.py
index 96247d57aeba03179934b0212ff54bab440d3478..52c36084dc626e8e9b130465f4b49d179f60244c 100644
--- a/app/services/views.py
+++ b/app/services/views.py
@@ -26,7 +26,7 @@ def nlp():
         db.session.add(nlp_job)
         db.session.commit()
 
-        dir = os.path.join(current_app.config['OPAQUE_STORAGE'],
+        dir = os.path.join(current_app.config['OPAQUE_STORAGE_DIRECTORY'],
                            str(nlp_job.user_id),
                            'jobs',
                            str(nlp_job.id))
@@ -72,7 +72,7 @@ def ocr():
         db.session.add(ocr_job)
         db.session.commit()
 
-        dir = os.path.join(current_app.config['OPAQUE_STORAGE'],
+        dir = os.path.join(current_app.config['OPAQUE_STORAGE_DIRECTORY'],
                            str(ocr_job.user_id),
                            'jobs',
                            str(ocr_job.id))
diff --git a/app/templates/main/jobs/job.html.j2 b/app/templates/main/jobs/job.html.j2
index a9bfd2b54c19519bfbd5801355855572b006f3cc..12865aa2a18e543d90245ec5ce9338176463ffb8 100644
--- a/app/templates/main/jobs/job.html.j2
+++ b/app/templates/main/jobs/job.html.j2
@@ -161,11 +161,12 @@
         </div>
       </div>
 
+      <span class="card-title">Files</span>
       <table>
         <thead>
           <tr>
-              <th>Inputs</th>
-              <th>Results</th>
+              <th style="width: 50%;">Inputs</th>
+              <th style="width: 50%;">Results</th>
           </tr>
         </thead>
         <tbody>
@@ -179,8 +180,6 @@
               {% for result in files[file]['results'] %}
               <a href="{{ url_for('main.job_download', job_id=job.id, file=files[file]['results'][result]['path']) }}" class="waves-effect waves-light btn-small">{{ result }}</a>
               {% endfor %}
-              {% else %}
-              None
               {% endif %}
             </td>
           </tr>
diff --git a/app/templates/services/nlp.html.j2 b/app/templates/services/nlp.html.j2
index 1ba9550b7eac2c1766f2e9c522b79ffd55d2eda9..8e394f084a93bb4f47477fc1fc58ca2fa4a2cead 100644
--- a/app/templates/services/nlp.html.j2
+++ b/app/templates/services/nlp.html.j2
@@ -13,56 +13,32 @@
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Tokenisierung
-              </span>
-              <p>
-                Aufteilung eines Textes in Sätze und Wörter. Dies
-                ist zur weiteren Verarbeitung notwendig.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Tokenisierung</span>
+              <p>Aufteilung eines Textes in Sätze und Wörter. Dies ist zur weiteren Verarbeitung notwendig.</p>
             </div>
           </div>
         </div>
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Lemmatisierung
-              </span>
-              <p>
-                Reduktion der Flexionsformen eines Wortes auf dessen
-                Grundform.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Lemmatisierung</span>
+              <p>Reduktion der Flexionsformen eines Wortes auf dessen Grundform.<br><br></p>
             </div>
           </div>
         </div>
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Part-of-speech-Tagging
-              </span>
-              <p>
-                Kontext- und definitionsbezogene Zuordnung von Wörtern
-                und Satzzeichen zu Wortarten.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Part-of-speech-Tagging</span>
+              <p>Kontext- und definitionsbezogene Zuordnung von Wörtern und Satzzeichen zu Wortarten.</p>
             </div>
           </div>
         </div>
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Eigennamenerkennung
-              </span>
-              <p>
-                Identifikation von Wörtern, die eine Entität
-                beschreiben, wie Firmen- und Personennamen.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Eigennamenerkennung</span>
+              <p>Identifikation von Wörtern, die eine Entitätbeschreiben, wie Firmen- und Personennamen.</p>
             </div>
           </div>
         </div>
diff --git a/app/templates/services/ocr.html.j2 b/app/templates/services/ocr.html.j2
index 2fc3198c37f24bd5e956435b0d2f5642c53c1877..3e27289814314ac626de7eb8a89e5ed5e5c5e3a4 100644
--- a/app/templates/services/ocr.html.j2
+++ b/app/templates/services/ocr.html.j2
@@ -14,56 +14,32 @@
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Eingabe von Bilddaten
-              </span>
-              <p>
-                Über ein Auftragsformular können Bilddaten in Form von
-                PDF-Dateien hochgeladen werden.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Eingabe von Bilddaten</span>
+              <p>Über ein Auftragsformular können Bilddaten in Form von PDF-Dateien hochgeladen werden.</p>
             </div>
           </div>
         </div>
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Optische Zeichenerkennung
-              </span>
-              <p>
-                Die optische Zeichenerkennung erfolgt in der
-                Recheninfrastruktur der Plattform.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Optische Zeichenerkennung</span>
+              <p>Die optische Zeichenerkennung erfolgt in der Recheninfrastruktur der Plattform.</p>
             </div>
           </div>
         </div>
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Fehlerkorrektur
-              </span>
-              <p>
-                Je nach Qualität der Eingabedaten kann es zu
-                Fehlern kommen, die korrigiert werden sollten.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Fehlerkorrektur</span>
+              <p>Je nach Qualität der Eingabedaten kann es zu Fehlern kommen, die korrigiert werden sollten.</p>
             </div>
           </div>
         </div>
         <div class="col s12 m6">
           <div class="card z-depth-0">
             <div class="card-content">
-              <span class="card-title">
-                <i class="material-icons blue-grey-text text-darken-2 left">layers</i>
-                Weiterverarbeitung
-              </span>
-              <p>
-                Die Textdaten können weiterverarbeitet<a class="tooltipped" data-position="top" data-tooltip="Zum Beispiel durch die hier angebotene linguistische Datenverarbeitung."><sup>[*]</sup></a>
-                oder in dieser Form bereits genutzt<a class="tooltipped" data-position="top" data-tooltip="Zum Beispiel mit dem Programm &quot;AntConc&quot;."><sup>[*]</sup></a> werden.
-              </p>
+              <span class="card-title"><i class="material-icons blue-grey-text text-darken-2 left">layers</i>Weiterverarbeitung</span>
+              <p>Die Textdaten können weiterverarbeitet<a class="tooltipped" data-position="top" data-tooltip="Zum Beispiel durch die hier angebotene linguistische Datenverarbeitung."><sup>[*]</sup></a> oder in dieser Form bereits genutzt<a class="tooltipped" data-position="top" data-tooltip="Zum Beispiel mit dem Programm &quot;AntConc&quot;."><sup>[*]</sup></a> werden.</p>
             </div>
           </div>
         </div>
diff --git a/config.py b/config.py
index 1b3403011003d8c85032acc8d32bc7ba71bdbd64..f80e8f2743e849f54d04a5c31068e120cfebbd22 100644
--- a/config.py
+++ b/config.py
@@ -1,29 +1,34 @@
 import os
 
 
-basedir = os.path.abspath(os.path.dirname(__file__))
-
-
 class Config:
+    ''' ### Flask ### '''
+    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
+
+    ''' ### Flask-Mail ### '''
     MAIL_SERVER = os.environ.get('MAIL_SERVER')
     MAIL_PORT = int(os.environ.get('MAIL_PORT'))
     MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS').lower() == 'true'
     MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
     MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
-    OPAQUE_ADMIN = os.environ.get('OPAQUE_ADMIN')
-    OPAQUE_STORAGE = os.environ.get('OPAQUE_STORAGE')
-    OPAQUE_MAIL_SUBJECT_PREFIX = '[Opaque]'
-    OPAQUE_MAIL_SENDER = 'Opaque'
-    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
+    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')
+
+    ''' ### Flask-SQLAlchemy ### '''
     SQLALCHEMY_TRACK_MODIFICATIONS = False
 
+    ''' ### Opaque ### '''
+    OPAQUE_ADMIN = os.environ.get('OPAQUE_ADMIN')
+    OPAQUE_STORAGE_DIRECTORY = '/opaque_storage'
+
     @staticmethod
     def init_app(app):
         pass
 
 
 class DevelopmentConfig(Config):
+    ''' ### Flask ### '''
     DEBUG = True
+<<<<<<< HEAD
     # SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir,
     #                                                       'data_dev.sqlite')
     SQLALCHEMY_DATABASE_URI = \
@@ -34,22 +39,29 @@ class DevelopmentConfig(Config):
             port=os.environ.get('POSTGRES_PORT'),
             db=os.environ.get('POSTGRES_DB_NAME'))
     print(SQLALCHEMY_DATABASE_URI)
+=======
+>>>>>>> e3db2ecd1e52b4993b91e1a1c3501c0fc775de1d
 
-
-class TestingConfig(Config):
-    TESTING = True
-    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
-        'sqlite://'
-    WTF_CSRF_ENABLED = False
+    ''' ### Flask-SQLAlchemy ### '''
+    SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(
+        os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                     'data_dev.sqlite')
+    )
 
 
-# class ProductionConfig(Config):
+class ProductionConfig(Config):
+    ''' ### Flask-SQLAlchemy ### '''
+    SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
 
 
 config = {
     'development': DevelopmentConfig,
+<<<<<<< HEAD
     'testing': TestingConfig,
     #'production': ProductionConfig,
 
+=======
+    'production': ProductionConfig,
+>>>>>>> e3db2ecd1e52b4993b91e1a1c3501c0fc775de1d
     'default': DevelopmentConfig
 }
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..535d86a0675c5438727ca300658882c64452364a
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# If no argument is given, start Opaque
+if [ $# -eq 0 ]
+then
+  venv/bin/python opaque.py
+else
+  if [[ $1 == "--create-db" ]]
+  then
+    flask db init
+    flask db upgrade
+  fi
+fi
diff --git a/flask-entrypoint.sh b/flask-entrypoint.sh
deleted file mode 100644
index ed4b193f9a836d4177495fc483e909972058693b..0000000000000000000000000000000000000000
--- a/flask-entrypoint.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash -x
-
-python opaque.py
diff --git a/opaque.py b/opaque.py
index 3f8483afdbdc32c6be9ad16067663ca12f5b616d..8d4b01010a64cea0f553c3a81f384a6ec9e7ea86 100644
--- a/opaque.py
+++ b/opaque.py
@@ -1,7 +1,5 @@
 import eventlet
 eventlet.monkey_patch()
-from dotenv import load_dotenv
-load_dotenv()
 from app import create_app, db, socketio
 from app.models import Corpus, User, Role, Permission, Job
 from flask_migrate import Migrate
diff --git a/requirements.txt b/requirements.txt
index 3fce165f3eb186f2fb15a8f2c4036a4a04e25e4b..a36d129ce19113878f34c43eb179b4d891ed1c85 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,9 @@ Flask-SQLAlchemy
 Flask-Table
 Flask-WTF
 jsonpatch
+<<<<<<< HEAD
 psycopg2
 python-dotenv
+=======
+>>>>>>> e3db2ecd1e52b4993b91e1a1c3501c0fc775de1d
 redis