diff --git a/.dockerignore b/.dockerignore
index 4397baf9eb25ec30c10569642acf9cb7c2f1166e..9f22a7b20aa768934194e7f040284af7bde7e5c5 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -26,14 +26,8 @@ dmypy.json
 .idea
 *.iml
 
-#graphql-prototype
-exercise_definition/uploaded_files
-data
-
 # definitions with .md files
 definitions/
 !definitions/*.md
-
-# export/import
-export_import
-logs.jsonl
+data
+build
diff --git a/.gitignore b/.gitignore
index 0eaf6a04409931fda8837ec241e44c07e032654f..f64b986a1ccb1054075bb2afdc10369b29b8482a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,4 @@ dmypy.json
 exercise_definition/uploaded_files
 definitions/*.zip
 data
-
-# export/import
-export_import
+build/.env.dev
diff --git a/Dockerfile b/build/Dockerfile
similarity index 82%
rename from Dockerfile
rename to build/Dockerfile
index b8a66befe341df56dc7a39570e20b55df2329a62..17b5c519514e42ba13b3e11bf436e40d09258b68 100644
--- a/Dockerfile
+++ b/build/Dockerfile
@@ -36,12 +36,18 @@ RUN find \( -name "*.md" -o -name "*.txt" \) ! -path "*venv*/**" ! -path "*defin
     find \( -path "*/tests/*" \) ! -path "*venv*/**" | xargs rm -rf && \
     rm -rf /backend/.git* && \
     rm -rf /backend/dev && \
-    rm /backend/Dockerfile /backend/.dockerignore && \
+    rm -rf /backend/build && \
     rm /backend/poetry.lock /backend/pyproject.toml && \
     chmod +x /backend/exec.sh
     
 # Create work directory and technical files
-RUN mkdir /logs && touch /logs/accesslog.log && touch /logs/errorlog.log && chmod 777 /logs/accesslog.log && chmod 777 /logs/errorlog.log && mkdir /backend/data && chmod 777 /backend/data/
+RUN mkdir /logs && \
+    touch /logs/accesslog.log && \
+    touch /logs/errorlog.log && \
+    chmod 777 /logs/accesslog.log && \
+    chmod 777 /logs/errorlog.log && \
+    mkdir /backend/data && \
+    chmod 777 /backend/data/
 
 # Expose port for communication
 EXPOSE 8000
@@ -49,4 +55,4 @@ EXPOSE 8000
 WORKDIR /backend
 
 # Run backend
