diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cae401b3e48e85b933157a52c5b7bc42a0820683
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,30 @@
+stages:
+  - build
+
+image: docker:20.10.8
+
+variables:
+  DOCKER_TLS_CERTDIR: "/certs"
+  IMAGE_TAG_APP: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+  IMAGE_TAG_NGINX: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-nginx
+
+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
+
+build_nginx:
+  stage: build
+  when: manual
+  script:
+    - docker pull $CI_REGISTRY_IMAGE:test-nginx || true
+    - docker build --cache-from $CI_REGISTRY_IMAGE:test-nginx -t $IMAGE_TAG_NGINX . -f Dockerfile.nginx
+    - docker push $IMAGE_TAG_NGINX
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..2f94022e81653bbafb194090e1b9a98fb4559a6a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,30 @@
+FROM python:3.10
+
+RUN mkdir /app
+WORKDIR /app
+
+RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash -
+RUN apt-get install nodejs && rm -rf /var/lib/apt/lists/*
+
+COPY . .
+
+RUN pip install -r requirements/base.txt -r requirements/production.txt
+RUN npm install
+RUN npm run build
+
+# Placeholder values so the static files collect
+RUN DATABASE_URL=postgres://x/x \
+    SECRET_KEY=x \
+    ALLOWED_HOSTS=x \
+    python manage.py collectstatic --noinput --settings=registry.settings.production
+
+RUN bash -c "adduser --disabled-login --quiet --gecos app app &&  \
+             chmod -R o+r /app/ && \
+             chmod o+x /app/run.sh"
+USER app
+
+ENV DJANGO_SETTINGS_MODULE "registry.settings.production"
+
+EXPOSE 8000
+
+CMD ["bash", "run.sh"]
diff --git a/Dockerfile.nginx b/Dockerfile.nginx
new file mode 100644
index 0000000000000000000000000000000000000000..7f2e08a2efd6984816a72be5b6d930cdc4ec1687
--- /dev/null
+++ b/Dockerfile.nginx
@@ -0,0 +1,3 @@
+FROM nginx:1.18
+EXPOSE 8080
+ADD nginx.conf /etc/nginx/conf.d/contract_registry.conf
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000000000000000000000000000000000000..4725c76f1dab2ac47717af04a9e227b6af423a30
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,23 @@
+upstream contract_registry {
+    ip_hash;
+    server app:8000;
+}
+
+server {
+    server_name contract_registry;
+    listen 8080;
+
+    client_max_body_size 10M;
+
+    proxy_connect_timeout   60;
+    proxy_send_timeout      60;
+    proxy_read_timeout      60;
+    send_timeout            60;
+
+    location / {
+        proxy_pass http://contract_registry/;
+        proxy_set_header Host $host;
+        proxy_set_header X-Forwarded-Proto https;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    }
+}
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..416f8b5af8e41c7f70372fa9aae0ee92b8bf332b
--- /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 registry.wsgi