diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000000000000000000000000000000000..8977349c5bd7350bea0b06af9904796f4ab29554
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,55 @@
+# This has to be set to any good enough random string. E.g. something that
+# `pwgen -s 32 1` would print out. If you want to know more about this go to
+# https://docs.djangoproject.com/en/3.2/ref/settings/#secret-key
+# (mandatory setting)
+SECRET_KEY=put-something-awesomely-random-here
+
+# A comma-separated list of hostnames/IPs Django should listen to. For a
+# production setup this will be something like aura.example.org, for a dev
+# setup you might just use the default settings.
+# (default: 127.0.0.1, localhost)
+#ALLOWED_HOSTS=
+
+# A comma-separated list of URIs where the webclients live that should be able
+# to access the steering API. In particular the dashboard. Might not be needed
+# in a production setup if steering and dashboard share the same domain. In
+# a dev setup the defaults might be just fine.
+# (default: http://127.0.0.1:8080, http://localhost:8080)
+#CORS_ORIGIN_WHITELIST=
+
+# The database settings.
+# if you use a dev environment where django is not running inside a docker
+# container, but you use the postgres container for the db and map its port,
+# then use localhost as the database hostname
+# (default host: steering-postgres ; or if RUN_IN_DOCKER is False: localhost)
+# (default port: 5432)
+# (default name: steering)
+# (default user: steering)
+# (pass is a mandatory setting)
+#DBHOST=
+#DBPORT=
+#DBNAME=
+#DBUSER=
+DBPASS=change-to-something-secure
+
+# The timezone of this server. For a list of all available tz database names see
+# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+# (default: Europe/Vienna)
+#TIME_ZONE=
+
+# The language code for the localization of this server. For a list of available
+# codes see http://www.i18nguy.com/unicode/language-identifiers.html
+# (default: de)
+#LANGUAGE_CODE=
+
+# If steering is run inside a docker container. This will be de default for a
+# production deployment. In a dev scenario you might still want to have the
+# database in its container, but run the steering dev server directly on your
+# host. In this case make this False.
+# (default: True)
+#RUN_IN_DOCKER=
+
+# This should be turned on only for your development environment unless you
+# know exactly what you are doing and what the consequences are.
+# (default: False)
+#DEBUG=
diff --git a/.gitignore b/.gitignore
index ce00991647ecbcf8584f45253e4dec372c687b83..922a1a5dde993acbe15f09edbc2830f3f01294aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,5 @@ pv/cache/*
 pv/mysql.cnf
 pv/postgresql.cnf
 venv/
-python/
\ No newline at end of file
+python/
+.env
diff --git a/Dockerfile b/Dockerfile
index f95a8b3d108aa9d1413b771f7a7e2a94d97686c8..b4dcb1ac71b02bbc3e907ee8af46e061a6b09f34 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,21 @@
-FROM python:3.6
+FROM python:3.8 AS base
 
-COPY requirements.txt /tmp/
+ENV PYTHONUNBUFFERED=1
 
-RUN pip install --no-cache-dir -r /tmp/requirements.txt
+WORKDIR /aura
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
 
-#RUN python /tmp/manage.py migrate
-#ENTRYPOINT ["/tmp/entrypoint.sh"]
+EXPOSE 8000
 
-# Superuser parameters are --username USERNAME and --email EMAIL but there is no password
-# RUN python /tmp/manage.py createsuperuser
+
+FROM base AS dev
+
+#VOLUME .
+CMD ["python", "manage.py", "runserver", "0.0.0.0:8000" ]
+
+
+FROM base AS prod
+
+COPY . .
+CMD ["sh", "-c", "gunicorn -b 0.0.0.0:8000 -w $(nproc) pv.wsgi"]
diff --git a/README.rst b/README.rst
index bf24008c43b379cbc826d9316dfef4cb4b770c29..e7db5d1143372defa1f49b71f720d2cf6e3d859e 100644
--- a/README.rst
+++ b/README.rst
@@ -5,28 +5,32 @@ AURA Steering: Program Scheduler
 Installation
 ------------
 
-If you have a working Docker environment, do::
+If you want to have quick dev server and already have the *steering-postgres*
+container of the *aura-web* repository running, follow the steps outlined in the
+*Setting up the configuration* section and do::
 
-    $ docker build -t pv_container .
-    $ docker run -p 8000:8000 -d pv_container:latest
+    $ docker build -t steering-django --target dev .
+    $ docker run --rm -v $(pwd):/aura --network auraweb_auranet steering-django:latest python manage.py migrate
+    $ docker run -it --rm -v $(pwd):/aura --network auraweb_auranet steering-django:latest python manage.py createsuperuser
+    $ docker run --rm -p 8000:8000 -v $(pwd):/aura --network auraweb_auranet steering-django:latest
 
-and log into it at http://127.0.0.1:8000/admin/ with username "admin" and password "admin". Full setup without Docker is done as described below.
+and log into it at http://127.0.0.1:8000/admin/ with the credentials you have
+set in the ``createsuperuser`` step. Once this is done, every other time you
+want to start a *steering-django* container, you will only have to use the
+last command.
+
+**Full setup without Docker** is done as described below.
 
 To get setup you must have the following installed:
 
-* PostgresSQL or MySQL client development libraries
+* PostgresSQL (except you are using the aura-web docker container *steering-postgres* for it)
 * JPEG library development files
 * Python 3.8 or later including development files
 
-In Debian or Ubuntu (or derivatives) you should be able to achieve this with this command:
-
-* Using PostgreSQL::
+In Debian or Ubuntu (or derivatives) you should be able to achieve this with this command::
 
     $ sudo apt-get install postgresql postgresql-contrib libjpeg-dev python3 python3-dev
 
-* Using MySQL::
-
-    $ sudo apt-get install libmysqlclient-dev libjpeg-dev python3 python3-dev
 
 Setting up the environment
 --------------------------
@@ -44,57 +48,21 @@ Change into the base directory of this software and install the project dependen
 Setting up the configuration
 ----------------------------
 
-By default the project is set up to run on a SQLite database.
-
-Create a file ``pv/local_settings.py`` and add at least the following line::
-
-    SECRET_KEY = 'secret key'
-
-(obviously replacing "secret key" with a key of your choice).
-
-Setting up PostgreSQL
----------------------
-
-We recommend using PostgreSQL in order to be able to use the collation utf8mb64_unicode_ci and thus being able to display all languages.
-
-To use PostgreSQL, add the following to your ``local_settings.py`` (before migrating) and add your credentials::
-
-    DATABASES = {
-        'default': {
-            'ENGINE': 'django.db.backends.postgresql',
-            'NAME': '',
-            'USER': '',
-            'PASSWORD': '',
-            'HOST': 'localhost',
-            'PORT': '5432'
-        }
-    }
+Copy the ``.env.example`` file to ``.env`` and change the values accordingly.
+You have to at least provide the ``SECRET_KEY`` and the ``DBPASS`` values
+for Django to start. The file provides extensive comments on all the settings.
+For a developments environment the defaults should be just fine if you use the
+*steering-postgres* docker container from the *aura-web* repository. If you
+want to create your own database on you local machine, you will have to use
+*steering* as the database and user name, or adopt the ``DB*`` values
+accordingly.
 
-Setting up MySQL
-----------------
+Also be aware that there is a ``RUN_IN_DOCKER`` setting that is ``True`` by
+default. This should be fine for a production environment where Django is
+run inside a container. If you follow these steps here to create your own
+development environment you have to set this setting to ``False``. You also
+might want to set ``DEBUG`` to ``True`` for your development environment.
 
-**Note:** When adding your database, make sure you **don't** use the collation utf8mb4_unicode_ci or you will get a key length error during migration. (use e.g. utf8_general_ci instead).
-
-To use MySQL, add the following to your ``local_settings.py`` (before migrating)::
-
-    DATABASES = {
-        'default': {
-            'ENGINE': 'django.db.backends.mysql',
-            'OPTIONS': {
-                'read_default_file': os.path.join(PROJECT_DIR, 'mysql.cnf'),
-            },
-        }
-    }
-
-Create a file ``pv/mysql.cnf`` and give your MySQL credentials::
-
-    [client]
-    database =
-    host = localhost
-    port = 3306
-    user =
-    password =
-    default-character-set = utf8
 
 Setting up the database
 -----------------------
@@ -119,6 +87,30 @@ In development you should run::
     (python)$ python manage.py runserver
 
 
-After this you can open http://127.0.0.1:8000/admin in your browser and log in with username "admin" and password "admin".
+After this you can open http://127.0.0.1:8000/admin in your browser and log in
+with the credentials you have chosen in the ``createsuperuser`` command.
+
+If you are using some placeholder credentials, make sure to change your password
+by visiting http://127.0.0.1:8000/admin/auth/user/1/password/
+
+Configuring OpenID Connect clients
+----------------------------------
+
+To make AuRa usable, you have to set up OpenID Connect (OIDC) clients for the
+*dashboard* and *tank*, so they can make authenticated requests on behalf of
+the user against the *steering* API.
+
+To do so, you can either visit the Django admin interface and create an RSA key
+as well as two clients, or do so programmatically by running::
+
+    (python)$ python manage.py creatersakey
+    (python)$ python manage.py create_oidc_client dashboard public -r "id_token token" -u https://aura-test.o94.at/oidc_callback.html -u https://aura-test.o94.at/oidc_callback_silentRenew.html -p https://aura-test.o94.at/
+    (python)$ python manage.py create_oidc_client tank confidential -r "code" -u https://aura-test.o94.at/tank/auth/oidc/callback
+
+In these examples you will have to *https://aura-test.o94.at* and
+*https://aura-test.o94.at/tank_with* with wherever *dashboard* and *tank* are
+running in your setup. In a local development environment this might be
+something like *http://localhost:8080* and *http://localhost:4000* respectively.
 
-Make sure to change your password by visiting http://127.0.0.1:8000/admin/auth/user/1/password/
+The client id and in case of the tank also the client secret are then needed for
+the configuration of those components.
diff --git a/program/management/commands/create_oidc_client.py b/program/management/commands/create_oidc_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..b60503caf737ae7ca2b30e827ba470d6281aa0d1
--- /dev/null
+++ b/program/management/commands/create_oidc_client.py
@@ -0,0 +1,101 @@
+from django.core.management.base import BaseCommand, CommandError
+import json, sys, random, string
+from oidc_provider.models import Client, ResponseType
+
+
+class Command(BaseCommand):
+    help = 'Sets up an OIDC client / relaying party. For details check out the' + \
+        'section on Relying Parties at https://django-oidc-provider.readthedocs.io'
+
+    def add_arguments(self, parser):
+        parser.add_argument('name', type=str,
+            help='A label that you associate with this client')
+        parser.add_argument('client_type', type=str, choices=['public', 'confidential'],
+            help='The type of client can be either public or confidential')
+        parser.add_argument('--no-require-consent', dest='require_consent', action='store_false',
+            help='By default user consent is required. Use this to skip user consent.')
+        parser.add_argument('--no-reuse-consent', dest='reuse_consent', action='store_false',
+            help='By default user consent will be reused. Use this if the user should provide consent on every login.')
+        parser.set_defaults(require_consent=True, reuse_consent=True)
+        parser.add_argument('-u', '--redirect-uri', type=str, action='append',
+            help='Redirect URI after successful authentication. Can be used more than once.')
+        parser.add_argument('-p', '--post-logout-redirect', type=str, action='append',
+            help='Post logout redirect URI. Can be used more than once.')
+        parser.add_argument('-s', '--scope', type=str, action='append',
+            help='Authorized scope values for this client. Can be used more than once.')
+        parser.add_argument('-r', dest='response_types', action='append',
+            choices=['code', 'id_token', 'id_token token', 'code token', 'code id_token', 'code id_token token'],
+            help='The type of response the client will get.')
+        parser.add_argument('-i', '--id-only', dest='id_only', action='store_true',
+            help='Do not print anything else then the ID of the newly created client '+\
+                 '(and the client secret in case of confidential clients).')
+        parser.set_defaults(id_only=False)
+
+
+    def handle(self, *args, **options):
+        # generate a new client ID and secret
+        client_id = False
+        counter = 0
+        while not client_id:
+            client_id = random.randint(100000, 999999)
+            counter += 1
+            if counter > 10000:
+                raise CommandError('Could not find a free client_id. Already'+\
+                    ' tried 10000 times. There seems to be something seriously'+\
+                    ' wrong with your setup. Please inspect manually.')
+            try:
+                Client.objects.get(client_id=client_id)
+            except Client.DoesNotExist:
+                pass
+            else:
+                client_id = False
+
+        client_secret = ''
+        if options['client_type'] == 'confidential':
+            client_secret = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(32))
+
+        # initialize lists if no option was provided
+        if options['redirect_uri'] is None:
+            options['redirect_uri'] = []
+        if options['post_logout_redirect'] is None:
+            options['post_logout_redirect'] = []
+        if options['scope'] is None:
+            options['scope'] = []
+
+        if not options["id_only"]:
+            self.stdout.write(f'Creating client with name {options["name"]}')
+        try:
+            c = Client(
+                client_id=client_id,
+                client_secret=client_secret,
+                name=options['name'], client_type=options['client_type'],
+                redirect_uris=options['redirect_uri'],
+                require_consent=options['require_consent'],
+                reuse_consent=options['reuse_consent'],
+                post_logout_redirect_uris=options['post_logout_redirect'],
+                scope=options['scope'],
+            )
+            c.save()
+        except:
+            raise CommandError('Could not create an OpenID connect client' +\
+                f' due to the following error: {sys.exc_info()}')
+
+
+        if options['response_types']:
+            try:
+                for r_value in options['response_types']:
+                    r = ResponseType.objects.get(value=r_value)
+                    c.response_types.add(r)
+            except:
+                raise CommandError('Client was stored, but could not set response_types'+\
+                    f' due to the following error: {sys.exc_info()}')
+
+        if options["id_only"]:
+            if options['client_type'] == 'confidential':
+                self.stdout.write(f'{c.client_id} {c.client_secret}')
+            else:
+                self.stdout.write(f'{c.client_id}')
+        else:
+            self.stdout.write(f'Successfully created new OIDC client, with ID: {c.client_id}')
+            if options['client_type'] == 'confidential':
+                self.stdout.write(f'The secret for this confidential client is: {c.client_secret}')
diff --git a/pv/local_settings.py.sample b/pv/local_settings.py.sample
deleted file mode 100644
index 04460116ada102d245fa136577f8524e84da9eec..0000000000000000000000000000000000000000
--- a/pv/local_settings.py.sample
+++ /dev/null
@@ -1,40 +0,0 @@
-
-import os
-
-from corsheaders.defaults import default_headers
-
-CONFIG_DIR = '/etc/aura/'
-DB_CONFIG = 'steering.mysql.cnf'
-
-SECRET_KEY = '---some-secred-key---'
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.mysql',
-        'OPTIONS': {
-            'read_default_file': os.path.join(CONFIG_DIR, DB_CONFIG),
-        },
-    }
-}
-
-CORS_ALLOW_CREDENTIALS = True
-CORS_ORIGIN_WHITELIST = (
-    'http://localhost:8080'
-    # 'https://aura-test.o94.at',
-    # 'https://aura-test.o94.at:443',
-)
-CORS_ALLOW_HEADERS = list(default_headers) + [
-    'content-disposition',
-]
-
-# Comment out the following for temporary debugging, if you want to use the
-# native DRF web forms
-"""
-REST_FRAMEWORK = {
-    # Use Django's standard `django.contrib.auth` permissions,
-    # or allow read-only access for unauthenticated users.
-    'DEFAULT_PERMISSION_CLASSES': [
-        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
-    ],
-}
-"""
diff --git a/pv/settings.py b/pv/settings.py
index 2faeb9b6fff125b8424d820b36bf90b9588bef83..365d775513ab97978ee724ff3bd0c68ac1fe3d51 100644
--- a/pv/settings.py
+++ b/pv/settings.py
@@ -1,6 +1,8 @@
 # Django settings for pv project.
 
 import os.path
+import environ
+from corsheaders.defaults import default_headers
 
 # Paths
 
@@ -18,35 +20,45 @@ STATIC_URL = '/static/'
 
 ROOT_URLCONF = 'pv.urls'
 
-DEBUG = True
+env = environ.Env()
+env.read_env(env_file=PROJECT_DIR+'/../.env')
+
+DOCKER = env.bool('RUN_IN_DOCKER', default=True)
+DEBUG = env.bool('DEBUG', default=False)
 SITE_ID = 1
 ADMINS = ()
 MANAGERS = ADMINS
 
 # Must be set if DEBUG is False
-ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
+ALLOWED_HOSTS = env.list('HOSTNAMES', default=['127.0.0.1', 'localhost'])
 
 # Whitelist IPs that access the API
-CORS_ORIGIN_WHITELIST = (
-    'http://localhost',
-    'http://localhost:8080'
-)
+CORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST', default=(
+    'http://localhost:8080',
+    'http://127.0.0.1:8080'
+))
+CORS_ALLOW_CREDENTIALS = True
+CORS_ALLOW_HEADERS = list(default_headers) + [
+    'content-disposition',
+]
 
 # Define which database backend to use for our apps
 DATABASES = {
     # SQLITE
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(PROJECT_DIR, 'dev_data.sqlite'),
-    },
+    #'default': {
+    #    'ENGINE': 'django.db.backends.sqlite3',
+    #    'NAME': os.path.join(PROJECT_DIR, 'dev_data.sqlite'),
+    #},
 
     # PostgreSQL
-    # 'default': {
-    #        'ENGINE': 'django.db.backends.postgresql',
-    #        'OPTIONS': {
-    #            'read_default_file': os.path.join(PROJECT_DIR, 'postgresql.cnf'),
-    #        },
-    #    },
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': env.str('DBNAME', default='steering'),
+        'USER': env.str('DBUSER', default='steering'),
+        'PASSWORD': env.str('DBPASS'),
+        'HOST': env.str('DBHOST', default='steering-postgres'),
+        'PORT': env.str('DBPORT', '5432'),
+    },
 
     # MySQL
     #    'default': {
@@ -55,18 +67,19 @@ DATABASES = {
     #            'read_default_file': os.path.join(PROJECT_DIR, 'mysql.cnf'),
     #        },
     #   },
-
 }
+if not DOCKER:
+    DATABASES['default']['HOST'] = env.str('DBHOST', default='localhost')
 
 CACHE_BACKEND = 'locmem://'
 
 # LOCALIZATION
-TIME_ZONE = 'Europe/Vienna'
-LANGUAGE_CODE = 'de'
+TIME_ZONE = env.str('TIME_ZONE', default='Europe/Vienna')
+LANGUAGE_CODE = env.str('LANGUAGE_CODE', default='de')
 USE_I18N = True
 USE_L10N = True
 
-SECRET_KEY = ''
+SECRET_KEY = env.str('SECRET_KEY')
 
 TEMPLATES = [
     {
diff --git a/requirements.txt b/requirements.txt
index 895d7bbde875a25d6eca5072254a5dad1a77d617..8e201e9e35e8ec7e2d60b4306babfe925720ab0a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,8 +3,15 @@ django-cors-headers==3.2.1
 django-oidc-provider==0.7.0
 django-tinymce==2.8.0
 django-versatileimagefield==1.11
+django-environ==0.4.5
 djangorestframework==3.11.0
 drf-nested-routers==0.91
 Pillow==4.3.0
 python-dateutil==2.8.1
 PyYAML==3.13
+
+# needed for the database (container)
+psycopg2_binary==2.8.6
+
+# needed for production server
+gunicorn==20.0.4