diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..988df2788911128c1668ad27e3f9f3f11fd60b97
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,48 @@
+image:
+  name: docker/compose:latest
+
+stages:
+  - build
+  - test
+
+cache:
+  paths:
+    - host-files/docker-stack.yml
+
+variables:
+  DBADMIN_USER: postgres
+  DBADMIN_PASSWORD: postgres
+  DBPROJECT_USER: postgres
+  DBPROJECT_PASSWORD: postgres
+  DOMAIN: localhost
+  ESAP_DB_IMAGE: ${CI_REGISTRY_IMAGE}:latest
+
+before_script:
+  - docker version
+  - docker-compose version
+
+build:
+  stage: build
+  variables:
+    DOMAIN: backend
+    SMTP_HOST: ""
+    TRAEFIK_PUBLIC_NETWORK_IS_EXTERNAL: "false"
+    INSTALL_DEV: "true"
+  artifacts:
+    paths:
+      - host-files/docker-stack.yml
+    expire_in: 1 week
+  script:
+    - docker-compose -f host-files/docker-compose.yml config > host-files/docker-stack.yml
+    - docker-compose -f host-files/docker-stack.yml build
+    - echo ${CI_JOB_TOKEN} | docker login -u "${CI_REGISTRY_USER}" --password-stdin "${CI_REGISTRY}"
+    - docker push ${ESAP_DB_IMAGE}
+
+test-functional:
+  stage: test
+  script:
+    - docker-compose -f host-files/docker-stack.yml down -v --remove-orphans
+    - docker-compose -f host-files/docker-stack.yml pull
+    - docker-compose -f host-files/docker-stack.yml up -d
+    - docker-compose -f host-files/docker-stack.yml exec -T esap-db bash /code/scripts/run-tests.sh
+    - docker-compose -f host-files/docker-stack.yml down -v --remove-orphans
diff --git a/Dockerfile b/Dockerfile
index 24cdc311a5af1f73c6d66cfa265a60e405294579..a6e714b124186103584ec31e593e8f5a45e63246 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,5 +26,5 @@ RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; els
 ARG INSTALL_JUPYTER=false
 RUN bash -c "if [ $INSTALL_JUPYTER == 'true' ] ; then pip install jupyterlab ; fi"
 
-COPY ./app /code
+COPY . /code
 ENV PYTHONPATH=/code
diff --git a/host-files/docker-compose.yml b/host-files/docker-compose.yml
index 349769e3338f9902edecc9b2ed8d2df24d500c7d..a32af31049065aef4f638c4f67dfc0bd50afb666 100644
--- a/host-files/docker-compose.yml
+++ b/host-files/docker-compose.yml
@@ -2,28 +2,8 @@ version: "3"
 
 services:
 
-  dbadmin:
-    image: postgres:12
-    ports:
-      - "5432:5432"
-    environment:
-      - POSTGRES_USER=${DBADMIN_USER}
-      - POSTGRES_PASSWORD=${DBADMIN_PASSWORD}
-      - POSTGRES_DB=dbadmin
-
-  dbproject1:
-    image: postgres:12
-    expose:
-      - "5433"
-    ports:
-      - "5433:5433"
-    command: -p 5433
-    environment:
-      - POSTGRES_USER=${DBPROJECT_USER}
-      - POSTGRES_PASSWORD=${DBPROJECT_PASSWORD}
-
   esap-db:
-    image: esap-db:latest
+    image: ${ESAP_DB_IMAGE:-esap-db:latest}
     container_name: esap-db
     stdin_open: true  # docker run -i
     tty: true         # docker run -t
@@ -45,14 +25,33 @@ services:
     build:
       context: ..
       args:
-        INSTALL_DEV: ${INSTALL_DEV-false}
-    command: bash -c "scripts/prepare-app.sh && uvicorn app.main:app --port 8001 --host 0.0.0.0 --reload"
-    volumes:
-      - ..:/code
+        INSTALL_DEV: ${INSTALL_DEV:-false}
+    command: bash -c "/code/scripts/prepare-app.sh && uvicorn app.main:app --port 8001 --host 0.0.0.0 --reload"
     ports:
       - "8001:8001"
     depends_on:
       - dbadmin
+      - dbproject1
+
+  dbadmin:
+    image: postgres:12
+    ports:
+      - "5432:5432"
+    environment:
+      - POSTGRES_USER=${DBADMIN_USER}
+      - POSTGRES_PASSWORD=${DBADMIN_PASSWORD}
+      - POSTGRES_DB=dbadmin
+
+  dbproject1:
+    image: postgres:12
+    expose:
+      - "5433"
+    ports:
+      - "5433:5433"
+    command: -p 5433
+    environment:
+      - POSTGRES_USER=${DBPROJECT_USER}
+      - POSTGRES_PASSWORD=${DBPROJECT_PASSWORD}
 
   pgadmin:
     container_name: pgadmin