-ENTRYPOINT ["/bin/sh", "exec.sh"]
\ No newline at end of file
+ENTRYPOINT ["/bin/sh", "exec.sh"]
diff --git a/build/Dockerfile.dev b/build/Dockerfile.dev
new file mode 100644
index 0000000000000000000000000000000000000000..01ce4b64c025f224f05e60d4430699ba95a41fdd
--- /dev/null
+++ b/build/Dockerfile.dev
@@ -0,0 +1,18 @@
+FROM python:3.8-alpine
+
+ENV POETRY_NO_INTERACTION=1 \
+    POETRY_VIRTUALENVS_IN_PROJECT=1 \
+    POETRY_VIRTUALENVS_CREATE=1 \
+    POETRY_CACHE_DIR=/tmp/poetry_cache
+
+WORKDIR /backend
+
+# Install poetry
+RUN apk add gcc musl-dev libffi-dev && \
+    rm -rf /var/cache/apk/
+RUN pip install poetry==1.8.2 --no-cache
+
+COPY . /backend/
+
+ENTRYPOINT [ "/bin/sh", "dev/init.sh" ]
+EXPOSE 8000
diff --git a/build/be-dev-compose.yml b/build/be-dev-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..55d6682e73ba0f5011239c45a70d796fa97a51dc
--- /dev/null
+++ b/build/be-dev-compose.yml
@@ -0,0 +1,48 @@
+services:
+  postgres:
+    container_name: postgres-inject
+    image: postgres:16-alpine
+    environment:
+      - POSTGRES_NAME=inject
+      - POSTGRES_USER=inject
+      - POSTGRES_PASSWORD=inject
+    ports:
+      - 5432:5432
+    volumes:
+      - pgdata-dev:/var/lib/postgresql/data
+    networks:
+      inject-db:
+        aliases:
+          - inject-postgres
+  backend:
+    container_name: backend-inject
+    depends_on:
+      - postgres
+    env_file:
+      - .env.dev
+    environment:
+      - INJECT_DB_HOST=inject-postgres
+      - INJECT_DB_USER=inject
+      - INJECT_DB_PASS=inject
+      - INJECT_SECRET_KEY="devdevdevdevdevdevdevdevdevdevdevdevdevdevdevdevdev"
+    ports:
+      - 8000:8000
+    volumes:
+      - inject-data-dev:/backend/data
+    networks:
+      inject-db:
+        aliases:
+          - inject-backend
+    build:
+      context: ..
+      dockerfile: build/Dockerfile.dev
+
+volumes:
+  inject-data-dev:
+    name: inject-data-dev
+  pgdata-dev:
+    name: inject-pgdata-dev
+
+networks:
+  inject-db:
+    driver: bridge
diff --git a/build/be-prod-compose.yml b/build/be-prod-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e66cdc1fc7d87dcc500c2ed0e462ad030d8e66dd
--- /dev/null
+++ b/build/be-prod-compose.yml
@@ -0,0 +1,46 @@
+services:
+  postgres:
+    container_name: postgres-inject
+    image: postgres:16-alpine
+    environment:
+      - POSTGRES_NAME=inject
+      - POSTGRES_USER=${INJECT_DB_USER}
+      - POSTGRES_PASSWORD=${INJECT_DB_PASS}
+    expose:
+      - 5432
+    volumes:
+      - pgdata:/var/lib/postgresql/data
+    networks:
+      inject-db:
+        aliases:
+          - inject-postgres
+  backend:
+    container_name: backend-inject
+    depends_on:
+      - postgres
+    environment:
+      - INJECT_DB_HOST=inject-postgres
+    ports:
+      - 8000:8000
+    volumes:
+      - inject-data:/backend/data
+    networks:
+      inject-db:
+        aliases:
+          - inject-backend
+      inject-nginx:
+        aliases:
+          - inject-backend
+
+
+volumes:
+  inject-data:
+    name: inject-data
+  pgdata:
+    name: inject-pgdata
+
+networks:
+  inject-db:
+    driver: bridge
+  inject-nginx:
+    external: true
diff --git a/build/db-dev-compose.yml b/build/db-dev-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8688c2af3a52c1e589c72ffa0be0951933c9e9e4
--- /dev/null
+++ b/build/db-dev-compose.yml
@@ -0,0 +1,16 @@
+services:
+  postgres:
+    container_name: postgres-inject
+    image: postgres:16-alpine
+    environment:
+      - POSTGRES_NAME=inject
+      - POSTGRES_USER=inject
+      - POSTGRES_PASSWORD=inject
+    ports:
+      - 5432:5432
+    volumes:
+      - pgdata:/var/lib/postgresql/data
+
+volumes:
+  pgdata:
+    name: inject-pgdata
diff --git a/dev/README.md b/dev/README.md
index 75fdb0fb5771a7f47567074fbfa0f940ccbb0aab..41bb69a1826796762fc13908985067f1a57b8ea6 100644
--- a/dev/README.md
+++ b/dev/README.md
@@ -4,16 +4,43 @@ All files in this directory are subject to change and there are no guarantees on
 It is the responsibility of the developer to keep these tools up-to-date, 
 however the updating process should be kept simple.
 
