diff --git a/dev/build/Dockerfile b/dev/build/Dockerfile
index bd22e6569..e7cc510bc 100644
--- a/dev/build/Dockerfile
+++ b/dev/build/Dockerfile
@@ -17,6 +17,7 @@ RUN echo "deb http://deb.debian.org/debian bullseye-backports main" > /etc/apt/s
 COPY . .
 COPY ./dev/build/start.sh ./start.sh
 COPY ./dev/build/datatracker-start.sh ./datatracker-start.sh
+COPY ./dev/build/migration-start.sh ./migration-start.sh
 COPY ./dev/build/celery-start.sh ./celery-start.sh
 COPY ./dev/build/gunicorn.conf.py ./gunicorn.conf.py
 
@@ -27,6 +28,7 @@ RUN pip3 --disable-pip-version-check --no-cache-dir install -r requirements.txt
 
 RUN chmod +x start.sh && \
     chmod +x datatracker-start.sh && \
+    chmod +x migration-start.sh && \
     chmod +x celery-start.sh && \
     chmod +x docker/scripts/app-create-dirs.sh && \
     sh ./docker/scripts/app-create-dirs.sh
diff --git a/dev/build/celery-start.sh b/dev/build/celery-start.sh
index c8d4450da..d0dda4b23 100644
--- a/dev/build/celery-start.sh
+++ b/dev/build/celery-start.sh
@@ -8,11 +8,14 @@ echo "Running Datatracker checks..."
 if ! ietf/manage.py migrate --skip-checks --check ; then
     echo "Unapplied migrations found, waiting to start..."
     sleep 5
-    while ! ietf/manage.py migrate --skip-checks --check ; do 
+    while ! ietf/manage.py migrate --skip-checks --check ; do
+        echo "... still waiting for migrations..."
         sleep 5
     done
 fi
 
+echo "Starting Celery..."
+
 cleanup () {
   # Cleanly terminate the celery app by sending it a TERM, then waiting for it to exit.
   if [[ -n "${celery_pid}" ]]; then
diff --git a/dev/build/datatracker-start.sh b/dev/build/datatracker-start.sh
index 31cf7a5b7..85017dc54 100644
--- a/dev/build/datatracker-start.sh
+++ b/dev/build/datatracker-start.sh
@@ -3,8 +3,14 @@
 echo "Running Datatracker checks..."
 ./ietf/manage.py check
 
-echo "Running Datatracker migrations..."
-./ietf/manage.py migrate --skip-checks --settings=settings_local
+if ! ietf/manage.py migrate --skip-checks --check ; then
+    echo "Unapplied migrations found, waiting to start..."
+    sleep 5
+    while ! ietf/manage.py migrate --skip-checks --check ; do 
+        echo "... still waiting for migrations..."
+        sleep 5
+    done
+fi
 
 echo "Starting Datatracker..."
 
diff --git a/dev/build/migration-start.sh b/dev/build/migration-start.sh
new file mode 100644
index 000000000..d3505f3b2
--- /dev/null
+++ b/dev/build/migration-start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+echo "Running Datatracker migrations..."
+./ietf/manage.py migrate --skip-checks --settings=settings_local
+
+echo "Done!"
diff --git a/dev/build/start.sh b/dev/build/start.sh
index f20d264be..ef6c7fc0d 100644
--- a/dev/build/start.sh
+++ b/dev/build/start.sh
@@ -5,14 +5,20 @@
 #  CONTAINER_ROLE - datatracker, celery, or beat (defaults to datatracker)
 #
 case "${CONTAINER_ROLE:-datatracker}" in
-    datatracker)
+    auth)
         exec ./datatracker-start.sh
         ;;
+    beat)
+        exec ./celery-start.sh --app=ietf beat
+        ;;
     celery)
         exec ./celery-start.sh --app=ietf worker
         ;;
-    beat)
-        exec ./celery-start.sh --app=ietf beat
+    datatracker)
+        exec ./datatracker-start.sh
+        ;;
+    migrations)
+        exec ./migration-start.sh
         ;;
     *)
         echo "Unknown role '${CONTAINER_ROLE}'"
diff --git a/k8s/datatracker.yaml b/k8s/datatracker.yaml
index 72e35b73d..5ad433661 100644
--- a/k8s/datatracker.yaml
+++ b/k8s/datatracker.yaml
@@ -82,6 +82,35 @@ spec:
             readOnlyRootFilesystem: true
             runAsUser: 1000
             runAsGroup: 1000
+      initContainers:
+        - name: migration
+          image: "ghcr.io/ietf-tools/datatracker:$APP_IMAGE_TAG"
+          env:
+            - name: "CONTAINER_ROLE"
+              value: "migrations"
+          envFrom:
+            - configMapRef:
+                name: django-config
+          securityContext:
+            allowPrivilegeEscalation: false
+            capabilities:
+              drop:
+              - ALL
+            readOnlyRootFilesystem: true
+            runAsUser: 1000
+            runAsGroup: 1000
+          volumeMounts:
+            - name: dt-vol
+              mountPath: /a
+            - name: dt-tmp
+              mountPath: /tmp
+            - name: dt-home
+              mountPath: /home/datatracker
+            - name: dt-xml2rfc-cache
+              mountPath: /var/cache/xml2rfc
+            - name: dt-cfg
+              mountPath: /workspace/ietf/settings_local.py
+              subPath: settings_local.py
       volumes:
         # To be overriden with the actual shared volume
         - name: dt-vol