diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0924ee3ad3ee0e8fb34559e40b668d0e405fd26f..6f9b78b3bfa0c8a98905aac4d3985151ef1c72e9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,6 @@
 stages:
   - build
+  - build docker image
   - test
   - upload artifacts
 
@@ -120,11 +121,6 @@ test:register:element-web-stable:
     - "export CARGO_PROFILE_RELEASE_LTO=thin"
     - time cargo build --target $TARGET --release
     - 'mv "target/$TARGET/release/conduit" "conduit-$TARGET"'
-  artifacts:
-    name: "conduit-$TARGET"
-    expose_as: "Binary"
-    paths:
-      - "conduit-$TARGET"
 
 build:cargo:x86_64-unknown-linux-gnu:
   extends: .build-cargo-shared-settings
@@ -132,6 +128,11 @@ build:cargo:x86_64-unknown-linux-gnu:
     TARGET: "x86_64-unknown-linux-gnu"
   rules:
     - if: "$CI_COMMIT_BRANCH"
+  artifacts:
+    name: "conduit-x86_64-unknown-linux-gnu"
+    paths:
+      - "conduit-x86_64-unknown-linux-gnu"
+    expose_as: "Release binary x86_64-unknown-linux-gnu"
 
 build:cargo:armv7-unknown-linux-gnueabihf:
   extends: .build-cargo-shared-settings
@@ -141,6 +142,11 @@ build:cargo:armv7-unknown-linux-gnueabihf:
     CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
     CC_armv7_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
     CXX_armv7_unknown_linux_gnueabihf: arm-linux-gnueabihf-g++
+  artifacts:
+    name: "conduit-armv7-unknown-linux-gnueabihf"
+    paths:
+      - "conduit-armv7-unknown-linux-gnueabihf"
+    expose_as: "Release binary armv7-unknown-linux-gnueabihf"
 
 build:cargo:aarch64-unknown-linux-gnu:
   extends: .build-cargo-shared-settings
@@ -152,6 +158,11 @@ build:cargo:aarch64-unknown-linux-gnu:
     CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
     TARGET_CC: "/usr/bin/aarch64-linux-gnu-gcc-8"
     TARGET_AR: "/usr/bin/aarch64-linux-gnu-gcc-ar-8"
+  artifacts:
+    name: "conduit-aarch64-unknown-linux-gnu"
+    paths:
+      - "conduit-aarch64-unknown-linux-gnu"
+    expose_as: "Release binary aarch64-unknown-linux-gnu"
 
 build:cargo:x86_64-unknown-linux-musl:
   extends: .build-cargo-shared-settings
@@ -166,6 +177,11 @@ build:cargo:x86_64-unknown-linux-musl:
     - "cat /etc/*-release && rustc --version && cargo --version"  # Print version info for debugging
     - "rustup target add $TARGET"
     - "apk add libc-dev"
+  artifacts:
+    name: "conduit-x86_64-unknown-linux-musl"
+    paths:
+      - "conduit-x86_64-unknown-linux-musl"
+    expose_as: "Release binary x86_64-unknown-linux-musl"
 
 
 # --------------------------------------------------------------------- #
@@ -198,20 +214,60 @@ build:cargo:x86_64-unknown-linux-musl:
   script:
     - time cargo deb --target $TARGET
     - 'mv target/$TARGET/debian/*.deb "conduit-$TARGET.deb"'
-  artifacts:
-    name: "conduit-$TARGET.deb"
-    expose_as: "Debian Package"
-    paths:
-      - "conduit-$TARGET.deb"
 
 build:cargo-deb:x86_64-unknown-linux-gnu:
   extends: .build-cargo-deb-shared-settings
   variables:
     TARGET: "x86_64-unknown-linux-gnu"
     NEEDED_PACKAGES: ""
+  artifacts:
+    name: "conduit-x86_64-unknown-linux-gnu.deb"
+    paths:
+      - "conduit-x86_64-unknown-linux-gnu.deb"
+    expose_as: "Debian Package x86_64"
+
+
 