-## Postman
-[Postman](https://www.postman.com/) is a tool for managing APIs.
 
-The file `inject.postman_collection.json` contains the current (incomplete) definitions of some 
-endpoints that are commonly used during development. 
+## Docker containers
+The backend uses a [PostgresSQL 16 database](https://www.postgresql.org/docs/16/index.html).
+To simplify development, there is a number of different docker compose scripts in the `build` directory:
+- [db-dev-compose.yml](../build/db-dev-compose.yml) - this file is meant for backend developers,
+it simply starts the database on localhost:5432 so that the default database connection settings can be used
+- [be-dev-compose.yml](../build/be-dev-compose.yml) - this file is meant for frontend developers,
+in addition to starting the database, it also starts the backend on localhost:8000.
+If you want to change/add some env variables, create a `.env.dev` file in the `build` directory and add them there.
+- [be-prod-compose.yml](../build/be-prod-compose.yml) - this is meant to be the production compose file.
+It requires much more setup and does not expose the services directly for development.
+
+To run these compose files, simply run
+
+`docker compose -f <your-file> up -d`
+
+and if you want to stop them run
+
+`docker compose -f <the-same-file-as-up> down`
+
+The backend compose files also require a way to build the backend, either from a Dockerfile or from
+an image in our registry. 
+This has to be supplied on the command line as an additional file with the `-f` option.
+
+In order to ensure persistence, the services use [volumes](https://docs.docker.com/storage/volumes/).
+If you want to clear the data to start from scratch, simply stop the containers and run
+
+```shell
+docker volume rm inject-pgdata
+docker volume rm inject-data
+```
+
+### Database seeding
+To seed the database with some initial data, use the provided [seed-db.sh](seed-db.sh).
+The script is meant to be run in an environment where python is directly accessible, 
+e.g. an image built from the `build/Dockerfile`, or after running `poetry shell`.
 
-Note: I would like to move to the online version of Postman workspaces, as they provide extended 
-functionality and much simpler synchronization between developers. 
-This however requires an account. 
-Needs to be discussed.
 
 ## Git hooks
 [Git hook documentation](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
diff --git a/dev/init.sh b/dev/init.sh
new file mode 100644
index 0000000000000000000000000000000000000000..1ccf4432414444d676ea1fdd6a5abdddc1280c2c
--- /dev/null
+++ b/dev/init.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+poetry install
+poetry run python manage.py migrate
+
+export DJANGO_SETTINGS_MODULE=ttxbackend.settings
+poetry run python dev/seed.py
+
+poetry run python manage.py runserver 0.0.0.0:8000
diff --git a/dev/seed-db.sh b/dev/seed-db.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0ddf1ddb509298290bd35e464c83d6729a7d2031
--- /dev/null
+++ b/dev/seed-db.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+export DJANGO_SETTINGS_MODULE=ttxbackend.settings
+export INJECT_DEBUG=
+python manage.py migrate
+python dev/seed.py
diff --git a/dev/seed.py b/dev/seed.py
new file mode 100644
index 0000000000000000000000000000000000000000..64c1bc3a1206ec2ab3308c2d8e3106d28f49d226
--- /dev/null
+++ b/dev/seed.py
@@ -0,0 +1,19 @@
+from django import setup
+
+setup()
+
+from user.models import User
+from common_lib.logger import logger
+
+
+if User.objects.count() == 0:
+    User.objects.create_superuser("admin@admin.com", "test")
+    logger.info("created user: admin@admin.com, password: test")
+    User.objects.create_staffuser("instructor@instructor.com", "test")
+    logger.info("created user: instructor@instructor.com, password: test")
+    User.objects.create_user("user1@user.com", "test")
+    logger.info("created user: user1@admin.com, password: test")
+    User.objects.create_user("user2@user.com", "test")
+    logger.info("created user: user2@user.com, password: test")
+    User.objects.create_user("user3@user.com", "test")
+    logger.info("created user: user3@user.com, password: test")
diff --git a/exercise/migrations/0005_auto_20240625_1241.py b/exercise/migrations/0005_auto_20240625_1241.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bc82cdb08597a6f5dfc713110390239c4b136cc
--- /dev/null
+++ b/exercise/migrations/0005_auto_20240625_1241.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.2.22 on 2024-06-25 12:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('exercise', '0004_exercise_name'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='emailparticipant',
+            name='address',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='exercise',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='team',
+            name='name',
+            field=models.TextField(default=''),
+        ),
+        migrations.AlterField(
+            model_name='team',
+            name='role',
+            field=models.TextField(default=''),
+        ),
+    ]
diff --git a/exercise/models.py b/exercise/models.py
index 2fc7f703fa695320c8e852715d591b8cd17e2a2d..db1fc8e3c82038104ed7cdb61df5a3de2f2cbc95 100644
--- a/exercise/models.py
+++ b/exercise/models.py
@@ -18,7 +18,7 @@ from exercise_definition.models import (
 
 
 class Exercise(models.Model):
-    name = models.CharField(max_length=200)
+    name = models.TextField()
     running = models.BooleanField(default=False)
     finished = models.BooleanField(default=False)
     exercise_start = models.DateTimeField(null=True, blank=True)
@@ -58,8 +58,8 @@ class Team(models.Model):
         on_delete=models.CASCADE,
         related_name="teams",
     )
-    name = models.CharField(default="", max_length=100)
-    role = models.CharField(default="", max_length=100)
+    name = models.TextField(default="")
+    role = models.TextField(default="")
     state = models.ForeignKey(
         TeamState, on_delete=models.CASCADE, related_name="teams"
     )
@@ -189,7 +189,7 @@ class EmailParticipant(models.Model):
         null=True,
         blank=True,
     )
-    address = models.CharField(max_length=100)
+    address = models.TextField()
     team = models.OneToOneField(
         Team,
         on_delete=models.CASCADE,
diff --git a/exercise_definition/lib/definition_validator.py b/exercise_definition/lib/definition_validator.py
index a4c367c16105dd1a36441f53ecc9a1a8d8b216ac..fc87fa02c9ee055fd45b08b14667d305e4f7dcde 100644
--- a/exercise_definition/lib/definition_validator.py
+++ b/exercise_definition/lib/definition_validator.py
@@ -71,7 +71,10 @@ class DefinitionValidator:
         files = self.parser.get_files()
         config = unwrap_or_throw(validate_version(self.parser.get_config()))
         channel_types = unwrap_or_throw(
-            validate_channels(self.parser.get_channels(), [])
+            validate_channels(
+                self.parser.get_channels(),
+                [],
+            )
         )
         config.enable_email = InjectTypes.EMAIL in channel_types
         config.enable_tools = InjectTypes.TOOL in channel_types
@@ -80,7 +83,10 @@ class DefinitionValidator:
             raise ValidationError(err.unwrap_err())
 
         activity_names = unwrap_or_throw(
-            validate_learning_objectives(self.parser.get_objectives(), [])
+            validate_learning_objectives(
+                self.parser.get_objectives(),
+                [],
+            )
         )
 
         if config.enable_roles:
@@ -97,7 +103,7 @@ class DefinitionValidator:
                 [
                     functools.partial(
                         validate_milestone, roles, files, activity_names
-                    )
+                    ),
                 ],
             )
         )
