diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..e7994c115c1945a0092785eedac6c65f6f3574d4
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+.git
+.venv
+.env
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8c917865f49f0979f34f14da6d3b66ba9148514c
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,21 @@
+stages:
+  - build
+
+image: docker:20.10.8
+
+variables:
+  DOCKER_TLS_CERTDIR: "/certs"
+  IMAGE_TAG_APP: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+
+services:
+  - docker:20.10.8-dind
+
+before_script:
+  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+
+build_app:
+  stage: build
+  script:
+    - docker pull $CI_REGISTRY_IMAGE:test || true
+    - docker build --cache-from $CI_REGISTRY_IMAGE:test -t $IMAGE_TAG_APP .
+    - docker push $IMAGE_TAG_APP
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..fdc82f900929857eb6a263f248fc3d5e9eff3ffb
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM python:3.6
+
+RUN mkdir /app
+WORKDIR /app
+
+COPY requirements.txt .
+RUN pip install -r requirements.txt
+
+COPY . .
+
+RUN bash -c 'adduser --disabled-login --quiet --gecos app app &&  \
+             chmod -R o+r /app/ && \
+             chmod o+x /app/run.sh'
+USER app
+
+EXPOSE 8000
+
+CMD ["bash", "run.sh"]
diff --git a/README.md b/README.md
index c6d2bbc8e9cf1979bc2b5bbfdb4f8c745a2dac43..626383f3ae92cc853959f9b27122885fd71b8a49 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,29 @@
 Fork used by the Czech Pirate Party. Includes PirateID integration and Czech translation.
 
 Helios is an end-to-end verifiable voting system.
+
+## Info
+
+větve v gitu:
+
+- `original` je kopie https://github.com/benadida/helios-server
+- `master` je z `original` odvozená Pirátská verze nasazená v produkci
+- `test` je testovací verze odvozená z `master` nasazená pro testování
+
+Obvykle děláme změny v `test` a když je to ok, releasujeme do `master`.
+
+### Jak na upgrade?
+
+1. aktualizuj větev `original`
+2. udělej rebase větve `master` z větve `original`
+3. udělej novou větev `test` odvozenou z `master`
+
+## Čas
+
+S časem se v kódu pracuje velmi primitivně. Všechny časy jsou naivně a musí být UTC.
+Což komplikuje všechno. Ale podařilo se to rozumně pořešit na vstupu a výstupu.
+
+V kódu je to ohnuté takto:
+
+* Vizuálně času v templatech prohnán přes filtr `|timezone:"Europe/Prague"`.
+* Ve form field `SplitDateTimeField` očekáváme Europe/Prague a převádíme na UTC.
diff --git a/gunicorn.conf.py b/gunicorn.conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..16484fd52069292cf24727fbf8e393ac8583608e
--- /dev/null
+++ b/gunicorn.conf.py
@@ -0,0 +1,7 @@
+bind = "0.0.0.0:8000"
+accesslog = "-"
+workers = 1
+max_requests = 1000
+max_requests_jitter = 10
+timeout = 60
+graceful_timeout = 60
diff --git a/requirements.txt b/requirements.txt
index f5e6b05f0027523fa10070481e48267e93354532..7adbb62495a229746054b07f8938454199f90fbc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ gunicorn==20.0.4
 dj_database_url==0.5.0
 psycopg2==2.8.4
 
-celery==4.2.1
+celery[redis]==4.4.7
 django-picklefield==1.1.0
 
 django-anymail==8.4
@@ -25,3 +25,5 @@ rollbar==0.14.7
 
 requests-oauthlib==1.3.1
 unidecode==1.3.6
+
+sentry-sdk[django]==1.40.5
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..d8aac6d26acff007f393e0aa1ec754d31b5395bf
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# exit on error
+set -e
+
+# migrate database
+python manage.py migrate
+
+# start webserver
+exec gunicorn -c gunicorn.conf.py wsgi
diff --git a/settings.py b/settings.py
index a7f24a3e5a409ad5b60568776d0ad8da6f8f80f7..05fed0bbd467c6693e06a2e4ce95e8d9df33749c 100644
--- a/settings.py
+++ b/settings.py
@@ -5,6 +5,11 @@ import sys
 import json
 import os
 
+import sentry_sdk
+from sentry_sdk.integrations.celery import CeleryIntegration
+from sentry_sdk.integrations.django import DjangoIntegration
+from sentry_sdk.integrations.logging import LoggingIntegration
+
 TESTING = 'test' in sys.argv
 
 # go through environment variables and override them
@@ -49,7 +54,7 @@ DATABASES = {
 # override if we have an env variable
 if get_from_env('DATABASE_URL', None):
     import dj_database_url
-    DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True)
+    DATABASES['default'] = dj_database_url.config(conn_max_age=600)
     DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
 
 # Local time zone for this installation. Choices can be found here:
@@ -308,3 +313,16 @@ OCTOPUS_API_TOKEN = get_from_env('OCTOPUS_API_TOKEN', '')
 
 if DEBUG:
     EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+
+# Sentry setup
+SENTRY_DSN = get_from_env('SENTRY_DSN', '')
+if SENTRY_DSN:
+    sentry_sdk.init(
+        dsn=SENTRY_DSN,
+        integrations=[
+            DjangoIntegration(),
+            CeleryIntegration(),
+            LoggingIntegration(level=logging.INFO, event_level=logging.WARNING),
+        ],
+        send_default_pii=True,
+    )