diff --git a/.docker/Dockerfile b/.docker/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..319682d386d571f706290ca4083efd017849e699
--- /dev/null
+++ b/.docker/Dockerfile
@@ -0,0 +1,10 @@
+FROM debian:buster
+RUN apt-get update \
+    && \
+    apt-get install -y \
+        make \
+        octave \
+        python3-pip \
+        python3-sphinx
+
+RUN pip3 install breathe miss_hit sphinx-rtd-theme recommonmark 
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..164ac33b41939514ce25f13af311e7ce3d54bd4d
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,158 @@
+#
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com>
+# Copyright (C) 2021 Daniel Hornung <d.hornung@indiscale.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+
+variables:
+  OCTAVE_REGISTRY_IMAGE: $CI_REGISTRY_IMAGE/testenv:$CI_COMMIT_REF_NAME
+
+  # CPPINTTEST_PIPELINE: https://gitlab.indiscale.com/api/v4/projects/111/trigger/pipeline
+  # CPPINTTEST_BRANCHES: https://gitlab.indiscale.com/api/v4/projects/111/repository/branches
+  # GIT_SUBMODULE_STRATEGY: normal
+
+  # ## FOR DEBUGGING
+  # TRIGGERED_BY_REPO: CPPLIB
+  # TRIGGERED_BY_REF: $CI_COMMIT_REF_NAME
+  # TRIGGERED_BY_HASH: $CI_COMMIT_SHORT_SHA
+
+
+image: $OCTAVE_REGISTRY_IMAGE
+
+stages:
+  - setup
+  - test
+  - deploy
+
+######## Setup ########
+
+# Build a docker image in which tests for this repository can run
+build-testenv:
+  tags: [ cached-dind ]
+  stage: setup
+  image: docker:20.10
+  only:
+    - main
+    - schedules
+    - f-pipeline
+  script:
+    - cd .docker
+    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+    # Attempt to reuse the previously stored image.
+    - docker pull "$OCTAVE_REGISTRY_IMAGE" || true
+    - docker build
+      --pull
+      --cache-from "$OCTAVE_REGISTRY_IMAGE"
+      -t "$OCTAVE_REGISTRY_IMAGE" .
+    - docker push "$OCTAVE_REGISTRY_IMAGE"
+
+# info:
+#   tags: [cached-dind]
+#   image: docker:20.10
+#   stage: info
+#   needs: []
+#   script:
+#     - echo "Pipeline triggered by $TRIGGERED_BY_REPO@$TRIGGERED_BY_REF ($TRIGGERED_BY_HASH)"
+#     - echo "$CPPLIB_REGISTRY_IMAGE"
+#     - echo "$CPPINTTEST_PIPELINE"
+#     - echo "$CPPINTTEST_BRANCHES"
+#     - echo "$GIT_SUBMODULE_STRATEGY"
+
+# Build a docker image in which tests for this repository can run
+# build-testenv:
+#   tags: [ cached-dind ]
+#   image: docker:20.10
+#   stage: setup
+#   script:
+#     - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+#       # use here general latest or specific branch latest...
+#     - docker pull $CPPLIB_REGISTRY_IMAGE|| true
+#     - docker build
+#       --file .docker/Dockerfile
+#       --pull
+#       --cache-from $CPPLIB_REGISTRY_IMAGE
+#       --tag $CPPLIB_REGISTRY_IMAGE .
+#     - docker push $CPPLIB_REGISTRY_IMAGE
+
+# Formatting with miss_hit
+code_style:
+  tags: [ docker ]
+  stage: test
+  script:
+    - make style
+  allow_failure: true
+
+# Unit tests
+test:
+  tags: [ docker ]
+  stage: test
+  script:
+    - make test
+
+# trigger the integration tests
+# trigger_inttest:
+#   tags: [ docker ]
+#   stage: deploy
+#   script:
+
+#     ## Determine the cppinttest branch...
+#     # ... use an f-branch if posible...
+#     - if echo "$CI_COMMIT_REF_NAME" | grep -c "^f-" ; then
+#         CPPINT_REF=$CI_COMMIT_REF_NAME ;
+#       fi;
+#     # ... or use main if possible...
+#     - if [[ "$CI_COMMIT_REF_NAME" == "main" ]] ; then
+#         CPPINT_REF=main ;
+#       fi
+#     # ... and fall-back to dev
+#     - CPPINT_REF=${CPPINT_REF:-dev}
+
+#     - echo "Triggering caosdb-cppinttest@${CPPINT_REF}"
+#     - curl -w "%{stderr}HTTPCODE=%{http_code}" -X POST
+#       -F token=$CI_JOB_TOKEN
+#       -F "variables[TRIGGERED_BY_REPO]=$TRIGGERED_BY_REPO"
+#       -F "variables[TRIGGERED_BY_REF]=$TRIGGERED_BY_REF"
+#       -F "variables[TRIGGERED_BY_HASH]=$TRIGGERED_BY_HASH"
+#       -F "variables[CPPLIB_REGISTRY_IMAGE]=$CPPLIB_REGISTRY_IMAGE"
+#       -F ref=${CPPINT_REF} $CPPINTTEST_PIPELINE 2>HTTPCODE
+
+#     # fail if the request failed
+#     - grep -c "HTTPCODE=2" HTTPCODE
+
+# Build the sphinx documentation and make it ready for deployment by Gitlab Pages
+# Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages
+# .pages_prepare: &pages_prepare
+#   tags: [ cached-dind ]
+#   stage: deploy
+#   script:
+#     - mkdir -p build
+#     - cd build
+#     - cmake ..
+#     - cmake --build . --target doc-sphinx
+#     - cp -r doc/sphinx_out ../public
+
+# test_pages:
+#   <<: *pages_prepare
+
+# pages:
+#   <<: *pages_prepare
+#   only:
+#     refs:
+#       - main
+#   artifacts:
+#     paths:
+#       - public
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index b4e1f8ab97c192459c41675791bd12c6294e7c87..97b2a92980109912b1dfe7878cd94ccb15c8c84f 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -3,3 +3,8 @@
 * caosdb-cpplib
 * octave >= 4.4
 * liboctave-dev ?