@@ -110,7 +116,7 @@ class DefinitionValidator:
                     [
                         functools.partial(
                             validate_email_address, milestones, roles, files
-                        )
+                        ),
                     ],
                 )
             )
@@ -125,7 +131,7 @@ class DefinitionValidator:
                     [
                         functools.partial(
                             validate_tool, milestones, roles, files
-                        )
+                        ),
                     ],
                 )
             )
@@ -137,7 +143,7 @@ class DefinitionValidator:
                     [
                         functools.partial(
                             validate_questionnaire, milestones, roles
-                        )
+                        ),
                     ],
                 )
             )
diff --git a/exercise_definition/lib/validation/common.py b/exercise_definition/lib/validation/common.py
index 3aca96ccad8565d9666130081b8454b88fcc8939..e3174b4e8ed952c1fb832fa2cc9aaa340992456b 100644
--- a/exercise_definition/lib/validation/common.py
+++ b/exercise_definition/lib/validation/common.py
@@ -112,7 +112,7 @@ def validate_content(content: Optional[YamlContent], files: Set[str]) -> Error:
 
     if content.file_name is not None and content.file_name not in files:
         return Err(
-            f"File `{content.content_path}` does not exist in the definition"
+            f"File `{content.file_name}` does not exist in the definition"
         )
 
     return None
diff --git a/exercise_definition/lib/validation/learning_objectives.py b/exercise_definition/lib/validation/learning_objectives.py
index 93c46a937de5c97d2fe89958d252c90bade31ef4..5670420be9cab98afef1329b3655866049e2d868 100644
--- a/exercise_definition/lib/validation/learning_objectives.py
+++ b/exercise_definition/lib/validation/learning_objectives.py
@@ -1,7 +1,10 @@
 from typing import List, Set
 
 from common_lib.result import Result, Ok, Err
