diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1e4f9ee04631499c131c1459b74dedba25edb4d5..319c7600eeb8f9a47fe71a557871885ac0955392 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,6 +22,7 @@ variables:
       /kaniko/executor
       --cache=false
       --skip-unused-stages
+      --target "$BUILD_TARGET"
       --context "$CI_PROJECT_DIR"
       --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
       --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-${ANSIBLE_VERSION}"
@@ -35,14 +36,30 @@ variables:
 build:latest:
   extends: .build
   variables:
-    ALPINE_VERSION: 3
+    ALPINE_VERSION: 3.21
     ANSIBLE_VERSION: 2.18
+    BUILD_TARGET: default
 
 build:python2.7:
   extends: .build
   variables:
     ALPINE_VERSION: 3.19
     ANSIBLE_VERSION: 2.16
+    BUILD_TARGET: default
+
+build:2.10:
+  extends: .build
+  variables:
+    ALPINE_VERSION: 3.14
+    ANSIBLE_VERSION: 2.10
+    BUILD_TARGET: ansible-classic
+
+build:2.9:
+  extends: .build
+  variables:
+    ALPINE_VERSION: 3.12
+    ANSIBLE_VERSION: 2.9
+    BUILD_TARGET: ansible-classic
 
 
 
@@ -54,7 +71,7 @@ build:python2.7:
     pull_policy: always
   script:
     - ansible --version
-    - ansible --version | grep -q "ansible \[core $ANSIBLE_VERSION"
+    - ansible --version | grep -Eq "ansible( \[core)? $ANSIBLE_VERSION"
 
 test:latest:
   extends: .test
@@ -68,6 +85,18 @@ test:python2.7:
   variables:
     ANSIBLE_VERSION: 2.16
 
+test:2.10:
+  extends: .test
+  needs: ['build:2.10']
+  variables:
+    ANSIBLE_VERSION: 2.10
+
+test:2.9:
+  extends: .test
+  needs: ['build:2.9']
+  variables:
+    ANSIBLE_VERSION: 2.9
+
 
 
 .release:
@@ -102,4 +131,18 @@ release:python2.7:
     - crane tag ${DOCKER_HUB_REPO}:2.16 python3.6
     - crane tag ${DOCKER_HUB_REPO}:2.16 python2.7
 
+release:2.10:
+  extends: .release
+  needs: ['test:2.10']
+  script:
+    - crane tag ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-2.10 2.10
+    - crane copy ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-2.10 ${DOCKER_HUB_REPO}:2.10
+
+release:2.9:
+  extends: .release
+  needs: ['test:2.9']
+  script:
+    - crane tag ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-2.9 2.9
+    - crane copy ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-2.9 ${DOCKER_HUB_REPO}:2.9
+
 # vi: set ts=2 sw=2 et ft=yaml:
diff --git a/Dockerfile b/Dockerfile
index fb6e25ca5d7f309c0727f4c56e7384d5e7fa03aa..4ae17304ff61137bc97660a24557f8ede1f7fe55 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,8 @@
 ARG ALPINE_VERSION=3
-FROM alpine:${ALPINE_VERSION}
+FROM alpine:${ALPINE_VERSION} AS base
 
+# Define ANSIBLE_VERSION in this stage so it is inherited by later stages
 ARG ANSIBLE_VERSION=2
-RUN apk add --update --no-cache \
-    # Install latest version of these dependencies \
-    bash openssh python3 rsync sshpass vim \
-    # Install specific version of ansible-core and latest compatible ansible \
-    ansible ansible-core~=${ANSIBLE_VERSION}
 
 # Add an entrypoint script that copies files from the /tmp/home directory into
 # the actual home directory
@@ -15,16 +11,40 @@ COPY entrypoint.sh /
 RUN chmod +x /entrypoint.sh
 ENTRYPOINT ["/entrypoint.sh"]
 
-# Set VIM as the default editor. Used by 'ansible-vault edit'
-ENV EDITOR=/usr/bin/vim
-
 # Add some useful aliases for instances where we want to log into the container
 # and debug stuff
 ENV ENV=/etc/profile
 RUN echo "alias ll='ls -alFh'" >> /etc/profile.d/aliases.sh
 
-WORKDIR /ansible
+# Set VIM as the default editor. Used by 'ansible-vault edit'
+ENV EDITOR=/usr/bin/vim
 
+WORKDIR /ansible
 CMD ["ansible", "--help"]
 
+
+
+# This stage is the base for only the legacy ansible-classic target, which
+# This target is meant for building images containing "classic" Ansible versions
+# such as 2.9 or 2.10, before ansible-core was split out as a separate package
+FROM base AS ansible-classic
+RUN apk add --update --no-cache \
+    # Install latest version of these dependencies \
+    bash openssh python3 rsync sshpass vim \
+    # Install specific version of ansible-core and latest compatible ansible \
+    ansible~=${ANSIBLE_VERSION}
+
+# Symlink python to python3
+RUN ln -s /usr/bin/python3 /usr/bin/python
+
+
+
+# The default target that should be used for all modern versions of Ansible
+FROM base AS default
+RUN apk add --update --no-cache \
+    # Install latest version of these dependencies \
+    bash openssh python3 rsync sshpass vim \
+    # Install specific version of ansible-core and latest compatible ansible \
+    ansible ansible-core~=${ANSIBLE_VERSION}
+
 # vi: set ts=4 sw=4 et ft=dockerfile:
diff --git a/README.md b/README.md
index c18498fc1aab1279691310c9a5d22a2a60c318ba..34d8fa95a54ae26ce0aba1517ea5e70de1ed8b47 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ Images are tagged with the version of `ansible-core` included in the image. Ther
 
 - `2.18`, `latest`
 - `2.16`, `python2.7`, `python3.6` EOL, but kept around for use with managed nodes that only support Python 2.7
+- `2.10`, `2.9` EOL, but kept around for playbooks that still only work with classic Ansible
 
 ## Usage