Repository: RasaHQ/rasa
Pull request: Docker builds - refactor
**Proposed changes**:
- Resolves #7574
- Use Ubuntu 20.04 as a base image
- Build a base image with MITIE
- Tag the latest stable version as `latest`
- Tag Docker images build against the master branch as `master`
- Add options for building Docker images locally to the Makefile

**Status (please check what you already did)**:
- [ ] added some tests for the functionality
- [x] updated the documentation
- [x] updated the changelog (please check [changelog](https://github.com/RasaHQ/rasa/tree/master/changelog) for instructions)
- [ ] reformat files using `black` (please check [Readme](https://github.com/RasaHQ/rasa#code-style) for instructions)


Problem statement:
Docker builds - refactor
Proposed changes:
- Switch to Python 3.8 and Ubuntu 20.04 as a base image
- Refactor Dockerfiles in order to manage cache better
- Build a base image with MITIE
- Tag the latest stable version as `latest`
- Tag Docker images build against the master branch as `master`
- Add options for building Docker images locally to the Makefile

Hints from issue discussion:

Commit message: Add a changelog entry + update the README

Patch to review:
diff --git a/.dockerignore b/.dockerignore
index 29e6912ac6c6..8d8690403396 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,3 +4,5 @@ docs
 **/*.pyc
 **/__pycache__
 !docker/configs
+rasa/tests
+rasa/scripts
diff --git a/.gitattributes b/.gitattributes
index 5378fe089bc2..8f12c43c197e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,3 @@
-* -text
\ No newline at end of file
+* -text
+# Reclassifies `Dockerfile*` files as Dockerfile:
+Dockerfile.* linguist-language=Dockerfile
diff --git a/.github/workflows/continous-integration.yml b/.github/workflows/continous-integration.yml
index 689c6bc7e709..450850d8f841 100644
--- a/.github/workflows/continous-integration.yml
+++ b/.github/workflows/continous-integration.yml
@@ -28,6 +28,8 @@ env:
   # needed to fix issues with boto during testing:
   # https://github.com/travis-ci/travis-ci/issues/7940
   BOTO_CONFIG: /dev/null
+  IS_TAG_BUILD: ${{ startsWith(github.event.ref, 'refs/tags') }}
+  DOCKERHUB_USERNAME: tmbo
 
 jobs:
   changes:
@@ -279,27 +281,170 @@ jobs:
         COVERALLS_SERVICE_NAME: github
       run: poetry run coveralls
 
+  build_docker_base_images_and_set_env:
+    name: Build Docker base images and setup environment
+    runs-on: ubuntu-20.04
+    outputs:
+      base_image_hash: ${{ steps.check_image.outputs.base_image_hash }}
+      base_mitie_image_hash: ${{ steps.check_image.outputs.base_mitie_image_hash }}
+      # Tag name used for images created during Docker image builds, e.g. 3886 - a PR number
+      image_tag: ${{ steps.set_output.outputs.image_tag }}
+      # Return 'true' if tag version is equal or higher than the latest tagged Rasa version
+      is_newest_version: ${{ steps.rasa_get_version.outputs.is_newest_version }}
+    steps:
+      # Due to an issue with checking out a wrong commit, we make sure
+      # to checkout HEAD commit for a pull request.
+      # More details: https://github.com/actions/checkout/issues/299
+    - name: Checkout pull request HEAD commit instead of merge commit 🕝
+      uses: actions/checkout@v2
+      if: github.event_name == 'pull_request'
+      with:
+        ref: ${{ github.event.pull_request.head.sha }}
+
+    - name: Checkout git repository 🕝
+      uses: actions/checkout@v2
+      if: github.event_name != 'pull_request'
+
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v1
+      with:
+        version: latest
+        driver: docker
+
+    - name: Login to DockerHub Registry 🔢
+      run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ env.DOCKERHUB_USERNAME }} --password-stdin || true
+
+    - name: Check if tag version is equal or higher than the latest tagged Rasa version
+      id: rasa_get_version
+      if: env.IS_TAG_BUILD == 'true'
+      run: |
+        # Get latest tagged Rasa version
+        git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
+        # Fetch branch history
+        git fetch --prune --unshallow
+        LATEST_TAGGED_NON_ALPHA_RASA_VERSION=$(git tag | sort -r -V | grep -E "^[0-9.]+$" | head -n1)
+        CURRENT_TAG=${GITHUB_TAG/refs\/tags\//}
+        # Return 'true' if tag version is equal or higher than the latest tagged Rasa version
+        IS_NEWEST_VERSION=$((printf '%s\n%s\n' "${LATEST_TAGGED_NON_ALPHA_RASA_VERSION}" "$CURRENT_TAG" \
+          | sort -V -C && echo true || echo false) || true)
+        # Avoid that the script gets released for alphas or release candidates
+        if [[ "${IS_NEWEST_VERSION}" == "true" && "$CURRENT_TAG" =~ ^[0-9.]+$ ]]; then
+          echo "::set-output name=is_newest_version::true"
+        else
+          echo "::set-output name=is_newest_version::false"
+        fi
+
+    - name: Check if a base image exists
+      id: check_image
+      run: |
+        DOCKERHUB_TAGS_URL="https://registry.hub.docker.com/v2/repositories/rasa/rasa/tags?page_size=10000"
+
+        # Base image
+        BASE_IMAGE_HASH=${{ hashFiles('docker/Dockerfile.base') }}
+        BASE_IMAGE_TAG=$(curl -s ${DOCKERHUB_TAGS_URL} | jq -r '.results[] | select(.name == "base-'${BASE_IMAGE_HASH}'") | .name')
+
+        echo "::set-output name=base_image_hash::${BASE_IMAGE_HASH}"
+
+        echo $BASE_IMAGE_TAG $BASE_IMAGE_HASH
+        if [[ "${BASE_IMAGE_TAG}" == "base-${BASE_IMAGE_HASH}" ]]; then
+          echo "::set-output name=base_exists::true"
+        else
+          echo "::set-output name=base_exists::false"
+        fi
+
+        # Base MITIE image
+        BASE_MITIE_IMAGE_HASH=${{ hashFiles('docker/Dockerfile.base-mitie') }}
+        BASE_MITIE_IMAGE_TAG=$(curl -s ${DOCKERHUB_TAGS_URL} | jq -r '.results[] | select(.name == "base-mitie-'${BASE_MITIE_IMAGE_HASH}'") | .name')
+
+        echo "::set-output name=base_mitie_image_hash::${BASE_MITIE_IMAGE_HASH}"
+
+        echo $BASE_MITIE_IMAGE_TAG $BASE_MITIE_IMAGE_HASH
+        if [[ "${BASE_MITIE_IMAGE_TAG}" == "base-mitie-${BASE_MITIE_IMAGE_HASH}" ]]; then
+          echo "::set-output name=base_mitie_exists::true"
+        else
+          echo "::set-output name=base_mitie_exists::false"
+        fi
+
+    - name: Build Docker base image and push 🛠 ⬆
+      if: steps.check_image.outputs.base_exists == 'false' || env.IS_TAG_BUILD == 'true'
+      run: |
+        export IMAGE_TAG=${{ hashFiles('docker/Dockerfile.base') }}
+        docker buildx bake -f docker/docker-bake.hcl base --push
+
+    - name: Build Docker mitie base image and push 🛠 ⬆
+      if: steps.check_image.outputs.base_mitie_exists == 'false'
+      run: |
+        export IMAGE_TAG=${{ hashFiles('docker/Dockerfile.base-mitie') }}
+        docker buildx bake -f docker/docker-bake.hcl base-mitie --push
+
+    # Set environment variables for a pull request
+    #
+    # In this scenario, we've created a PR #1234
+    #
+    # Example output:
+    # IMAGE_TAG=1234
+    - name: Set environment variables - pull_request
+      if: github.event_name == 'pull_request' && env.IS_TAG_BUILD == 'false'
+      run: |
+        echo "IMAGE_TAG=${{ github.event.number }}" >> $GITHUB_ENV
+
+    # Set environment variables for a tag
+    #
+    # In this scenario, we've pushed the '2.0.6' tag
+    #
+    # Example output:
+    # TAG_NAME=2.0.6
+    # IMAGE_TAG=2.0.6
+    - name: Set environment variables - push - tag
+      if: github.event_name == 'push' && env.IS_TAG_BUILD == 'true'
+      run: |
+        TAG_NAME=${GITHUB_REF#refs/tags/}
+        echo "IMAGE_TAG=${TAG_NAME}" >> $GITHUB_ENV
+
+    # Set environment variables for a branch
+    #
+    # In this scenario, we've pushed changes into the master branch
+    #
+    # Example output:
+    # IMAGE_TAG=master
+    - name: Set environment variables - push - branch
+      if: github.event_name == 'push' && env.IS_TAG_BUILD == 'false'
+      run: |
+        BRANCH_NAME=${GITHUB_REF#refs/heads/}
+        SAFE_BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\./-/g')
+        echo "IMAGE_TAG=${BRANCH_NAME}" >> $GITHUB_ENV
+
+    - name: Set output
+      id: set_output
+      run: |
+        echo "::set-output name=image_tag::${{ env.IMAGE_TAG }}"
+
   docker:
     name: Build Docker
     runs-on: ubuntu-latest
-    needs: [changes]
+    needs: [changes, build_docker_base_images_and_set_env]
+    env:
+      IMAGE_TAG: ${{ needs.build_docker_base_images_and_set_env.outputs.image_tag }}
+      BASE_IMAGE_HASH: ${{ needs.build_docker_base_images_and_set_env.outputs.base_image_hash }}
+      BASE_MITIE_IMAGE_HASH: ${{ needs.build_docker_base_images_and_set_env.outputs.base_mitie_image_hash }}
 
     strategy:
       matrix:
-        image:
-          - {"file": "Dockerfile", "tag_ext": ""}
-          - {"file": "docker/Dockerfile_full", "tag_ext": "-full"}
-          - {"file": "docker/Dockerfile_pretrained_embeddings_mitie_en", "tag_ext": "-mitie-en"}
-          - {"file": "docker/Dockerfile_pretrained_embeddings_spacy_de", "tag_ext": "-spacy-de"}
-          - {"file": "docker/Dockerfile_pretrained_embeddings_spacy_en", "tag_ext": "-spacy-en"}
-
-    env:
-      DOCKERHUB_USERNAME: tmbo
+        image: [default, full, mitie-en, spacy-de, spacy-en]
 
     steps:
+      # Due to an issue with checking out a wrong commit, we make sure
+      # to checkout HEAD commit for a pull request.
+      # More details: https://github.com/actions/checkout/issues/299
+    - name: Checkout pull request HEAD commit instead of merge commit 🕝
+      uses: actions/checkout@v2
+      if: github.event_name == 'pull_request'
+      with:
+        ref: ${{ github.event.pull_request.head.sha }}
+
     - name: Checkout git repository 🕝
-      if: needs.changes.outputs.docker == 'true'
       uses: actions/checkout@v2
+      if: github.event_name != 'pull_request'
 
     - name: Free disk space
       if: needs.changes.outputs.docker == 'true'
@@ -312,14 +457,16 @@ jobs:
         docker rmi $(docker image ls -aq)
         df -h
 
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v1
+      with:
+        version: latest
+        driver: docker
+
     - name: Login to DockerHub Registry 🔢
       if: needs.changes.outputs.docker == 'true'
       run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ env.DOCKERHUB_USERNAME }} --password-stdin || true
 
-    - name: Pull latest${{ matrix.image.tag_ext }} Docker image for caching
-      if: needs.changes.outputs.docker == 'true'
-      run: docker pull rasa/rasa:latest${{ matrix.image.tag_ext }} || true
-
     - name: Copy Segment write key to the package
       if: needs.changes.outputs.docker == 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && github.repository == 'RasaHQ/rasa'
       env:
@@ -328,22 +475,38 @@ jobs:
       run: |
         ./scripts/write_keys_file.sh
 
-    - name: Build latest${{ matrix.image.tag_ext }} Docker image
+    - name: Build Docker image
       if: needs.changes.outputs.docker == 'true'
-      run: docker build . --file ${{ matrix.image.file }} --tag rasa/rasa:latest${{ matrix.image.tag_ext }} --cache-from rasa/rasa:latest${{ matrix.image.tag_ext }}
+      run: |
+        docker buildx bake -f docker/docker-bake.hcl ${{ matrix.image }}
 
-    - name: Push image with latest tag 📦
+    - name: Push image with master tag 📦
       if: needs.changes.outputs.docker == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'RasaHQ/rasa'
-      run: docker push rasa/rasa:latest${{ matrix.image.tag_ext }}
+      run: |
+        docker buildx bake -f docker/docker-bake.hcl ${{ matrix.image }} --push
 
     - name: Push image with ${{github.ref}} tag 📦
-      env:
-        GITHUB_TAG: ${{ github.ref }}
-      if: needs.changes.outputs.docker == 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && github.repository == 'RasaHQ/rasa'
+      if: needs.changes.outputs.docker == 'true' && github.event_name == 'push' && env.IS_TAG_BUILD == 'true' && github.repository == 'RasaHQ/rasa'
       run: |
-        GITHUB_TAG=${GITHUB_TAG/refs\/tags\//}
-        docker tag rasa/rasa:latest${{ matrix.image.tag_ext }} rasa/rasa:${GITHUB_TAG}${{ matrix.image.tag_ext }}
-        docker push rasa/rasa:${GITHUB_TAG}${{ matrix.image.tag_ext }}
+        IS_NEWEST_VERSION=${{ needs.build_docker_base_images_and_set_env.outputs.is_newest_version }}
+
+        docker buildx bake -f docker/docker-bake.hcl ${{ matrix.image }} --push
+
+        # Tag the image as latest
+        if [[ "${IS_NEWEST_VERSION}" == "true" ]]; then
+          if [[ "${{ matrix.image }}" == "default" ]]; then
+            RELEASE_TAG="${IMAGE_TAG}"
+          else
+            RELEASE_TAG="${IMAGE_TAG}-${{ matrix.image }}"
+          fi
+
+          LATEST_TAG=$(echo $RELEASE_TAG | sed 's/'$GITHUB_TAG'/latest/g')
+
+          docker tag rasa/rasa:${RELEASE_TAG} rasa/rasa:${LATEST_TAG}
+          docker push rasa/rasa:${LATEST_TAG}
+        fi
+
+
 
   deploy:
     name: Deploy to PyPI
diff --git a/Dockerfile b/Dockerfile
index dd4ba31f1e82..4076bb0d8307 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,34 +1,8 @@
-FROM python:3.7-slim as base
+# The default Docker image
+ARG IMAGE_BASE_NAME
+ARG BASE_IMAGE_HASH
 
-RUN apt-get update -qq \
- && apt-get install -y --no-install-recommends \
-    # required by psycopg2 at build and runtime
-    libpq-dev \
-     # required for health check
-    curl \
- && apt-get autoremove -y
-
-FROM base as builder
-
-RUN apt-get update -qq && \
-  apt-get install -y --no-install-recommends \
-  build-essential \
-  wget \
-  openssh-client \
-  graphviz-dev \
-  pkg-config \
-  git-core \
-  openssl \
-  libssl-dev \
-  libffi6 \
-  libffi-dev \
-  libpng-dev
-
-# install poetry
-# keep this in sync with the version in pyproject.toml and Dockerfile
-ENV POETRY_VERSION 1.1.4
-RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
-ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as builder
 
 # copy files
 COPY . /build/
@@ -46,7 +20,7 @@ RUN python -m venv /opt/venv && \
   rm -rf dist *.egg-info
 
 # start a new build stage
-FROM base as runner
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as runner
 
 # copy everything from /opt
 COPY --from=builder /opt/venv /opt/venv
diff --git a/Makefile b/Makefile
index ee3cda52f057..901253cbe50b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: clean test lint init docs
+.PHONY: clean test lint init docs build-docker build-docker-full build-docker-mitie-en build-docker-spacy-en build-docker-spacy-de
 
 JOBS ?= 1
 
@@ -178,3 +178,33 @@ livedocs:
 
 release:
 	poetry run python scripts/release.py
+
+build-docker:
+	export IMAGE_NAME=rasa && \
+	docker buildx use default && \
+	docker buildx bake -f docker/docker-bake.hcl base && \
+	docker buildx bake -f docker/docker-bake.hcl default
+
+build-docker-full:
+	export IMAGE_NAME=rasa && \
+	docker buildx use default && \
+	docker buildx bake -f docker/docker-bake.hcl base-images && \
+	docker buildx bake -f docker/docker-bake.hcl full
+
+build-docker-mitie-en:
+	export IMAGE_NAME=rasa && \
+	docker buildx use default && \
+	docker buildx bake -f docker/docker-bake.hcl base-images && \
+	docker buildx bake -f docker/docker-bake.hcl mitie-en
+
+build-docker-spacy-en:
+	export IMAGE_NAME=rasa && \
+	docker buildx use default && \
+	docker buildx bake -f docker/docker-bake.hcl base && \
+	docker buildx bake -f docker/docker-bake.hcl spacy-en
+
+build-docker-spacy-de:
+	export IMAGE_NAME=rasa && \
+	docker buildx use default && \
+	docker buildx bake -f docker/docker-bake.hcl base && \
+	docker buildx bake -f docker/docker-bake.hcl spacy-de
diff --git a/README.md b/README.md
index ee80404ec9b6..5a80ea7d988c 100644
--- a/README.md
+++ b/README.md
@@ -28,9 +28,9 @@ or voice assistants as:
 - Alexa Skills
 - Google Home Actions
 
-Rasa helps you build contextual assistants capable of having layered conversations with 
-lots of back-and-forth. In order for a human to have a meaningful exchange with a contextual 
-assistant, the assistant needs to be able to use context to build on things that were previously 
+Rasa helps you build contextual assistants capable of having layered conversations with
+lots of back-and-forth. In order for a human to have a meaningful exchange with a contextual
+assistant, the assistant needs to be able to use context to build on things that were previously
 discussed – Rasa enables you to build assistants that can do this in a scalable way.
 
 There's a lot more background information in this
@@ -58,7 +58,7 @@ There's a lot more background information in this
 - **I would like to contribute 🤗**
   [How to Contribute](#how-to-contribute)
 
----  
+---
 ## Where to get help
 
 There is extensive documentation in the [Rasa Docs](https://rasa.com/docs/rasa).
@@ -74,7 +74,7 @@ questions.
 - [License](#license)
 
 ### How to contribute
-We are very happy to receive and merge your contributions into this repository! 
+We are very happy to receive and merge your contributions into this repository!
 
 To contribute via pull request, follow these steps:
 
@@ -105,13 +105,13 @@ you have to install Poetry first. This is how it can be done:
 curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
 ```
 
-There are several other ways to install Poetry. Please, follow 
+There are several other ways to install Poetry. Please, follow
 [the official guide](https://python-poetry.org/docs/#installation) to see all possible options.
 
 ### Managing environments
 
 The official [Poetry guide](https://python-poetry.org/docs/managing-environments/) suggests to use
-[pyenv](https://github.com/pyenv/pyenv) or any other similar tool to easily switch between Python versions. 
+[pyenv](https://github.com/pyenv/pyenv) or any other similar tool to easily switch between Python versions.
 This is how it can be done:
 
 ```bash
@@ -119,7 +119,7 @@ pyenv install 3.7.6
 pyenv local 3.7.6  # Activate Python 3.7.6 for the current project
 ```
 
-By default, Poetry will try to use the currently activated Python version to create the virtual environment 
+By default, Poetry will try to use the currently activated Python version to create the virtual environment
 for the current project automatically. You can also create and activate a virtual environment manually — in this
 case, Poetry should pick it up and use it to install the dependencies. For example:
 
@@ -128,7 +128,7 @@ python -m venv .venv
 source .venv/bin/activate
 ```
 
-You can make sure that the environment is picked up by executing 
+You can make sure that the environment is picked up by executing
 
 ```bash
 poetry env info
@@ -202,10 +202,20 @@ Just execute this command to resolve merge conflicts in `poetry.lock` automatica
 poetry-merge-lock
 ```
 
+### Build a Docker image locally
+
+In order to build a Docker image on your local machine execute the following command:
+
+```bash
+make build-docker
+```
+
+Docker image is available on your local machine as `rasa:localdev`.
+
 ### Code Style
 
 To ensure a standardized code style we use the formatter [black](https://github.com/ambv/black).
-To ensure our type annotations are correct we use the type checker [pytype](https://github.com/google/pytype). 
+To ensure our type annotations are correct we use the type checker [pytype](https://github.com/google/pytype).
 If your code is not formatted properly or doesn't type check, GitHub will fail to build.
 
 #### Formatting
@@ -238,11 +248,11 @@ the `documentation` branch. Netlify automatically re-deploys the docs pages when
 
 ## Releases
 ### Release Timeline for Minor Releases
-**For Rasa Open Source, we usually commit to time-based releases, specifically on a monthly basis.** 
-This means that we commit beforehand to releasing a specific version of Rasa Open Source on a specific day, 
-and we cannot be 100% sure what will go in a release, because certain features may not be ready. 
+**For Rasa Open Source, we usually commit to time-based releases, specifically on a monthly basis.**
+This means that we commit beforehand to releasing a specific version of Rasa Open Source on a specific day,
+and we cannot be 100% sure what will go in a release, because certain features may not be ready.
 
-At the beginning of each quarter, the Rasa team will review the scheduled release dates for all products and make sure 
+At the beginning of each quarter, the Rasa team will review the scheduled release dates for all products and make sure
 they work for the projected work we have planned for the quarter, as well as work well across products.
 
 **Once the dates are settled upon, we update the respective [milestones](https://github.com/RasaHQ/rasa/milestones).**
@@ -250,12 +260,12 @@ they work for the projected work we have planned for the quarter, as well as wor
 ### Cutting a Major / Minor release
 #### A week before release day
 
-1. **Make sure the [milestone](https://github.com/RasaHQ/rasa/milestones) already exists and is scheduled for the 
+1. **Make sure the [milestone](https://github.com/RasaHQ/rasa/milestones) already exists and is scheduled for the
 correct date.**
-2. **Take a look at the issues & PRs that are in the milestone**: does it look about right for the release highlights 
-we are planning to ship? Does it look like anything is missing? Don't worry about being aware of every PR that should 
+2. **Take a look at the issues & PRs that are in the milestone**: does it look about right for the release highlights
+we are planning to ship? Does it look like anything is missing? Don't worry about being aware of every PR that should
 be in, but it's useful to take a moment to evaluate what's assigned to the milestone.
-3. **Post a message on the engineering Slack channel**, letting the team know you'll be the one cutting the upcoming 
+3. **Post a message on the engineering Slack channel**, letting the team know you'll be the one cutting the upcoming
 release, as well as:
     1. Providing the link to the appropriate milestone
     2. Reminding everyone to go over their issues and PRs and please assign them to the milestone
@@ -263,20 +273,20 @@ release, as well as:
 
 #### A day before release day
 
-1. **Go over the milestone and evaluate the status of any PR merging that's happening. Follow up with people on their 
-bugs and fixes.** If the release introduces new bugs or regressions that can't be fixed in time, we should discuss on 
-Slack about this and take a decision to go forward or postpone the release. The PR / issue owners are responsible for 
+1. **Go over the milestone and evaluate the status of any PR merging that's happening. Follow up with people on their
+bugs and fixes.** If the release introduces new bugs or regressions that can't be fixed in time, we should discuss on
+Slack about this and take a decision to go forward or postpone the release. The PR / issue owners are responsible for
 communicating any issues which might be release relevant.
 
 #### Release day! 🚀
 
-1. **At the start of the day, post a small message on slack announcing release day!**. Communicate you'll be handling 
-the release, and the time you're aiming to start releasing (again, no later than 4pm, as issues may arise and 
+1. **At the start of the day, post a small message on slack announcing release day!**. Communicate you'll be handling
+the release, and the time you're aiming to start releasing (again, no later than 4pm, as issues may arise and
 cause delays)
 2. Make sure the milestone is empty (everything has been either merged or moved to the next milestone)
-3. Once everything in the milestone is taken care of, post a small message on Slack communicating you are about to 
+3. Once everything in the milestone is taken care of, post a small message on Slack communicating you are about to
 start the release process (in case anything is missing).
-4. **You may now do the release by following the instructions outlined in the 
+4. **You may now do the release by following the instructions outlined in the
 [Rasa Open Source README](#steps-to-release-a-new-version) !**
 
 ### Steps to release a new version
@@ -291,7 +301,7 @@ Releasing a new version is quite simple, as the packages are build and distribut
 1. Make sure all dependencies are up to date (**especially Rasa SDK**)
     - For Rasa SDK that means first creating a [new Rasa SDK release](https://github.com/RasaHQ/rasa-sdk#steps-to-release-a-new-version) (make sure the version numbers between the new Rasa and Rasa SDK releases match)
     - Once the tag with the new Rasa SDK release is pushed and the package appears on [pypi](https://pypi.org/project/rasa-sdk/), the dependency in the rasa repository can be resolved (see below).
-2. Switch to the branch you want to cut the release from (`master` in case of a major / minor, the current feature branch for micro releases) 
+2. Switch to the branch you want to cut the release from (`master` in case of a major / minor, the current feature branch for micro releases)
     - Update the `rasa-sdk` entry in `pyproject.toml` with the new release version and run `poetry update`. This creates a new `poetry.lock` file with all dependencies resolved.
     - Commit the changes with `git commit -am "bump rasa-sdk dependency"` but do not push them. They will be automatically picked up by the following step.
 3. Run `make release`
@@ -314,11 +324,11 @@ Micro releases are simpler to cut, since they are meant to contain only bugfixes
 
 **The only things you need to do to cut a micro are:**
 
-1. Notify the engineering team on Slack that you are planning to cut a micro, in case someone has an important fix 
+1. Notify the engineering team on Slack that you are planning to cut a micro, in case someone has an important fix
 to add.
-2. Make sure the bugfix(es) are in the release branch you will use (p.e if you are cutting a `2.0.4` micro, you will 
+2. Make sure the bugfix(es) are in the release branch you will use (p.e if you are cutting a `2.0.4` micro, you will
 need your fixes to be on the `2.0.x` release branch). All micros must come from a `.x` branch!
-3. Once you're ready to release the Rasa Open Source micro, checkout the branch, run `make release` and follow the 
+3. Once you're ready to release the Rasa Open Source micro, checkout the branch, run `make release` and follow the
 steps + get the PR merged.
 4. Once the PR is in, pull the `.x` branch again and push the tag!
 
diff --git a/changelog/7574.misc.md b/changelog/7574.misc.md
new file mode 100644
index 000000000000..f130223d2bda
--- /dev/null
+++ b/changelog/7574.misc.md
@@ -0,0 +1,5 @@
+Refactor of Docker builds that includes:
+
+- switching to Python 3.8 and Ubuntu 20.04 as a base image
+- tagging the latest stable version as `latest`
+- tagging Docker images build against the master branch as `master`
diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base
new file mode 100644
index 000000000000..d4d6c772d173
--- /dev/null
+++ b/docker/Dockerfile.base
@@ -0,0 +1,39 @@
+# The base image used for all images
+FROM ubuntu:20.04
+
+ENV DEBIAN_FRONTEND="noninteractive"
+
+RUN apt-get update -qq && \
+  apt-get install -y --no-install-recommends \
+  python3 \
+  python3-venv \
+  python3-pip \
+  python3-dev \
+  build-essential \
+  wget \
+  openssh-client \
+  graphviz-dev \
+  pkg-config \
+  git-core \
+  openssl \
+  libssl-dev \
+  libffi7 \
+  libffi-dev \
+  libpng-dev \
+  # required by psycopg2 at build and runtime
+  libpq-dev \
+  # required for health check
+  curl \
+  && apt-get autoremove -y
+
+# Make sure that all security updates are installed
+RUN apt-get update && apt-get dist-upgrade -y --no-install-recommends
+
+RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 100 \
+   && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 100
+
+# install poetry
+# keep this in sync with the version in pyproject.toml and Dockerfile
+ENV POETRY_VERSION 1.1.4
+RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
+ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
diff --git a/docker/Dockerfile.base-mitie b/docker/Dockerfile.base-mitie
new file mode 100644
index 000000000000..2b8b4a34c38a
--- /dev/null
+++ b/docker/Dockerfile.base-mitie
@@ -0,0 +1,7 @@
+# The base image used for all images that require a MITIE model
+FROM alpine:latest
+
+# download mitie model
+RUN mkdir -p /build/data/ && wget -P /build/data/ https://github.com/mit-nlp/MITIE/releases/download/v0.4/MITIE-models-v0.2.tar.bz2 && \
+  tar -xvjf /build/data/MITIE-models-v0.2.tar.bz2 --strip-components 2 -C /build/data/ MITIE-models/english/total_word_feature_extractor.dat && \
+  rm /build/data/MITIE*.bz2
diff --git a/docker/Dockerfile.full b/docker/Dockerfile.full
new file mode 100644
index 000000000000..7fc0401c4f0a
--- /dev/null
+++ b/docker/Dockerfile.full
@@ -0,0 +1,61 @@
+# The image tagged with the 'full' suffix
+ARG IMAGE_BASE_NAME
+ARG BASE_IMAGE_HASH
+ARG BASE_MITIE_IMAGE_HASH
+
+FROM ${IMAGE_BASE_NAME}:base-mitie-${BASE_MITIE_IMAGE_HASH} as mitie
+
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as builder
+
+COPY --from=mitie /build/data /build/data
+
+# copy files
+COPY . /build/
+COPY docker/configs/config_pretrained_embeddings_spacy_en_duckling.yml /build/config.yml
+
+# change working directory
+WORKDIR /build
+
+# install dependencies
+RUN python -m venv /opt/venv && \
+  . /opt/venv/bin/activate && \
+  pip install --no-cache-dir -U 'pip<20'
+RUN . /opt/venv/bin/activate && poetry install --extras full --no-dev --no-root --no-interaction
+RUN . /opt/venv/bin/activate && make install-mitie && \
+  poetry build -f wheel -n && \
+  pip install --no-deps dist/*.whl && \
+  rm -rf dist *.egg-info
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# spacy link
+RUN python -m spacy download en_core_web_md && \
+    python -m spacy download de_core_news_sm && \
+    python -m spacy link en_core_web_md en && \
+    python -m spacy link de_core_news_sm de
+
+# start a new build stage
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as runner
+
+# copy everything from /opt
+COPY --from=builder /opt/venv /opt/venv
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# update permissions & change user to not run as root
+WORKDIR /app
+RUN chgrp -R 0 /app && chmod -R g=u /app
+USER 1001
+
+# Create a volume for temporary data
+VOLUME /tmp
+
+# change shell
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+
+# the entry point
+EXPOSE 5005
+ENTRYPOINT ["rasa"]
+CMD ["--help"]
diff --git a/docker/Dockerfile.pretrained_embeddings_mitie_en b/docker/Dockerfile.pretrained_embeddings_mitie_en
new file mode 100644
index 000000000000..a64da0be786b
--- /dev/null
+++ b/docker/Dockerfile.pretrained_embeddings_mitie_en
@@ -0,0 +1,51 @@
+# The image tagged with the 'mitie-en' suffix
+ARG IMAGE_BASE_NAME
+ARG BASE_IMAGE_HASH
+ARG BASE_MITIE_IMAGE_HASH
+
+FROM ${IMAGE_BASE_NAME}:base-mitie-${BASE_MITIE_IMAGE_HASH} as mitie
+
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as builder
+
+COPY --from=mitie /build/data /build/data
+
+# copy files
+COPY . /build/
+COPY docker/configs/config_pretrained_embeddings_mitie.yml /build/config.yml
+
+# change working directory
+WORKDIR /build
+
+# install dependencies
+RUN python -m venv /opt/venv && \
+  . /opt/venv/bin/activate && pip install --no-cache-dir -U 'pip<20'
+RUN . /opt/venv/bin/activate && poetry install --no-dev --no-root --no-interaction
+RUN . /opt/venv/bin/activate && make install-mitie  && \
+  poetry build -f wheel -n && \
+  pip install --no-deps dist/*.whl && \
+  rm -rf dist *.egg-info
+
+# start a new build stage
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as runner
+
+# copy everything from /opt
+COPY --from=builder /opt/venv /opt/venv
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# update permissions & change user to not run as root
+WORKDIR /app
+RUN chgrp -R 0 /app && chmod -R g=u /app
+USER 1001
+
+# create a volume for temporary data
+VOLUME /tmp
+
+# change shell
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+
+# the entry point
+EXPOSE 5005
+ENTRYPOINT ["rasa"]
+CMD ["--help"]
diff --git a/docker/Dockerfile.pretrained_embeddings_spacy_de b/docker/Dockerfile.pretrained_embeddings_spacy_de
new file mode 100644
index 000000000000..9177ea19494d
--- /dev/null
+++ b/docker/Dockerfile.pretrained_embeddings_spacy_de
@@ -0,0 +1,52 @@
+# The image tagged with the 'spacy-de' suffix
+ARG IMAGE_BASE_NAME
+ARG BASE_IMAGE_HASH
+
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as builder
+
+# copy files
+COPY . /build/
+COPY docker/configs/config_pretrained_embeddings_spacy_de.yml /build/config.yml
+
+# change working directory
+WORKDIR /build
+
+# install dependencies
+RUN python -m venv /opt/venv && \
+  . /opt/venv/bin/activate && pip install --no-cache-dir -U 'pip<20'
+RUN . /opt/venv/bin/activate && poetry install --extras spacy --no-dev --no-root --no-interaction
+RUN . /opt/venv/bin/activate && poetry build -f wheel -n && \
+  pip install --no-deps dist/*.whl && \
+  rm -rf dist *.egg-info
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# spacy link
+RUN python -m spacy download de_core_news_sm && \
+    python -m spacy link de_core_news_sm de
+
+# start a new build stage
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as runner
+
+# copy everything from /opt
+COPY --from=builder /opt/venv /opt/venv
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# update permissions & change user to not run as root
+WORKDIR /app
+RUN chgrp -R 0 /app && chmod -R g=u /app
+USER 1001
+
+# Create a volume for temporary data
+VOLUME /tmp
+
+# change shell
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+
+# the entry point
+EXPOSE 5005
+ENTRYPOINT ["rasa"]
+CMD ["--help"]
diff --git a/docker/Dockerfile.pretrained_embeddings_spacy_en b/docker/Dockerfile.pretrained_embeddings_spacy_en
new file mode 100644
index 000000000000..5766f3d9c31c
--- /dev/null
+++ b/docker/Dockerfile.pretrained_embeddings_spacy_en
@@ -0,0 +1,52 @@
+# The image tagged with the 'spacy-en' suffix
+ARG IMAGE_BASE_NAME
+ARG BASE_IMAGE_HASH
+
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as builder
+
+# copy files
+COPY . /build/
+COPY docker/configs/config_pretrained_embeddings_spacy_en.yml /build/config.yml
+
+# change working directory
+WORKDIR /build
+
+# install dependencies
+RUN python -m venv /opt/venv && \
+  . /opt/venv/bin/activate && pip install --no-cache-dir -U 'pip<20'
+RUN . /opt/venv/bin/activate && poetry install --extras spacy --no-dev --no-root --no-interaction
+RUN . /opt/venv/bin/activate && poetry build -f wheel -n && \
+  pip install --no-deps dist/*.whl && \
+  rm -rf dist *.egg-info
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# spacy link
+RUN python -m spacy download en_core_web_md && \
+    python -m spacy link en_core_web_md en
+
+# start a new build stage
+FROM ${IMAGE_BASE_NAME}:base-${BASE_IMAGE_HASH} as runner
+
+# copy everything from /opt
+COPY --from=builder /opt/venv /opt/venv
+
+# make sure we use the virtualenv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# update permissions & change user to not run as root
+WORKDIR /app
+RUN chgrp -R 0 /app && chmod -R g=u /app
+USER 1001
+
+# Create a volume for temporary data
+VOLUME /tmp
+
+# change shell
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+
+# the entry point
+EXPOSE 5005
+ENTRYPOINT ["rasa"]
+CMD ["--help"]
diff --git a/docker/Dockerfile_full b/docker/Dockerfile_full
deleted file mode 100644
index 24bc5edc5c16..000000000000
--- a/docker/Dockerfile_full
+++ /dev/null
@@ -1,87 +0,0 @@
-FROM python:3.7-slim as base
-
-RUN apt-get update -qq \
- && apt-get install -y --no-install-recommends \
-    # required by psycopg2 at build and runtime
-    libpq-dev \
-     # required for health check
-    curl \
- && apt-get autoremove -y
-
-FROM base as builder
-
-RUN apt-get update -qq && \
-  apt-get install -y --no-install-recommends \
-  build-essential \
-  wget \
-  openssh-client \
-  graphviz-dev \
-  pkg-config \
-  git-core \
-  openssl \
-  libssl-dev \
-  libffi6 \
-  libffi-dev \
-  libpng-dev
-
-# install poetry
-# keep this in sync with the version in pyproject.toml and Dockerfile
-ENV POETRY_VERSION 1.1.4
-RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
-ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
-
-# copy files
-COPY . /build/
-COPY docker/configs/config_pretrained_embeddings_spacy_en_duckling.yml /build/config.yml
-
-# download mitie model
-RUN wget -P /build/data/ https://github.com/mit-nlp/MITIE/releases/download/v0.4/MITIE-models-v0.2.tar.bz2 && \
-  tar -xvjf /build/data/MITIE-models-v0.2.tar.bz2 --strip-components 2 -C /build/data/ MITIE-models/english/total_word_feature_extractor.dat && \
-  rm /build/data/MITIE*.bz2
-
-# change working directory
-WORKDIR /build
-
-# install dependencies
-RUN python -m venv /opt/venv && \
-  . /opt/venv/bin/activate && \
-  pip install --no-cache-dir -U 'pip<20' && \
-  poetry install --extras full --no-dev --no-root --no-interaction && \
-  make install-mitie && \
-  poetry build -f wheel -n && \
-  pip install --no-deps dist/*.whl && \
-  rm -rf dist *.egg-info
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# spacy link
-RUN python -m spacy download en_core_web_md && \
-    python -m spacy download de_core_news_sm && \
-    python -m spacy link en_core_web_md en && \
-    python -m spacy link de_core_news_sm de
-
-# start a new build stage
-FROM base as runner
-
-# copy everything from /opt
-COPY --from=builder /opt/venv /opt/venv
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# update permissions & change user to not run as root
-WORKDIR /app
-RUN chgrp -R 0 /app && chmod -R g=u /app
-USER 1001
-
-# Create a volume for temporary data
-VOLUME /tmp
-
-# change shell
-SHELL ["/bin/bash", "-o", "pipefail", "-c"]
-
-# the entry point
-EXPOSE 5005
-ENTRYPOINT ["rasa"]
-CMD ["--help"]
diff --git a/docker/Dockerfile_pretrained_embeddings_mitie_en b/docker/Dockerfile_pretrained_embeddings_mitie_en
deleted file mode 100644
index 4dca77fd5826..000000000000
--- a/docker/Dockerfile_pretrained_embeddings_mitie_en
+++ /dev/null
@@ -1,78 +0,0 @@
-FROM python:3.7-slim as base
-
-RUN apt-get update -qq \
- && apt-get install -y --no-install-recommends \
-    # required by psycopg2 at build and runtime
-    libpq-dev \
-     # required for health check
-    curl \
- && apt-get autoremove -y
-
-FROM base as builder
-
-RUN apt-get update -qq && \
-  apt-get install -y --no-install-recommends \
-  build-essential \
-  wget \
-  openssh-client \
-  graphviz-dev \
-  pkg-config \
-  git-core \
-  openssl \
-  libssl-dev \
-  libffi6 \
-  libffi-dev \
-  libpng-dev
-
-# install poetry
-# keep this in sync with the version in pyproject.toml and Dockerfile
-ENV POETRY_VERSION 1.1.4
-RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
-ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
-
-# copy files
-COPY . /build/
-COPY docker/configs/config_pretrained_embeddings_mitie.yml /build/config.yml
-
-# download mitie model
-RUN wget -P /build/data/ https://github.com/mit-nlp/MITIE/releases/download/v0.4/MITIE-models-v0.2.tar.bz2 && \
-  tar -xvjf /build/data/MITIE-models-v0.2.tar.bz2 --strip-components 2 -C /build/data/ MITIE-models/english/total_word_feature_extractor.dat && \
-  rm /build/data/MITIE*.bz2
-
-# change working directory
-WORKDIR /build
-
-# install dependencies
-RUN python -m venv /opt/venv && \
-  . /opt/venv/bin/activate && \
-  pip install --no-cache-dir -U 'pip<20' && \
-  poetry install --no-dev --no-root --no-interaction && \
-  make install-mitie  && \
-  poetry build -f wheel -n && \
-  pip install --no-deps dist/*.whl && \
-  rm -rf dist *.egg-info
-
-# start a new build stage
-FROM base as runner
-
-# copy everything from /opt
-COPY --from=builder /opt/venv /opt/venv
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# update permissions & change user to not run as root
-WORKDIR /app
-RUN chgrp -R 0 /app && chmod -R g=u /app
-USER 1001
-
-# create a volume for temporary data
-VOLUME /tmp
-
-# change shell
-SHELL ["/bin/bash", "-o", "pipefail", "-c"]
-
-# the entry point
-EXPOSE 5005
-ENTRYPOINT ["rasa"]
-CMD ["--help"]
diff --git a/docker/Dockerfile_pretrained_embeddings_spacy_de b/docker/Dockerfile_pretrained_embeddings_spacy_de
deleted file mode 100644
index f5dec022d390..000000000000
--- a/docker/Dockerfile_pretrained_embeddings_spacy_de
+++ /dev/null
@@ -1,79 +0,0 @@
-FROM python:3.7-slim as base
-
-RUN apt-get update -qq \
- && apt-get install -y --no-install-recommends \
-    # required by psycopg2 at build and runtime
-    libpq-dev \
-     # required for health check
-    curl \
- && apt-get autoremove -y
-
-FROM base as builder
-
-RUN apt-get update -qq && \
-  apt-get install -y --no-install-recommends \
-  build-essential \
-  wget \
-  openssh-client \
-  graphviz-dev \
-  pkg-config \
-  git-core \
-  openssl \
-  libssl-dev \
-  libffi6 \
-  libffi-dev \
-  libpng-dev
-
-# install poetry
-# keep this in sync with the version in pyproject.toml and Dockerfile
-ENV POETRY_VERSION 1.1.4
-RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
-ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
-
-# copy files
-COPY . /build/
-COPY docker/configs/config_pretrained_embeddings_spacy_de.yml /build/config.yml
-
-# change working directory
-WORKDIR /build
-
-# install dependencies
-RUN python -m venv /opt/venv && \
-  . /opt/venv/bin/activate && \
-  pip install --no-cache-dir -U 'pip<20' && \
-  poetry install --extras spacy --no-dev --no-root --no-interaction && \
-  poetry build -f wheel -n && \
-  pip install --no-deps dist/*.whl && \
-  rm -rf dist *.egg-info
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# spacy link
-RUN python -m spacy download de_core_news_sm && \
-    python -m spacy link de_core_news_sm de
-
-# start a new build stage
-FROM base as runner
-
-# copy everything from /opt
-COPY --from=builder /opt/venv /opt/venv
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# update permissions & change user to not run as root
-WORKDIR /app
-RUN chgrp -R 0 /app && chmod -R g=u /app
-USER 1001
-
-# Create a volume for temporary data
-VOLUME /tmp
-
-# change shell
-SHELL ["/bin/bash", "-o", "pipefail", "-c"]
-
-# the entry point
-EXPOSE 5005
-ENTRYPOINT ["rasa"]
-CMD ["--help"]
diff --git a/docker/Dockerfile_pretrained_embeddings_spacy_en b/docker/Dockerfile_pretrained_embeddings_spacy_en
deleted file mode 100644
index 870e89b48718..000000000000
--- a/docker/Dockerfile_pretrained_embeddings_spacy_en
+++ /dev/null
@@ -1,79 +0,0 @@
-FROM python:3.7-slim as base
-
-RUN apt-get update -qq \
- && apt-get install -y --no-install-recommends \
-    # required by psycopg2 at build and runtime
-    libpq-dev \
-     # required for health check
-    curl \
- && apt-get autoremove -y
-
-FROM base as builder
-
-RUN apt-get update -qq && \
-  apt-get install -y --no-install-recommends \
-  build-essential \
-  wget \
-  openssh-client \
-  graphviz-dev \
-  pkg-config \
-  git-core \
-  openssl \
-  libssl-dev \
-  libffi6 \
-  libffi-dev \
-  libpng-dev
-
-# install poetry
-# keep this in sync with the version in pyproject.toml and Dockerfile
-ENV POETRY_VERSION 1.1.4
-RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
-ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
-
-# copy files
-COPY . /build/
-COPY docker/configs/config_pretrained_embeddings_spacy_en.yml /build/config.yml
-
-# change working directory
-WORKDIR /build
-
-# install dependencies
-RUN python -m venv /opt/venv && \
-  . /opt/venv/bin/activate && \
-  pip install --no-cache-dir -U 'pip<20' && \
-  poetry install --extras spacy --no-dev --no-root --no-interaction && \
-  poetry build -f wheel -n && \
-  pip install --no-deps dist/*.whl && \
-  rm -rf dist *.egg-info
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# spacy link
-RUN python -m spacy download en_core_web_md && \
-    python -m spacy link en_core_web_md en
-
-# start a new build stage
-FROM base as runner
-
-# copy everything from /opt
-COPY --from=builder /opt/venv /opt/venv
-
-# make sure we use the virtualenv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# update permissions & change user to not run as root
-WORKDIR /app
-RUN chgrp -R 0 /app && chmod -R g=u /app
-USER 1001
-
-# Create a volume for temporary data
-VOLUME /tmp
-
-# change shell
-SHELL ["/bin/bash", "-o", "pipefail", "-c"]
-
-# the entry point
-EXPOSE 5005
-ENTRYPOINT ["rasa"]
-CMD ["--help"]
diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl
new file mode 100644
index 000000000000..f2d8a232738b
--- /dev/null
+++ b/docker/docker-bake.hcl
@@ -0,0 +1,119 @@
+variable "IMAGE_NAME" {
+  default = "rasa/rasa"
+}
+
+variable "IMAGE_TAG" {
+  default = "localdev"
+}
+
+variable "BASE_IMAGE_HASH" {
+  default = "localdev"
+}
+
+variable "BASE_MITIE_IMAGE_HASH" {
+  default = "localdev"
+}
+
+group "base-images" {
+  targets = ["base", "base-mitie"]
+}
+
+target "base" {
+  dockerfile = "docker/Dockerfile.base"
+  tags       = ["${IMAGE_NAME}:base-${IMAGE_TAG}"]
+  cache-to   = ["type=inline"]
+}
+
+target "base-mitie" {
+  dockerfile = "docker/Dockerfile.base-mitie"
+  tags       = ["${IMAGE_NAME}:base-mitie-${IMAGE_TAG}"]
+  cache-to   = ["type=inline"]
+}
+
+target "default" {
+  dockerfile = "Dockerfile"
+  tags       = ["${IMAGE_NAME}:${IMAGE_TAG}"]
+
+  args = {
+    IMAGE_BASE_NAME = "${IMAGE_NAME}"
+    BASE_IMAGE_HASH = "${BASE_IMAGE_HASH}"
+  }
+
+  cache-to = ["type=inline"]
+
+  cache-from = [
+    "type=registry,ref=${IMAGE_NAME}:base-${BASE_IMAGE_HASH}",
+    "type=registry,ref=${IMAGE_NAME}:latest",
+  ]
+}
+
+target "full" {
+  dockerfile = "docker/Dockerfile.full"
+  tags       = ["${IMAGE_NAME}:${IMAGE_TAG}-full"]
+
+  args = {
+    IMAGE_BASE_NAME       = "${IMAGE_NAME}"
+    BASE_IMAGE_HASH       = "${BASE_IMAGE_HASH}"
+    BASE_MITIE_IMAGE_HASH = "${BASE_MITIE_IMAGE_HASH}"
+  }
+
+  cache-to = ["type=inline"]
+
+  cache-from = [
+    "type=registry,ref=${IMAGE_NAME}:base-${BASE_IMAGE_HASH}",
+    "type=registry,ref=${IMAGE_NAME}:latest-full",
+  ]
+}
+
+target "mitie-en" {
+  dockerfile = "docker/Dockerfile.pretrained_embeddings_mitie_en"
+  tags       = ["${IMAGE_NAME}:${IMAGE_TAG}-mitie-en"]
+
+  args = {
+    IMAGE_BASE_NAME       = "${IMAGE_NAME}"
+    BASE_IMAGE_HASH       = "${BASE_IMAGE_HASH}"
+    BASE_MITIE_IMAGE_HASH = "${BASE_MITIE_IMAGE_HASH}"
+  }
+
+  cache-to = ["type=inline"]
+
+  cache-from = [
+    "type=registry,ref=${IMAGE_NAME}:base-${BASE_IMAGE_HASH}",
+    "type=registry,ref=${IMAGE_NAME}:base-mitie-${BASE_MITIE_IMAGE_HASH}",
+    "type=registry,ref=${IMAGE_NAME}:latest-mitie-en",
+  ]
+}
+
+target "spacy-de" {
+  dockerfile = "docker/Dockerfile.pretrained_embeddings_spacy_de"
+  tags       = ["${IMAGE_NAME}:${IMAGE_TAG}-spacy-de"]
+
+  args = {
+    IMAGE_BASE_NAME = "${IMAGE_NAME}"
+    BASE_IMAGE_HASH = "${BASE_IMAGE_HASH}"
+  }
+
+  cache-to = ["type=inline"]
+
+  cache-from = [
+    "type=registry,ref=${IMAGE_NAME}:base-${BASE_IMAGE_HASH}",
+    "type=registry,ref=${IMAGE_NAME}:latest-spacy-de",
+  ]
+}
+
+target "spacy-en" {
+  dockerfile = "docker/Dockerfile.pretrained_embeddings_spacy_en"
+  tags       = ["${IMAGE_NAME}:${IMAGE_TAG}-spacy-en"]
+
+  args = {
+    IMAGE_BASE_NAME = "${IMAGE_NAME}"
+    BASE_IMAGE_HASH = "${BASE_IMAGE_HASH}"
+  }
+
+  cache-to = ["type=inline"]
+
+  cache-from = [
+    "type=registry,ref=${IMAGE_NAME}:base-${BASE_IMAGE_HASH}",
+    "type=registry,ref=${IMAGE_NAME}:latest-spacy-en",
+  ]
+}


Code review comments:
diff_hunk: @@ -0,0 +1,7 @@
+# The base image used for all images that require a MITIE model
+FROM alpine:latest
+
+# download mitie model
+RUN mkdir -p /build/data/ && wget -P /build/data/ https://github.com/mit-nlp/MITIE/releases/download/v0.4/MITIE-models-v0.2.tar.bz2 && \
path: docker/Dockerfile.base-mitie
text: @user1:
it sounds like a duplication of what's in the makefile. Is there a way to not duplicate it? (especially important if we want to keep this version in sync with the one from the makefile)

@author:
Ok, here I re-use the Makefile :)

diff_hunk: @@ -279,27 +281,170 @@ jobs:
         COVERALLS_SERVICE_NAME: github
       run: poetry run coveralls
 
+  build_docker_base_images_and_set_env:
+    name: Build Docker base images and setup environment
+    runs-on: ubuntu-20.04
+    outputs:
+      base_image_hash: ${{ steps.check_image.outputs.base_image_hash }}
+      base_mitie_image_hash: ${{ steps.check_image.outputs.base_mitie_image_hash }}
+      # Tag name used for images created during Docker image builds, e.g. 3886 - a PR number
+      image_tag: ${{ steps.set_output.outputs.image_tag }}
+      # Return 'true' if tag version is equal or higher than the latest tagged Rasa version
+      is_newest_version: ${{ steps.rasa_get_version.outputs.is_newest_version }}
+    steps:
+      # Due to an issue with checking out a wrong commit, we make sure
+      # to checkout HEAD commit for a pull request.
+      # More details: https://github.com/actions/checkout/issues/299
+    - name: Checkout pull request HEAD commit instead of merge commit 🕝
+      uses: actions/checkout@v2
+      if: github.event_name == 'pull_request'
+      with:
+        ref: ${{ github.event.pull_request.head.sha }}
+
+    - name: Checkout git repository 🕝
+      uses: actions/checkout@v2
+      if: github.event_name != 'pull_request'
+
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v1
+      with:
+        version: latest
+        driver: docker
+
+    - name: Login to DockerHub Registry 🔢
+      run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ env.DOCKERHUB_USERNAME }} --password-stdin || true
+
+    - name: Check if tag version is equal or higher than the latest tagged Rasa version
+      id: rasa_get_version
+      if: env.IS_TAG_BUILD == 'true'
+      run: |
+        # Get latest tagged Rasa version
+        git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
+        # Fetch branch history
+        git fetch --prune --unshallow
+        LATEST_TAGGED_NON_ALPHA_RASA_VERSION=$(git tag | sort -r -V | grep -E "^[0-9.]+$" | head -n1)
+        CURRENT_TAG=${GITHUB_TAG/refs\/tags\//}
+        # Return 'true' if tag version is equal or higher than the latest tagged Rasa version
+        IS_NEWEST_VERSION=$((printf '%s\n%s\n' "${LATEST_TAGGED_NON_ALPHA_RASA_VERSION}" "$CURRENT_TAG" \
+          | sort -V -C && echo true || echo false) || true)
+        # Avoid that the script gets released for alphas or release candidates
+        if [[ "${IS_NEWEST_VERSION}" == "true" && "$CURRENT_TAG" =~ ^[0-9.]+$ ]]; then
+          echo "::set-output name=is_newest_version::true"
+        else
+          echo "::set-output name=is_newest_version::false"
+        fi
+
+    - name: Check if a base image exists
+      id: check_image
+      run: |
+        DOCKERHUB_TAGS_URL="https://registry.hub.docker.com/v2/repositories/rasa/rasa/tags?page_size=10000"
path: .github/workflows/continous-integration.yml
text: @user1:
probably a question for later if this becomes a performance bottleneck (maybe never 🤷🏻 ) but I was wondering if we could use the [Manifest API](https://docs.docker.com/registry/spec/api/#manifest) to get the manifest of an image for a particular tag, that way you'd get a 404 if the tag doesn't exist, a 200 if it exists.

@author:
It's a good idea 👍🏻

diff_hunk: @@ -312,14 +457,16 @@ jobs:
         docker rmi $(docker image ls -aq)
         df -h
 
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v1
+      with:
+        version: latest
path: .github/workflows/continous-integration.yml
text: @user1:
wondering if this is safe, and if we should pin it to something static instead

@author:
Good point, I'll pin it to a specific version.