-from exercise_definition.lib.validation.common import ValidatorFunc, Error
+from exercise_definition.lib.validation.common import (
+    ValidatorFunc,
+    Error,
+)
 from exercise_definition.models import (
     LearningObjectives,
     YamlLearningObjective,
diff --git a/exercise_definition/lib/validation/questionnaires.py b/exercise_definition/lib/validation/questionnaires.py
index 2132be87438aa18cd5a52c6212c0ccdf369e8125..2c0e16703dccd90440de28598cf2b60609e2b7bc 100644
--- a/exercise_definition/lib/validation/questionnaires.py
+++ b/exercise_definition/lib/validation/questionnaires.py
@@ -14,7 +14,6 @@ from exercise_definition.models import (
 
 
 def validate_question(milestones: Set[str], question: YamlQuestion) -> Error:
-    err: Error
     num_labels = len(question.labels.split(", "))
     if question.max != num_labels:
         return Err(
diff --git a/exercise_definition/migrations/0004_auto_20240625_1239.py b/exercise_definition/migrations/0004_auto_20240625_1239.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6180b714a8ff63aa088dea90c5ab21708fef579
--- /dev/null
+++ b/exercise_definition/migrations/0004_auto_20240625_1239.py
@@ -0,0 +1,203 @@
+# Generated by Django 3.2.22 on 2024-06-25 12:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('exercise_definition', '0003_auto_20240418_1132'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='channel',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='config',
+            name='custom_email_suffix',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='content',
+            name='raw',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='content',
+            name='rendered',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='control',
+            name='activate_milestone',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='control',
+            name='deactivate_milestone',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='control',
+            name='milestone_condition',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='control',
+            name='roles',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='definition',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='definition',
+            name='version',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailaddress',
+            name='address',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailaddress',
+            name='description',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailaddress',
+            name='organization',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailalternative',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailalternative',
+            name='sender',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailalternative',
+            name='subject',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='emailtemplate',
+            name='context',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='fileinfo',
+            name='file_name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='infoalternative',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='inject',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='inject',
+            name='organization',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='learningactivity',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='learningactivity',
+            name='tags',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='learningobjective',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='learningobjective',
+            name='tags',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='milestone',
+            name='file_names',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='milestone',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='milestone',
+            name='roles',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='question',
+            name='labels',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='question',
+            name='text',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='questionnaire',
+            name='title',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='response',
+            name='param',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='role',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='tool',
+            name='default_response',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='tool',
+            name='hint',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='tool',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='tool',
+            name='roles',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='tool',
+            name='tooltip_description',
+            field=models.TextField(),
+        ),
+    ]
diff --git a/exercise_definition/models/models.py b/exercise_definition/models/models.py
index 701038ced180193eea4fe6b5354dbc164999418d..915247a0d7240d49f53b379fc815b0d03df3707d 100644
--- a/exercise_definition/models/models.py
+++ b/exercise_definition/models/models.py
@@ -39,8 +39,8 @@ class Feature(models.TextChoices):
 
 
 class Definition(models.Model):
-    name = models.CharField(max_length=100)
-    version = models.CharField(max_length=20)
+    name = models.TextField()
+    version = models.TextField()
 
     class Meta:
         default_permissions = ()
@@ -53,7 +53,7 @@ class FileInfo(models.Model):
         unique=True,
         editable=False,
     )
-    file_name = models.CharField(max_length=500)
+    file_name = models.TextField()
     definition = models.ForeignKey(
         Definition,
         on_delete=models.CASCADE,
@@ -67,8 +67,8 @@ class FileInfo(models.Model):
 
 
 class Content(models.Model):
-    raw = models.CharField(max_length=2000)
-    rendered = models.CharField(max_length=5000)
+    raw = models.TextField()
+    rendered = models.TextField()
     file_info = models.ForeignKey(
         FileInfo,
         on_delete=models.CASCADE,
@@ -83,7 +83,7 @@ class Content(models.Model):
 
 class Config(models.Model):
     exercise_duration = models.IntegerField()
-    custom_email_suffix = models.CharField(max_length=100)
+    custom_email_suffix = models.TextField()
     definition = models.OneToOneField(
         Definition, on_delete=models.CASCADE, null=True, blank=True
     )
@@ -106,18 +106,18 @@ class EnabledFeatures(models.Model):
 
 
 class Control(models.Model):
-    milestone_condition = models.CharField(max_length=200)
-    activate_milestone = models.CharField(max_length=200)
-    deactivate_milestone = models.CharField(max_length=200)
-    roles = models.CharField(max_length=200)
+    milestone_condition = models.TextField()
+    activate_milestone = models.TextField()
+    deactivate_milestone = models.TextField()
+    roles = models.TextField()
 
     class Meta:
         default_permissions = ()
 
 
 class LearningObjective(models.Model):
-    name = models.CharField(max_length=200)
-    tags = models.CharField(max_length=1000)
+    name = models.TextField()
+    tags = models.TextField()
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="learning_objectives"
     )
@@ -127,8 +127,8 @@ class LearningObjective(models.Model):
 
 
 class LearningActivity(models.Model):
-    name = models.CharField(max_length=200)
-    tags = models.CharField(max_length=1000)
+    name = models.TextField()
+    tags = models.TextField()
     objective = models.ForeignKey(
         LearningObjective, on_delete=models.CASCADE, related_name="activities"
     )
@@ -138,10 +138,10 @@ class LearningActivity(models.Model):
 
 
 class Milestone(models.Model):
-    name = models.CharField(max_length=500)
+    name = models.TextField()
     team_visible = models.BooleanField()
-    roles = models.CharField(max_length=200)
-    file_names = models.CharField(max_length=200)
+    roles = models.TextField()
+    file_names = models.TextField()
     final = models.BooleanField()
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="milestones"
@@ -160,13 +160,13 @@ class Milestone(models.Model):
 
 
 class EmailAddress(models.Model):
-    address = models.CharField(max_length=100)
-    description = models.CharField(max_length=500)
+    address = models.TextField()
+    description = models.TextField()
     control = models.ForeignKey(
         Control, on_delete=models.CASCADE, related_name="+"
     )
     team_visible = models.BooleanField()
-    organization = models.CharField(max_length=50)
+    organization = models.TextField()
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="addresses"
     )
