diff --git a/.gitignore b/.gitignore
index d3c652fd439d8383ba3a20db17a1b4500514e910..6c86200ee337b50675bc910ab0d31fb57ebb2642 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,7 @@ config.py
 .vscode/
 documentation
 
+/deploy/.env.dev
+/deploy/dbdata/*
+/deploy/log/nginx/access.log
+/deploy/log/nginx/error.log
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..238646df1b09fdb832c528ed803acdc5b190a8c2
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.7-slim
+
+WORKDIR /srv/app
+
+COPY requirements.txt /srv/app
+
+RUN apt-get update && \
+    apt-get install -y \
+        build-essential \
+        make \
+        gcc \
+        pkg-config
+        
+#ython3-matplotlib
+
+
+RUN pip install --upgrade pip -r requirements.txt
+
+COPY app/. /srv/app/app
+COPY run.py /srv/app
+COPY config.py /srv/app
+COPY messages.pot /srv/app
+
+RUN mkdir logs
+
diff --git a/deploy/config/mysql/rating.cnf b/deploy/config/mysql/rating.cnf
new file mode 100644
index 0000000000000000000000000000000000000000..45d4224d41dc28791e58629a162995149577eebe
--- /dev/null
+++ b/deploy/config/mysql/rating.cnf
@@ -0,0 +1,2 @@
+[mysqld] 
+explicit_defaults_for_timestamp = 1
\ No newline at end of file
diff --git a/deploy/config/mysql/schema.sql b/deploy/config/mysql/schema.sql
new file mode 100644
index 0000000000000000000000000000000000000000..c12823cb96e14983b9f50014ab8180e3d2b134e9
--- /dev/null
+++ b/deploy/config/mysql/schema.sql
@@ -0,0 +1,229 @@
+/* 
+SQL initialization script
+
+Run: mysql -u rating -p -D rating_db < create_rating_db.sql
+
+This will create user 'admin' with password 'password'.
+
+*/
+
+CREATE DATABASE IF NOT EXISTS rating_db;
+USE rating_db;
+GRANT ALL PRIVILEGES ON rating_db.* TO 'rating'@'%' WITH GRANT OPTION;
+
+/* Drop all tables (cascade removes foreign keys also) */
+
+DROP TABLE IF EXISTS background_question_answer;
+DROP TABLE IF EXISTS background_question_option;
+DROP TABLE IF EXISTS background_question;
+DROP TABLE IF EXISTS forced_id;
+DROP TABLE IF EXISTS user;
+DROP TABLE IF EXISTS trial_randomization;
+DROP TABLE IF EXISTS embody_answer;
+DROP TABLE IF EXISTS embody_question;
+DROP TABLE IF EXISTS answer;
+DROP TABLE IF EXISTS answer_set;
+DROP TABLE IF EXISTS question;
+DROP TABLE IF EXISTS page;
+DROP TABLE IF EXISTS experiment;
+
+/* Experiment set */
+CREATE TABLE experiment (
+	idexperiment INTEGER NOT NULL AUTO_INCREMENT,
+	name VARCHAR(120), 
+	instruction TEXT, 
+	directoryname VARCHAR(120), 
+	language VARCHAR(120), 
+	status VARCHAR(120), 
+	randomization VARCHAR(120), 
+	short_instruction TEXT, 
+	single_sentence_instruction TEXT, 
+	is_archived VARCHAR(120), 
+	creator_name VARCHAR(120), 
+	research_notification_filename VARCHAR(120), 
+	creation_time DATETIME, 
+	stimulus_size VARCHAR(120), 
+	consent_text TEXT, 
+	use_forced_id VARCHAR(120), 
+	PRIMARY KEY (idexperiment)
+);
+
+/* Answer set holds session information about users experiment */
+CREATE TABLE answer_set (
+	idanswer_set INTEGER NOT NULL AUTO_INCREMENT, 
+	experiment_idexperiment INTEGER, 
+	session VARCHAR(120), 
+	agreement VARCHAR(120), 
+	answer_counter INTEGER, 
+	registration_time DATETIME, 
+	last_answer_time DATETIME, 
+	PRIMARY KEY (idanswer_set), 
+	FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment)
+);
+
+/* Background questions are asked before the experiment begins */ 
+CREATE TABLE background_question (
+	idbackground_question INTEGER NOT NULL AUTO_INCREMENT,
+	background_question VARCHAR(120), 
+	experiment_idexperiment INTEGER, 
+	PRIMARY KEY (idbackground_question)
+);
+CREATE TABLE background_question_option (
+	idbackground_question_option INTEGER NOT NULL AUTO_INCREMENT,
+	background_question_idbackground_question INTEGER, 
+	`option` VARCHAR(120), 
+	PRIMARY KEY (idbackground_question_option), 
+	FOREIGN KEY(background_question_idbackground_question) REFERENCES background_question (idbackground_question)
+);
+CREATE TABLE background_question_answer (
+	idbackground_question_answer INTEGER NOT NULL AUTO_INCREMENT,
+	answer_set_idanswer_set INTEGER, 
+	answer VARCHAR(120), 
+	background_question_idbackground_question INTEGER, 
+	PRIMARY KEY (idbackground_question_answer), 
+	FOREIGN KEY(answer_set_idanswer_set) REFERENCES answer_set (idanswer_set), 
+	FOREIGN KEY(background_question_idbackground_question) REFERENCES background_question (idbackground_question)
+);
+
+/* Randomize experiment page order  */
+CREATE TABLE trial_randomization (
+	idtrial_randomization INTEGER NOT NULL AUTO_INCREMENT, 
+	page_idpage INTEGER, 
+	randomized_idpage INTEGER, 
+	answer_set_idanswer_set INTEGER, 
+	experiment_idexperiment INTEGER, 
+	PRIMARY KEY (idtrial_randomization)
+);
+
+CREATE TABLE user (
+	id INTEGER NOT NULL AUTO_INCREMENT, 
+	username VARCHAR(64), 
+	email VARCHAR(120), 
+	password_hash VARCHAR(128), 
+	PRIMARY KEY (id)
+);
+
+/* By using forced ID login subjects can only participate to a rating task by logging in with a pregenerated ID */
+CREATE TABLE forced_id (
+	idforced_id INTEGER NOT NULL AUTO_INCREMENT,
+	experiment_idexperiment INTEGER, 
+	pregenerated_id VARCHAR(120), 
+	PRIMARY KEY (idforced_id), 
+	FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment)
+);
+
+/* Information about stimulus type and content on a page */
+CREATE TABLE page (
+	idpage INTEGER NOT NULL AUTO_INCREMENT,
+	experiment_idexperiment INTEGER, 
+	type VARCHAR(120), 
+	text VARCHAR(120), 
+	media VARCHAR(120), 
+	PRIMARY KEY (idpage), 
+	FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment)
+);
+
+/* Slider question */
+CREATE TABLE question (
+	idquestion INTEGER NOT NULL AUTO_INCREMENT,
+	experiment_idexperiment INTEGER, 
+	question VARCHAR(120), 
+	`left` VARCHAR(120), 
+	`right` VARCHAR(120), 
+	PRIMARY KEY (idquestion), 
+	FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment)
+);
+
+/* Slider answer */
+CREATE TABLE answer (
+	idanswer INTEGER NOT NULL AUTO_INCREMENT,
+	question_idquestion INTEGER, 
+	answer_set_idanswer_set INTEGER, 
+	answer VARCHAR(120), 
+	page_idpage INTEGER, 
+	PRIMARY KEY (idanswer), 
+	FOREIGN KEY(answer_set_idanswer_set) REFERENCES answer_set (idanswer_set), 
+	FOREIGN KEY(page_idpage) REFERENCES page (idpage), 
+	FOREIGN KEY(question_idquestion) REFERENCES question (idquestion)
+);
+
+/* Create indexes for faster operations */
+CREATE INDEX ix_experiment_consent_text ON experiment (consent_text(255));
+CREATE INDEX ix_experiment_creation_time ON experiment (creation_time);
+CREATE UNIQUE INDEX ix_experiment_directoryname ON experiment (directoryname);
+CREATE INDEX ix_experiment_instruction ON experiment (instruction(255));
+CREATE INDEX ix_experiment_name ON experiment (name);
+CREATE INDEX ix_experiment_short_instruction ON experiment (short_instruction(255));
+CREATE INDEX ix_experiment_single_sentence_instruction ON experiment (single_sentence_instruction(255));
+CREATE UNIQUE INDEX ix_user_email ON user (email);
+CREATE UNIQUE INDEX ix_user_username ON user (username);
+CREATE INDEX ix_answer_set_last_answer_time ON answer_set (last_answer_time);
+CREATE INDEX ix_answer_set_registration_time ON answer_set (registration_time);
+CREATE INDEX ix_page_media ON page (media);
+CREATE INDEX ix_page_text ON page (text);
+CREATE INDEX ix_page_type ON page (type);
+
+
+/* New fields for updating embody tool to onni.utu.fi */
+
+/* Embody picture/question information */
+CREATE TABLE embody_question (
+	idembody INTEGER NOT NULL AUTO_INCREMENT,
+	experiment_idexperiment INTEGER, 
+	picture TEXT, 
+	question TEXT, 
+	PRIMARY KEY (idembody), 
+	FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment)
+);
+
+/* Embody answer (coordinates). Answer is saved as a json object: 
+	{x:[1,2,100,..], y:[3,4,101,..], r:[13,13,8,...]} */
+CREATE TABLE embody_answer (
+	idanswer INTEGER NOT NULL AUTO_INCREMENT,
+	answer_set_idanswer_set INTEGER, 
+	page_idpage INTEGER, 
+    embody_question_idembody INTEGER DEFAULT 0,
+	coordinates TEXT, 
+	PRIMARY KEY (idanswer), 
+	FOREIGN KEY(answer_set_idanswer_set) REFERENCES answer_set (idanswer_set), 
+	FOREIGN KEY(page_idpage) REFERENCES page (idpage) ,
+	FOREIGN KEY(embody_question_idembody) REFERENCES embody_question (idembody) 
+);
+
+
+/* Set flag if embody tool is enabled -> this is not the most modular solution, but works for now */
+ALTER TABLE experiment ADD COLUMN (embody_enabled BOOLEAN DEFAULT 0);
+
+/* Set current answer type (embody/slider/etc..) so returning users are routed to correct question */ 
+ALTER TABLE answer_set ADD COLUMN (answer_type VARCHAR(120));
+
+INSERT INTO user VALUES(1,'admin',NULL,'pbkdf2:sha256:50000$6Cc6Mjmo$3fe413a88db1bacfc4d617f7c1547bd1ea4cbd6c5d675a58e78332201f6befc6');
+
+/* eyelabs */
+INSERT INTO user VALUES(2,'eyelabs',NULL,'pbkdf2:sha256:50000$sdBu3Rjm$7ab97c6d2686460b85a2a20517b7012c15ffb341ba3fef5b0f17ed8354fc38d9');
+
+
+CREATE TABLE research_group (
+	id INTEGER NOT NULL AUTO_INCREMENT,
+	name TEXT, 
+	tag TEXT, 
+	description TEXT,
+	PRIMARY KEY (id)
+);
+
+INSERT INTO research_group(id, name, tag, description) VALUES(1, 'Human Emotion Systems', 'emotion', 'Welcome to the Human Emotion Systems -laboratory`s Onni-net laboratory! The experiments that are currently underway are listed below - you can participate for as many experiments you want.');
+INSERT INTO research_group(id, name, tag, description) VALUES(2, 'Turku Eye-tracking', 'eyelabs', 'Welcome to the Turku Eyelabs -laboratory`s Onni-net laboratory! The experiments that are currently underway are listed below - you can participate for as many experiments you want.');
+
+CREATE TABLE user_in_group (
+	idgroup INTEGER,
+	iduser INTEGER,
+	role TEXT,
+	FOREIGN KEY(idgroup) REFERENCES research_group (id), 
+	FOREIGN KEY(iduser) REFERENCES user (id)
+);
+
+INSERT INTO user_in_group VALUES (1,1, 'admin');
+INSERT INTO user_in_group VALUES (2,1, 'admin');
+
+ALTER TABLE experiment ADD COLUMN (group_id INTEGER), ADD FOREIGN KEY(group_id) REFERENCES research_group(id);
+
diff --git a/deploy/config/nginx/rating.conf b/deploy/config/nginx/rating.conf
new file mode 100644
index 0000000000000000000000000000000000000000..7fa4bb9e1e4efa8fb97be27b97ba1742cf1a3d6b
--- /dev/null
+++ b/deploy/config/nginx/rating.conf
@@ -0,0 +1,64 @@
+
+upstream flask_app {
+    server gunicorn:8000 fail_timeout=5s max_fails=5;
+}
+
+server {
+	# NON-SSL
+    listen       80;
+
+	# SSL
+    #listen       443 ssl http2;
+    #listen       [::]:443 ssl http2;
+
+    #ssl_certificate "/etc/nginx/tls/onni.utu.fi-rsa+chain.pem";
+    #ssl_certificate_key "/etc/nginx/tls/private/onni.utu.fi-rsa.key";
+	#ssl_protocols TLSv1.2;
+    #ssl_session_cache shared:SSL:1m;
+    #ssl_session_timeout  10m;
+    #ssl_ciphers HIGH:!aNULL:!MD5;
+    #ssl_prefer_server_ciphers on;
+
+    #server_name  _;
+
+	client_max_body_size 0;
+
+    location / {
+	    proxy_pass http://flask_app;
+
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header Host $host;
+        proxy_redirect off;
+
+        proxy_read_timeout 30s;
+    }
+
+    location /create_embody {  
+        #location /socket.io {  
+
+	    proxy_pass http://flask_app/socket.io;
+	    proxy_http_version 1.1;
+	    proxy_redirect off;
+	    proxy_buffering off;
+
+        proxy_connect_timeout 1d;
+        proxy_send_timeout 1d;
+        proxy_read_timeout 1d;
+
+	    proxy_set_header Host $host;
+	    proxy_set_header X-Real-IP $remote_addr;
+	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+	    proxy_set_header Upgrade $http_upgrade;
+	    proxy_set_header Connection "Upgrade";
+
+    }  
+
+    error_page 404 /404.html;
+        location = /40x.html {
+    }
+
+    error_page 500 502 503 504 /50x.html;
+        location = /50x.html {
+    }    
+}
diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8fea8eafdef17362d00fb5dc840bcb8d7d422345
--- /dev/null
+++ b/deploy/docker-compose.yml
@@ -0,0 +1,43 @@
+version: "3.3"
+
+services:
+  nginx:
+    #image: nginx:latest
+    build: ./nginx
+    restart: always
+    ports:
+      - "80:80"
+      #- "443:443"
+    volumes:
+      #- ./tls:/etc/nginx/tls
+      #- ./config/nginx/rating.conf:/etc/nginx/conf.d/rating.conf
+      - ./log/nginx:/var/log/nginx
+    depends_on:
+      - gunicorn
+
+  gunicorn:
+    image: rating_app:latest
+    restart: always
+    ports:
+      - 8000:8000
+    env_file:
+      - ./.env.dev
+    depends_on:
+      - db
+    command: sh -c "gunicorn run:app -b 0.0.0.0:8000 -k gevent --worker-connections=1000 --workers=1 --log-level debug -t 180"
+    #volumes: FOR DEV
+    #- ./application:/srv/app
+
+  db:
+    image: mysql
+    ports:
+      - "3200:3306"
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_DATABASE: rating_db
+      MYSQL_USER: rating
+      MYSQL_PASSWORD: rating_passwd
+    volumes:
+      - ./config/mysql/rating.cnf:/etc/mysql/conf.d/rating.cnf
+      - ./config/mysql/schema.sql:/docker-entrypoint-initdb.d/1.sql
+      - ./dbdata:/var/lib/mysql
diff --git a/deploy/nginx/Dockerfile b/deploy/nginx/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..8e5916e0c933d2a9ec34f39b7d933bc744228c0c
--- /dev/null
+++ b/deploy/nginx/Dockerfile
@@ -0,0 +1,4 @@
+FROM nginx:1.19.0-alpine
+
+RUN rm /etc/nginx/conf.d/default.conf
+COPY nginx.conf /etc/nginx/conf.d
\ No newline at end of file
diff --git a/deploy/nginx/nginx.conf b/deploy/nginx/nginx.conf
new file mode 100644
index 0000000000000000000000000000000000000000..7fa4bb9e1e4efa8fb97be27b97ba1742cf1a3d6b
--- /dev/null
+++ b/deploy/nginx/nginx.conf
@@ -0,0 +1,64 @@
+
+upstream flask_app {
+    server gunicorn:8000 fail_timeout=5s max_fails=5;
+}
+
+server {
+	# NON-SSL
+    listen       80;
+
+	# SSL
+    #listen       443 ssl http2;
+    #listen       [::]:443 ssl http2;
+
+    #ssl_certificate "/etc/nginx/tls/onni.utu.fi-rsa+chain.pem";
+    #ssl_certificate_key "/etc/nginx/tls/private/onni.utu.fi-rsa.key";
+	#ssl_protocols TLSv1.2;
+    #ssl_session_cache shared:SSL:1m;
+    #ssl_session_timeout  10m;
+    #ssl_ciphers HIGH:!aNULL:!MD5;
+    #ssl_prefer_server_ciphers on;
+
+    #server_name  _;
+
+	client_max_body_size 0;
+
+    location / {
+	    proxy_pass http://flask_app;
+
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header Host $host;
+        proxy_redirect off;
+
+        proxy_read_timeout 30s;
+    }
+
+    location /create_embody {  
+        #location /socket.io {  
+
+	    proxy_pass http://flask_app/socket.io;
+	    proxy_http_version 1.1;
+	    proxy_redirect off;
+	    proxy_buffering off;
+
+        proxy_connect_timeout 1d;
+        proxy_send_timeout 1d;
+        proxy_read_timeout 1d;
+
+	    proxy_set_header Host $host;
+	    proxy_set_header X-Real-IP $remote_addr;
+	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+	    proxy_set_header Upgrade $http_upgrade;
+	    proxy_set_header Connection "Upgrade";
+
+    }  
+
+    error_page 404 /404.html;
+        location = /40x.html {
+    }
+
+    error_page 500 502 503 504 /50x.html;
+        location = /50x.html {
+    }    
+}
diff --git a/requirements.txt b/requirements.txt
index 5b482fd0a7d61d18ef1ca1228b934c22a189553c..59c0e41fc73b6ddcaf9d18579fd987eda7945226 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -31,3 +31,4 @@ uuid==1.30
 Werkzeug==0.14.1
 WTForms==2.2.1
 WTForms-SQLAlchemy==0.1
+cryptography