+
+## Useful tools ##
+
+* Linter: [`miss_hit`](https://github.com/florianschanda/miss_hit): `pip3 install --user miss_hit`
+
diff --git a/Makefile b/Makefile
index 0332e8a738cd3f498c2293b98334f8b754f2e77c..1d5197bc37d983038cc308f2a7d9380c84ed34a4 100644
--- a/Makefile
+++ b/Makefile
@@ -28,3 +28,11 @@ help:
 .PHONY: doc
 doc:
 	$(MAKE) -C src/doc html
+
+.PHONY: style
+style:
+	mh_style --octave src
+
+.PHONY: test
+test:
+	cd src && octave Run_Test.m
diff --git a/src/.miss_hit b/src/.miss_hit
new file mode 100644
index 0000000000000000000000000000000000000000..f15ccfe29ff4a2e193fb4ae51ca5a7bd1cc3a88d
--- /dev/null
+++ b/src/.miss_hit
@@ -0,0 +1,7 @@
+# -*- mode:conf; -*-
+# See https://florianschanda.github.io/miss_hit/style_checker.html
+
+line_length: 100
+regex_function_name: "[a-z]+(_[a-z]+)*"
+suppress_rule: "whitespace_comments"
+tab_width: 2
diff --git a/src/Example.m b/src/Example.m
new file mode 100644
index 0000000000000000000000000000000000000000..6b6abe11364cf8df1e00aed202dd1612b7324a7f
--- /dev/null
+++ b/src/Example.m
@@ -0,0 +1,26 @@
+% (C) Copyright 2021 IndiScale GmbH <info@indiscale.com>
+% (C) Copyright 2021 Daniel Hornung <d.hornung@indiscale.com>
+%
+% This file is a part of the CaosDB Project.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU Affero General Public License as
+% published by the Free Software Foundation, either version 3 of the
+% License, or (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU Affero General Public License for more details.
+%
+% You should have received a copy of the GNU Affero General Public License
+% along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+%% Example file with dummy code from https://www.gnu.org/software/octave/
+
+x = -10:0.1:10;  % Create an evenly-spaced vector from -10..10
+y = sin(x);      % y is also a vector
+plot (x, y);
+title ("Simple 2-D Plot");
+xlabel ("x");
+ylabel ("sin (x)");
diff --git a/src/Run_Test.m b/src/Run_Test.m
new file mode 100644
index 0000000000000000000000000000000000000000..6854f196f81a9167ae7d286a8f500ff40e2eb00c
--- /dev/null
+++ b/src/Run_Test.m
@@ -0,0 +1,31 @@
+% (C) Copyright 2021 IndiScale GmbH <info@indiscale.com>
+% (C) Copyright 2021 Daniel Hornung <d.hornung@indiscale.com>
+%
+% This file is a part of the CaosDB Project.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU Affero General Public License as
+% published by the Free Software Foundation, either version 3 of the
+% License, or (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU Affero General Public License for more details.
+%
+% You should have received a copy of the GNU Affero General Public License
+% along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+% ------------------------------------------------------------------------
+% Run the tests, exit with their value.
+
+addpath("dummy");
+
+[n, nmax, nxfail, nbug, nskip, nrtskip, nregression] = ...
+  test("some_function.m", "verbose");
+
+disp(["Passed " num2str(n) " out of " num2str(nmax) " tests."]);
+if n == nmax
+  exit(0);
+end
+exit(1);
diff --git a/src/dummy/some_function.m b/src/dummy/some_function.m
new file mode 100644
index 0000000000000000000000000000000000000000..dcfaaac16d0b9942cc9b33632445b30dfb7a6b38
--- /dev/null
+++ b/src/dummy/some_function.m
@@ -0,0 +1,37 @@
+% (C) Copyright 2021 IndiScale GmbH <info@indiscale.com>
+% (C) Copyright 2021 Daniel Hornung <d.hornung@indiscale.com>
+%
+% This file is a part of the CaosDB Project.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU Affero General Public License as
+% published by the Free Software Foundation, either version 3 of the
+% License, or (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU Affero General Public License for more details.
+%
+% You should have received a copy of the GNU Affero General Public License
+% along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+function result = some_function(arg1, arg2)
+  if arg1 >= arg2
+    error("arg1 must be smaller than arg2!");
+  end
+  x = arg1:0.1:arg2;
+  result = sin(x);
+end
+
+%%%%%%%%%%
+% See https://wiki.octave.org/Tests and
+% https://octave.org/doc/interpreter/Test-Functions.html for unit tests.
+
+%!assert (some_function(1, 2))
+%!assert (some_function(-0.1, 0.1)(2) == 0)
+%!error <arg1 must be smaller than arg2!> (some_function(3, 2))
+
+%!demo
+%! ## Try out some_function(a, b):
+%! some_function([-1:1])
diff --git a/src/example.m b/src/example.m
deleted file mode 100644
index a2ccc0f68d8f233c0e1fb0b8e92ac579c184c5af..0000000000000000000000000000000000000000
--- a/src/example.m
+++ /dev/null
@@ -1,27 +0,0 @@
-# This file is a part of the CaosDB Project.
-#
-# Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
-# Copyright (C) 2021 Daniel Hornung <d.hornung@indiscale.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-
-## Example file with dummy code from https://www.gnu.org/software/octave/
-
-x = -10:0.1:10; # Create an evenly-spaced vector from -10..10
-y = sin(x);     # y is also a vector
-plot (x, y);
-title ("Simple 2-D Plot");
-xlabel ("x");
-ylabel ("sin (x)");