@@ -181,7 +181,7 @@ class EmailAddress(models.Model):
 
 
 class EmailTemplate(models.Model):
-    context = models.CharField(max_length=100)
+    context = models.TextField()
     content = models.ForeignKey(
         Content, on_delete=models.CASCADE, related_name="+"
     )
@@ -211,7 +211,7 @@ class InjectTypes(models.TextChoices):
 
 
 class InfoAlternative(models.Model):
-    name = models.CharField(max_length=100)
+    name = models.TextField()
     content = models.ForeignKey(
         Content, on_delete=models.CASCADE, related_name="+"
     )
@@ -235,9 +235,9 @@ class InfoAlternative(models.Model):
 
 
 class EmailAlternative(models.Model):
-    name = models.CharField(max_length=100)
-    sender = models.CharField(max_length=100)
-    subject = models.CharField(max_length=300)
+    name = models.TextField()
+    sender = models.TextField()
+    subject = models.TextField()
     content = models.ForeignKey(
         Content, on_delete=models.CASCADE, related_name="+"
     )
@@ -265,11 +265,11 @@ Alternative = Union["InfoAlternative", "EmailAlternative"]
 
 
 class Inject(models.Model):
-    name = models.CharField(max_length=100)
+    name = models.TextField()
     auto = models.BooleanField()
     time = models.IntegerField()
     delay = models.IntegerField()
-    organization = models.CharField(max_length=50)
+    organization = models.TextField()
     type = models.CharField(max_length=20, choices=InjectTypes.choices)
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="injects"
@@ -287,11 +287,11 @@ class Inject(models.Model):
 
 
 class Tool(models.Model):
-    name = models.CharField(max_length=100)
-    tooltip_description = models.CharField(max_length=500)
-    hint = models.CharField(max_length=100)
-    default_response = models.CharField(max_length=300)
-    roles = models.CharField(max_length=200)
+    name = models.TextField()
+    tooltip_description = models.TextField()
+    hint = models.TextField()
+    default_response = models.TextField()
+    roles = models.TextField()
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="tools"
     )
@@ -327,7 +327,7 @@ class Tool(models.Model):
 
 
 class Response(models.Model):
-    param = models.CharField(max_length=200)
+    param = models.TextField()
     content = models.ForeignKey(
         Content, on_delete=models.CASCADE, related_name="+"
     )
@@ -361,7 +361,7 @@ class Response(models.Model):
 
 
 class Role(models.Model):
-    name = models.CharField(max_length=100)
+    name = models.TextField()
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="roles"
     )
@@ -371,7 +371,7 @@ class Role(models.Model):
 
 
 class Channel(models.Model):
