diff --git a/.env_example b/.env_example
new file mode 100644
index 00000000..f328a3ed
--- /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 f88b6e5b..47093cda 100644
--- a/.flaskenv
+++ b/.flaskenv
@@ -1,2 +1,2 @@
+### Flask ###
 FLASK_APP=opaque.py
-FLASK_ENV=development
diff --git a/Dockerfile b/Dockerfile
index e6a7ea0b..e588a372 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 1e3fb3ab..072b7bc3 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 efb2b0bd..9acc9061 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 96247d57..52c36084 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 a9bfd2b5..12865aa2 100644
--- a/app/templates/main/jobs/job.html.j2
+++ b/app/templates/main/jobs/job.html.j2
@@ -161,11 +161,12 @@
         
       
 
+      Files
       
         
           
-              | Inputs | 
-              Results | 
+              Inputs | 
+              Results | 
           
         
         
@@ -179,8 +180,6 @@
               {% for result in files[file]['results'] %}
               {{ result }}
               {% endfor %}
-              {% else %}
-              None
               {% endif %}
             
           
diff --git a/app/templates/services/nlp.html.j2 b/app/templates/services/nlp.html.j2
index 1ba9550b..8e394f08 100644
--- a/app/templates/services/nlp.html.j2
+++ b/app/templates/services/nlp.html.j2
@@ -13,56 +13,32 @@
         
           
             
-              
-                layers
-                Tokenisierung
-              
-              
-                Aufteilung eines Textes in Sätze und Wörter. Dies
-                ist zur weiteren Verarbeitung notwendig.
-              
+              
layersTokenisierung
+              
Aufteilung eines Textes in Sätze und Wörter. Dies ist zur weiteren Verarbeitung notwendig.
              
            
          
         
           
             
-              
-                layers
-                Lemmatisierung
-              
-              
-                Reduktion der Flexionsformen eines Wortes auf dessen
-                Grundform.
-              
+              
layersLemmatisierung
+              
Reduktion der Flexionsformen eines Wortes auf dessen Grundform.
              
            
          
         
           
             
-              
-                layers
-                Part-of-speech-Tagging
-              
-              
-                Kontext- und definitionsbezogene Zuordnung von Wörtern
-                und Satzzeichen zu Wortarten.
-              
+              
layersPart-of-speech-Tagging
+              
Kontext- und definitionsbezogene Zuordnung von Wörtern und Satzzeichen zu Wortarten.
              
            
          
         
           
             
-              
-                layers
-                Eigennamenerkennung
-              
-              
-                Identifikation von Wörtern, die eine Entität
-                beschreiben, wie Firmen- und Personennamen.
-              
+              
layersEigennamenerkennung
+              
Identifikation von Wörtern, die eine Entitätbeschreiben, wie Firmen- und Personennamen.
              
            
          
diff --git a/app/templates/services/ocr.html.j2 b/app/templates/services/ocr.html.j2
index 2fc3198c..3e272898 100644
--- a/app/templates/services/ocr.html.j2
+++ b/app/templates/services/ocr.html.j2
@@ -14,56 +14,32 @@
         
           
             
-              
-                layers
-                Eingabe von Bilddaten
-              
-              
-                Über ein Auftragsformular können Bilddaten in Form von
-                PDF-Dateien hochgeladen werden.
-              
+              
layersEingabe von Bilddaten
+              
Über ein Auftragsformular können Bilddaten in Form von PDF-Dateien hochgeladen werden.
              
            
          
         
           
             
-              
-                layers
-                Optische Zeichenerkennung
-              
-              
-                Die optische Zeichenerkennung erfolgt in der
-                Recheninfrastruktur der Plattform.
-              
+              
layersOptische Zeichenerkennung
+              
Die optische Zeichenerkennung erfolgt in der Recheninfrastruktur der Plattform.
              
            
          
         
           
             
-              
-                layers
-                Fehlerkorrektur
-              
-              
-                Je nach Qualität der Eingabedaten kann es zu
-                Fehlern kommen, die korrigiert werden sollten.
-              
+              
layersFehlerkorrektur
+              
Je nach Qualität der Eingabedaten kann es zu Fehlern kommen, die korrigiert werden sollten.
              
            
          
         
           
             
-              
-                layers
-                Weiterverarbeitung
-              
-              
-                Die Textdaten können weiterverarbeitet[*]
-                oder in dieser Form bereits genutzt[*] werden.
-              
+              
layersWeiterverarbeitung
+              
Die Textdaten können weiterverarbeitet[*] oder in dieser Form bereits genutzt[*] werden.
              
            
          
diff --git a/config.py b/config.py
index 1b340301..f80e8f27 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
+
+    ''' ### Flask-SQLAlchemy ### '''
+    SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(
+        os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                     'data_dev.sqlite')
+    )
 
 
-class TestingConfig(Config):
-    TESTING = True
-    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
-        'sqlite://'
-    WTF_CSRF_ENABLED = False
-
-
-# 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 00000000..535d86a0
--- /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 ed4b193f..00000000
--- 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 3f8483af..8d4b0101 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 3fce165f..a36d129c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,9 @@ Flask-SQLAlchemy
 Flask-Table
 Flask-WTF
 jsonpatch
+<<<<<<< HEAD
 psycopg2
 python-dotenv
+=======
+>>>>>>> e3db2ecd1e52b4993b91e1a1c3501c0fc775de1d
 redis