+# --------------------------------------------------------------------- #
+#  Create and publish docker image                                      #
+# --------------------------------------------------------------------- #
+
+.docker-shared-settings:
+  stage: "build docker image"
+  needs: []
+  interruptible: true
+  image:
+    name: "gcr.io/kaniko-project/executor:debug"
+    entrypoint: [""]
+  tags: ["docker"]
+  variables:
+    # Configure Kaniko Caching: https://cloud.google.com/build/docs/kaniko-cache
+    KANIKO_CACHE_ARGS: "--cache=true --cache-copy-layers=true --cache-ttl=120h --cache-repo $CI_REGISTRY_IMAGE/kaniko-ci-cache"
+  before_script:
+    - "mkdir -p /kaniko/.docker"
+    - 'echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json'
 
 
+# Build a docker image by packaging up the x86_64-unknown-linux-musl binary into an alpine image
+build:docker:main:
+  extends: .docker-shared-settings
+  needs:
+    - "build:cargo:x86_64-unknown-linux-musl"
+  script:
+    - >
+      /kaniko/executor
+      $KANIKO_CACHE_ARGS
+      --context $CI_PROJECT_DIR
+      --build-arg CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+      --build-arg VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml)
+      --build-arg "GIT_REF=$CI_COMMIT_REF_NAME"
+      --dockerfile "$CI_PROJECT_DIR/docker/ci-binaries-packaging.Dockerfile"
+      --destination "$CI_REGISTRY_IMAGE/conduit:latest"
+      --destination "$CI_REGISTRY_IMAGE/conduit:alpine"
+      --destination "$CI_REGISTRY_IMAGE/conduit:commit-$CI_COMMIT_SHORT_SHA"
+  rules:
+    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+
 
 # --------------------------------------------------------------------- #
 #  Store binaries as package so they have download urls                 #
diff --git a/docker/ci-binaries-packaging.Dockerfile b/docker/ci-binaries-packaging.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..43ebc98d3d540c5e3e0f450ed641f01d033ae307
--- /dev/null
+++ b/docker/ci-binaries-packaging.Dockerfile
@@ -0,0 +1,73 @@
+# ---------------------------------------------------------------------------------------------------------
+# This Dockerfile is intended to be built as part of Conduit's CI pipeline.
+# It does not build Conduit in Docker, but just copies the matching build artifact from the build job.
+# As a consequence, this is not a multiarch capable image. It always expects and packages a x86_64 binary.
+#
+# It is mostly based on the normal Conduit Dockerfile, but adjusted in a few places to maximise caching.
+# Credit's for the original Dockerfile: Weasy666.
+# ---------------------------------------------------------------------------------------------------------
+
+FROM alpine:3.12
+
+ARG CREATED
+ARG VERSION
+ARG GIT_REF
+
+ENV CONDUIT_CONFIG="/srv/conduit/conduit.toml"
+
+# Labels according to https://github.com/opencontainers/image-spec/blob/master/annotations.md
+# including a custom label specifying the build command
+LABEL org.opencontainers.image.created=${CREATED} \
+      org.opencontainers.image.authors="Conduit Contributors" \
+      org.opencontainers.image.title="Conduit" \
+      org.opencontainers.image.version=${VERSION} \
+      org.opencontainers.image.vendor="Conduit Contributors" \
+      org.opencontainers.image.description="A Matrix homeserver written in Rust" \
+      org.opencontainers.image.url="https://conduit.rs/" \
+      org.opencontainers.image.revision=${GIT_REF} \
+      org.opencontainers.image.source="https://gitlab.com/famedly/conduit.git" \
+      org.opencontainers.image.licenses="Apache-2.0" \
+      org.opencontainers.image.documentation="" \
+      org.opencontainers.image.ref.name=""
+
+# Standard port on which Conduit launches. You still need to map the port when using the docker command or docker-compose.
+EXPOSE 6167
+
+# create data folder for database
+RUN mkdir -p /srv/conduit/.local/share/conduit
+
+# Add www-data user and group with UID 82, as used by alpine
+# https://git.alpinelinux.org/aports/tree/main/nginx/nginx.pre-install
+RUN set -x ; \
+    addgroup -Sg 82 www-data 2>/dev/null ; \
+    adduser -S -D -H -h /srv/conduit -G www-data -g www-data www-data 2>/dev/null ; \
+    addgroup www-data www-data 2>/dev/null && exit 0 ; exit 1
+
+# Change ownership of Conduit files to www-data user and group
+RUN chown -cR www-data:www-data /srv/conduit
+
+# Install packages needed to run Conduit
+RUN apk add --no-cache \
+        ca-certificates \
+        curl \
+        libgcc
+
+# Create a volume for the database, to persist its contents
+VOLUME ["/srv/conduit/.local/share/conduit"]
+
+# Test if Conduit is still alive, uses the same endpoint as Element
+HEALTHCHECK --start-period=5s \
+    CMD curl --fail -s "http://localhost:$(grep -m1 -o 'port\s=\s[0-9]*' conduit.toml | grep -m1 -o '[0-9]*')/_matrix/client/versions" || \
+        curl -k --fail -s "https://localhost:$(grep -m1 -o 'port\s=\s[0-9]*' conduit.toml | grep -m1 -o '[0-9]*')/_matrix/client/versions" || \
+        exit 1
+
+# Set user to www-data
+USER www-data
+# Set container home directory
+WORKDIR /srv/conduit
+# Run Conduit
+ENTRYPOINT [ "/srv/conduit/conduit" ]
+
+
+# Copy the Conduit binary into the image at the latest possible moment to maximise caching:
+COPY ./conduit-x86_64-unknown-linux-musl /srv/conduit/conduit