-    name = models.CharField(max_length=200)
+    name = models.TextField()
     type = models.CharField(max_length=20, choices=InjectTypes.choices)
     definition = models.ForeignKey(
         Definition, on_delete=models.CASCADE, related_name="channels"
@@ -382,7 +382,7 @@ class Channel(models.Model):
 
 
 class Questionnaire(models.Model):
-    title = models.CharField(max_length=200)
+    title = models.TextField()
     time = models.IntegerField()
     control = models.ForeignKey(
         Control, on_delete=models.CASCADE, related_name="+"
@@ -403,10 +403,10 @@ class Questionnaire(models.Model):
 
 
 class Question(models.Model):
-    text = models.CharField(max_length=1000)
+    text = models.TextField()
     max = models.IntegerField()
     correct = models.IntegerField()
-    labels = models.CharField(max_length=2000)
+    labels = models.TextField()
     control = models.ForeignKey(
         Control, on_delete=models.CASCADE, related_name="+"
     )
diff --git a/poetry.lock b/poetry.lock
index 17aca3dd51715f9890b0c25f67cd0bbfe913721f..ba69a86ffb85675469e40d53f8273f6863b4c9da 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1895,6 +1895,87 @@ files = [
 [package.extras]
 test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
 
+[[package]]
+name = "psycopg2-binary"
+version = "2.9.9"
+description = "psycopg2 - Python-PostgreSQL Database Adapter"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
+    {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
+    {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
+    {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
+    {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
+    {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
+]
+
 [[package]]
 name = "pyasn1"
 version = "0.5.0"
@@ -2965,4 +3046,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
 [metadata]
 lock-version = "2.0"
 python-versions = "~3.8.0"
-content-hash = "7f09869e8dd870930bbe04ab8c0321aa974878fc9225b0ff59ebad6f47d3ea40"
+content-hash = "59eacbc8986ad1411d5aa8a7109409c9a009e703c6a5006be3c73a4d103c3f7f"
diff --git a/pyproject.toml b/pyproject.toml
index 44d4d662f4709e0f86bdeed859a41abb9b065dcb..01ea33bfa341400e2c202063491aecee43db153c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,6 +18,7 @@ coverage = "^7.2.7"
 markdown = "==3.5"
 gunicorn = "==21.2.0"
 uvicorn = {extras = ["standard"], version = "==0.24.0.post1"}
+psycopg2-binary = "==2.9.9"
 
 [tool.poetry.group.dev.dependencies]
 mypy = "~1.4"
diff --git a/running_exercise/migrations/0004_auto_20240625_1316.py b/running_exercise/migrations/0004_auto_20240625_1316.py
new file mode 100644
index 0000000000000000000000000000000000000000..62b392ff2d4dfaef802a9d99daf54b3ec362da52
--- /dev/null
+++ b/running_exercise/migrations/0004_auto_20240625_1316.py
@@ -0,0 +1,38 @@
+# Generated by Django 3.2.22 on 2024-06-25 13:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('running_exercise', '0003_auto_20240412_1209'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='emailthread',
+            name='subject',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='injectoption',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='injectoption',
+            name='sender',
+            field=models.TextField(default=''),
+        ),
+        migrations.AlterField(
+            model_name='injectselection',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='tooldetails',
+            name='argument',
+            field=models.TextField(),
+        ),
+    ]
diff --git a/running_exercise/models.py b/running_exercise/models.py
index e46302f2c49f14276aa66a2839f8c675edc0e348..ee55e44c41dcdfec5756e889ffbd5fbe39eb2bc5 100644
--- a/running_exercise/models.py
+++ b/running_exercise/models.py
@@ -31,7 +31,7 @@ class ToolDetails(models.Model):
         on_delete=models.CASCADE,
         related_name="tool_details",
     )
-    argument = models.CharField(max_length=200)
+    argument = models.TextField()
     content = models.ForeignKey(
         Content,
         on_delete=models.CASCADE,
@@ -135,7 +135,7 @@ class ActionLog(models.Model):
 
 
 class EmailThread(models.Model):
-    subject = models.CharField(max_length=300)
+    subject = models.TextField()
     participants = models.ManyToManyField(
         to=EmailParticipant,
         through="ThreadParticipant",
@@ -205,7 +205,7 @@ class ThreadParticipant(models.Model):
 
 
 class InjectSelection(models.Model):
-    name = models.CharField(max_length=100)
+    name = models.TextField()
     timestamp = models.DateTimeField(null=True, blank=True)
     team = models.ForeignKey(
         Team, on_delete=models.CASCADE, related_name="inject_selections"
@@ -220,8 +220,8 @@ class InjectSelection(models.Model):
 
 
 class InjectOption(models.Model):
-    name = models.CharField(max_length=100)
-    sender = models.CharField(max_length=100, default="")
+    name = models.TextField()
+    sender = models.TextField(default="")
     control = models.ForeignKey(
         Control, on_delete=models.CASCADE, related_name="+"
     )
diff --git a/ttxbackend/settings.py b/ttxbackend/settings.py
index e672a44ba9c625fe6a5f5a9e696f46cf60b84823..0ce06a7ef5e3a90d238c89f8ec7ae2a712f9fd3d 100644
--- a/ttxbackend/settings.py
+++ b/ttxbackend/settings.py
@@ -91,20 +91,39 @@ WSGI_APPLICATION = "ttxbackend.wsgi.application"
 ASGI_APPLICATION = "ttxbackend.asgi.application"
 
 
-# Database
-# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
-
-DATA_STORAGE = "data"
-os.makedirs(os.path.join(BASE_DIR, DATA_STORAGE), exist_ok=True)
-DATABASES = {
-    "default": {
-        "ENGINE": "django.db.backends.sqlite3",
-        "NAME": os.path.join(BASE_DIR, DATA_STORAGE, "db.sqlite3"),
-        "OPTIONS": {
-            "timeout": 20,
+TESTING_MODE = len(sys.argv) > 1 and sys.argv[1] == "test"
+# The only way to use an in-memory database during tests is to use the sqlite driver
+if TESTING_MODE:
+    DATABASES = {
+        "default": {
+            "ENGINE": "django.db.backends.sqlite3",
+            "NAME": "",
+        }
+    }
+else:
+    # Database
+    # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
+    DB_HOST = os.environ.get("INJECT_DB_HOST", "localhost")
+    DB_USER = os.environ.get("INJECT_DB_USER")
+    DB_PASS = os.environ.get("INJECT_DB_PASS")
+
+    if DB_USER is None or DB_PASS is None:
+        raise Exception("Missing database credentials")
+    DATABASES = {
+        "default": {
+            "HOST": DB_HOST,
+            "ENGINE": "django.db.backends.postgresql",
+            "NAME": "inject",
+            "CONN_MAX_AGE": 10,  # type: ignore
+            "PORT": 5432,  # type: ignore
+            "USER": DB_USER,
+            "PASSWORD": DB_PASS,
+            "OPTIONS": {  # type: ignore
+                "client_encoding": "UTF8",
+            },
         },
     }
-}
+
 
 CHANNEL_LAYERS = {
     "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}
@@ -165,6 +184,8 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
 DEFINITION_EXAMPLE_STORAGE = "definitions"
 DEFINITION_FILE_STORAGE = "files"
 DEFINITION_CONTENT_STORAGE = "content"
+DATA_STORAGE = "data"
+os.makedirs(os.path.join(BASE_DIR, DATA_STORAGE), exist_ok=True)
 FILE_STORAGE = os.path.join(DATA_STORAGE, DEFINITION_FILE_STORAGE)
 LOG_STORAGE = os.path.join(DATA_STORAGE, "logs")
 EXPORT_IMPORT_STORAGE = os.path.join(DATA_STORAGE, "export_import")
@@ -226,8 +247,6 @@ if cors_origins := os.environ.get("INJECT_CORS_ALLOWED_ORIGINS"):
 
 LOG_FILE_PATH = os.environ.get("INJECT_LOGS", "backend-logs.log")
 
-TESTING_MODE = len(sys.argv) > 1 and sys.argv[1] == "test"
-
 
 # filter to disable logging during tests to not flood the test output
 class TestingFilter(logging.Filter):
diff --git a/user/migrations/0004_auto_20240625_1246.py b/user/migrations/0004_auto_20240625_1246.py
new file mode 100644
index 0000000000000000000000000000000000000000..2215d47fa92d1df107ba7c6baa15ef726ca5809c
--- /dev/null
+++ b/user/migrations/0004_auto_20240625_1246.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.22 on 2024-06-25 12:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('user', '0003_user_is_imported'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='first_name',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='last_name',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='username',
+            field=models.TextField(),
+        ),
+    ]
diff --git a/user/models.py b/user/models.py
index d0e88e70ede2175a71960f70c1bf031d0a3681f5..b502e6e62f8893af978660a58e531936adad0961 100644
--- a/user/models.py
+++ b/user/models.py
@@ -1,14 +1,12 @@
 import re
 import uuid
 
-from django.db import models, transaction
-from django.apps import apps
-from django.utils import timezone
-
-from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, Group
 from django.contrib.auth.hashers import make_password
+from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, Group
+from django.db import models, transaction
 from django.db.models.signals import post_save, m2m_changed
 from django.dispatch import receiver
+from django.utils import timezone
 
 from aai.models import UserGroup
 from exercise.models import Team, Exercise, Definition
@@ -117,9 +115,9 @@ class User(AbstractBaseUser):
         unique=True,
         editable=False,
     )
-    username = models.CharField(max_length=150, blank=False)
-    first_name = models.CharField(max_length=150, blank=True, null=True)
-    last_name = models.CharField(max_length=150, blank=True, null=True)
+    username = models.TextField(blank=False)
+    first_name = models.TextField(blank=True, null=True)
+    last_name = models.TextField(blank=True, null=True)
     date_joined = models.DateTimeField(default=timezone.now)
     group = models.ForeignKey(Group, on_delete=models.SET_NULL, null=True)
     is_staff = models.BooleanField(default=False)