diff --git a/.environment-dev b/.environment-dev
new file mode 100644
index 0000000000000000000000000000000000000000..335bee02784270b902cb18a538adb44da03977dc
--- /dev/null
+++ b/.environment-dev
@@ -0,0 +1,9 @@
+UWSGI_PROCESS_PER_CONTAINER=4
+DEBUG=true
+ALLOWED_HOSTS=localhost,127.0.0.1
+SECRET_KEY=Ixosoh1iemoh0Heloh1thee5akooboonu5veehae4aikoh2ohg
+DATABASE_HOST=shellvalier-postgresql
+DATABASE_NAME=shellvalier
+DATABASE_USER=shellvalier
+DATABASE_PASSWORD=shellvalier
+DATABASE_PORT=5432
diff --git a/.gitignore b/.gitignore
index 41529f75e0b508f1f71bd0e45a78cdc46d651107..84e6ffe8d9ce7bc8261581bdf88cdcbb89a85220 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 *.pyc
 */__pycache__/*
 */migrations/*_auto_*.py
+/.datastore/
+/.idea/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..53e1671306bb33855ed3945915ee076d29291997
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,77 @@
+FROM ubuntu:focal
+
+MAINTAINER Dariusz Czerski <dcz@ipipan.waw.pl>
+
+ENV DEBIAN_FRONTEND=noninteractive \
+    LANG=en_US.UTF-8 \
+    LC_ALL=en_US.UTF-8 \
+    LANGUAGE=en_US:en \
+    TZ=Europe/Warsaw \
+    PYTHONUNBUFFERED=1 \
+    PYTHONFAULTHANDLER=1
+
+ENV PACKAGES="\
+    binutils \
+    curl \
+    gdal-bin \
+    gettext \
+    git \
+    libproj-dev \
+    locales \
+    nginx \
+    postgresql-client \
+    python3-pip \
+    python3-setuptools \
+    python3-wheel \
+    python3.8 \
+    python3.8-dev \
+    syslinux \
+    tar \
+    tzdata \
+    unzip \
+    wget \
+    "
+
+ENV DEV_PACKAGES="\
+    gpg-agent \
+    libcurl4-openssl-dev \
+    libssl-dev \
+    software-properties-common \
+    "
+
+ADD requirements.txt /requirements.txt
+
+RUN echo $TZ > /etc/timezone && \
+    ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
+    apt update && \
+    apt install -y -f --no-install-recommends $PACKAGES $DEV_PACKAGES && \
+    update-alternatives --install /usr/bin/python python /usr/bin/python3.8 1 && \
+    update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 && \
+    sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
+    echo "LC_ALL=$LC_ALL" >> /etc/environment && \
+    echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
+    echo "LANG=$LANG" > /etc/locale.conf && \
+    locale-gen $LANG && \
+    pip install -r /requirements.txt && \
+    wget -O - http://download.sgjp.pl/apt/sgjp.gpg.key | apt-key add - && \
+    apt-add-repository http://download.sgjp.pl/apt/ubuntu && \
+    apt update && \
+    apt install -y -f --no-install-recommends morfeusz2 && \
+    wget http://download.sgjp.pl/morfeusz/20211017/Linux/20.04/64/morfeusz2-1.99.1-20211017-cp35.cp36.cp37.cp38.cp39-abi3-linux_x86_64.whl && \
+    pip install morfeusz2-1.99.1-20211017-cp35.cp36.cp37.cp38.cp39-abi3-linux_x86_64.whl && \
+    rm morfeusz2-1.99.1-20211017-cp35.cp36.cp37.cp38.cp39-abi3-linux_x86_64.whl && \
+    mkdir /run/nginx/ && \
+    apt purge $DEV_PACKAGES -y && \
+    apt autoremove --purge -y && \
+    rm -rf /root/.cache/ && \
+    rm -rf /usr/src/ && \
+    rm -rf /var/lib/apt/lists/*
+
+
+ADD docker/config/uwsgi.ini /uwsgi.ini
+ADD . /app
+
+VOLUME /app
+WORKDIR /app
+
+ENTRYPOINT ["/app/docker/scripts/docker-entrypoint"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ba18189880d170a05d564219716df3c82f7dd1a4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# ShellValier v2
+
+## Running the development environment
+
+In order to run the development environment locally:
+1. Make sure you have [Docker Desktop](https://docs.docker.com/desktop/) and [Docker Compose](https://docs.docker.com/compose/) installed.
+2. Build and run the project by executing:
+
+        ./docker/scripts/run-docker
+
+## Working in the development environment
+
+Whenever you need to establish an interactive bash session in the running applicaiton container, execute:
+
+        ./docker/scripts/docker-bash
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..338cabc96379180aedfa3408330369236147bf70
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,32 @@
+version: "3"
+
+networks:
+  app-tier:
+    driver: bridge
+
+services:
+  postgresql:
+    container_name: shellvalier-postgresql
+    image: postgres:14.2
+    environment:
+      POSTGRES_USER: "shellvalier"
+      POSTGRES_DB: "shellvalier"
+      POSTGRES_PASSWORD: "shellvalier"
+      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8"
+    networks:
+      - app-tier
+    volumes:
+      - ./.datastore/postgresql:/var/lib/postgresql/data
+
+  backend:
+    container_name: shellvalier-backend
+    image: shellvalier-developer:latest
+    depends_on:
+      - postgresql
+    networks:
+      - app-tier
+    env_file: .environment-dev
+    volumes:
+      - .:/app
+    ports:
+      - "8000:8000"
diff --git a/docker/config/uwsgi.ini b/docker/config/uwsgi.ini
new file mode 100644
index 0000000000000000000000000000000000000000..4011356c26918e280eccdb98c50c8f070a279929
--- /dev/null
+++ b/docker/config/uwsgi.ini
@@ -0,0 +1,21 @@
+[uwsgi]
+chdir = /app
+wsgi-file = /app/shellvalier/wsgi.py
+socket = /run/uwsgi.sock
+pidfile = /run/uwsgi.pid
+
+chmod-socket = 666
+
+master              = true
+processes           = UWSGI_PROCESS_PER_CONTAINER
+enable-threads      = true
+lazy-apps           = true
+
+max-requests        = 500
+max-requests-delta  = 10
+
+buffer-size         = 32768
+
+harakiri            = 300
+
+disable-write-exception = true
diff --git a/docker/scripts/docker-bash b/docker/scripts/docker-bash
new file mode 100755
index 0000000000000000000000000000000000000000..7c6db1887115c53fdf1d994b838391a3a232a164
--- /dev/null
+++ b/docker/scripts/docker-bash
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+docker-compose exec backend bash
diff --git a/docker/scripts/docker-entrypoint b/docker/scripts/docker-entrypoint
new file mode 100755
index 0000000000000000000000000000000000000000..873a4186ae4a37593e9f701547d1400ca78dc724
--- /dev/null
+++ b/docker/scripts/docker-entrypoint
@@ -0,0 +1,23 @@
+#!/bin/bash
+echo 'Starting docker container'
+set -e
+
+function assertPresence() {
+    VARIABLE_NAME=$1
+    if [ -z ${!VARIABLE_NAME} ]; then
+        echo "${VARIABLE_NAME} is unset. Please set this label to run this docker container";
+        exit 1
+    fi
+}
+
+assertPresence UWSGI_PROCESS_PER_CONTAINER
+sed -i.bak "s/UWSGI_PROCESS_PER_CONTAINER/$UWSGI_PROCESS_PER_CONTAINER/" /uwsgi.ini
+
+exec "$@"
+
+python manage.py compilemessages
+
+/app/docker/scripts/postgres-alive
+python manage.py migrate --noinput
+
+exec python manage.py runserver 0:8000
diff --git a/docker/scripts/postgres-alive b/docker/scripts/postgres-alive
new file mode 100755
index 0000000000000000000000000000000000000000..e1b1d769f574c3bd3297bb209331fd087c5ce8fa
--- /dev/null
+++ b/docker/scripts/postgres-alive
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+function assertPresence() {
+    VARIABLE_NAME=$1
+    if [ -z ${!VARIABLE_NAME} ]; then
+        echo "${VARIABLE_NAME} is unset. Please set this label to run this docker container";
+        exit 1
+    fi
+}
+
+assertPresence DATABASE_HOST
+assertPresence DATABASE_USER
+assertPresence DATABASE_PASSWORD
+assertPresence DATABASE_PORT
+
+STATE=NOT_RUN
+WAIT=1
+
+is_alive() {
+    WAIT=$((WAIT+1))
+    if [[ $WAIT -le 180 ]]; then
+        DB_CON_OK=$(PGPASSWORD=$DATABASE_PASSWORD psql -h $DATABASE_HOST -U $DATABASE_USER -p $DATABASE_PORT -t -A -c "SELECT datname FROM pg_database WHERE datname='$DATABASE_NAME'" $DATABASE_NAME | grep -i $DATABASE_NAME)
+        if [[ $DB_CON_OK == "$DATABASE_NAME" ]]; then
+            STATE=RUN
+        fi
+    else
+       exit
+    fi
+}
+
+while [ $STATE == "NOT_RUN" ]; do
+    is_alive
+    sleep 1
+done
diff --git a/docker/scripts/run-docker b/docker/scripts/run-docker
new file mode 100755
index 0000000000000000000000000000000000000000..be5d39635b4c6d0d44c581f8b34c06f02191d60f
--- /dev/null
+++ b/docker/scripts/run-docker
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -e
+docker build -t shellvalier-developer -f Dockerfile .
+docker-compose up
diff --git a/requirements.txt b/requirements.txt
index c72847238a7deeb16dc32f5d77f8addf2718dd33..b01fc60731f4752c18f00694b660570625d9dfa5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,7 @@ certifi==2020.6.20
 Django==3.1.5
 django-crispy-forms==1.10.0
 django-delayed-union==0.1.4
+django-extensions==3.1.5
 future==0.18.2
 infinity==1.4
 Jinja2==2.10.3
diff --git a/shellvalier/environment.py b/shellvalier/environment.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa4006a9e5ba56f7a06d25f58e10120dd2b4c9ce
--- /dev/null
+++ b/shellvalier/environment.py
@@ -0,0 +1,67 @@
+"""Utils for accessing process environment variables"""
+
+import os
+from typing import (
+    Any,
+    Callable,
+    List,
+    Optional,
+)
+
+from django.core.exceptions import ImproperlyConfigured
+
+
+NOT_SET = object()
+
+
+def get_environment(name: str, mapper: Optional[Callable[[str], Any]] = None, default=NOT_SET) -> Any:
+    """Return a value with the given name from the process environment variables"""
+    possible_environ_names = [name]
+    for environ_name in possible_environ_names:
+        try:
+            value = os.environ[environ_name]
+        except KeyError:
+            continue
+        else:
+            break
+    else:
+        if default is NOT_SET:
+            raise ImproperlyConfigured('The {} environment variable is not present under names: {}'.format(name, ', '.join(possible_environ_names)))
+        else:
+            return default
+    mapper = mapper or str
+    return mapper(value)
+
+
+def boolean_mapper(value: str) -> bool:
+    """Map a string representation on a boolean value to Python bool"""
+    if value.lower() in {'true', '1', 'yes'}:
+        return True
+    if value.lower() in {'false', '0', 'no'}:
+        return False
+    raise ImproperlyConfigured('Boolean mapper cannot map the {} value to bool'.format(value))
+
+
+def list_mapper_factory(delimiter: Optional[str] = None, item_mapper: Optional[Callable[[str], Any]] = None) -> Callable[[str], List[Any]]:
+    """Return a function that maps lists serialized to a string with a specified delimiter to a Python list
+
+    :param delimiter: the item delimiter in the serialized list
+    :param item_mapper: the mapper for list elements
+    :return: the mapper function
+    """
+    delimiter = delimiter or ','
+    item_mapper = item_mapper or str
+
+    def list_mapper(value: str) -> List[Any]:
+        """Map a list serialized to a string to a Python list
+
+        Assumes an empty string represents an empty list, not a list containing an empty string.
+
+        :param value: the list serialized to string
+        :return: the parsed list
+        """
+        if value == '':
+            return []
+        return [item_mapper(item) for item in value.split(delimiter)]
+
+    return list_mapper
diff --git a/shellvalier/settings.py b/shellvalier/settings.py
index f17b5f27751cdcf37696edd7f2b0964dfcfc3a89..797463aca63bea7d6a5c7e0579df919c03a1f0da 100644
--- a/shellvalier/settings.py
+++ b/shellvalier/settings.py
@@ -12,6 +12,9 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
 
 import os
 
+from .environment import get_environment, boolean_mapper, list_mapper_factory
+
+
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
@@ -20,10 +23,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
 
 # SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'xxx'
+SECRET_KEY = get_environment('SECRET_KEY')
 
 # SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
+DEBUG = get_environment('DEBUG', mapper=boolean_mapper)
 
 # for production
 '''
@@ -34,7 +37,7 @@ CSRF_COOKIE_SECURE = True
 X_FRAME_OPTIONS = 'DENY'
 '''
 
-ALLOWED_HOSTS = ['127.0.0.1']
+ALLOWED_HOSTS = get_environment('ALLOWED_HOSTS', mapper=list_mapper_factory())
 
 # Application definition
 
@@ -56,6 +59,7 @@ INSTALLED_APPS = [
     'dictionary_statistics.apps.DictionaryStatisticsConfig',
     'download.apps.DownloadConfig',
     'crispy_forms',
+    'django_extensions',
 ]
 
 CRISPY_TEMPLATE_PACK = 'bootstrap4'
@@ -99,11 +103,11 @@ WSGI_APPLICATION = 'shellvalier.wsgi.application'
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.postgresql',
-        'NAME': 'shellvalier',
-        'USER': 'shellvalier',
-        'PASSWORD': 'shellvalier',
-        'HOST': 'localhost',
-        'PORT': '',
+        'NAME': get_environment('DATABASE_NAME'),
+        'USER': get_environment('DATABASE_USER'),
+        'PASSWORD': get_environment('DATABASE_PASSWORD'),
+        'HOST': get_environment('DATABASE_HOST'),
+        'PORT': get_environment('DATABASE_PORT', mapper=int),
     }}