diff --git a/.github/workflows/check-developer-experience.yml b/.github/workflows/check-developer-experience.yml
index 7d1a32bb..a7fdcf0a 100644
--- a/.github/workflows/check-developer-experience.yml
+++ b/.github/workflows/check-developer-experience.yml
@@ -88,6 +88,8 @@ jobs:
         continue-on-error: ${{ matrix.rust.optional }}
 
       - name: Setup tmate session for debug
-        if: ${{ failure() }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
         uses: mxschmitt/action-tmate@v3
         timeout-minutes: 30
+        with:
+          limit-access-to-actor: true
diff --git a/.github/workflows/sterile-build-and-test.yml b/.github/workflows/sterile-build-and-test.yml
index 21d68805..872a421e 100644
--- a/.github/workflows/sterile-build-and-test.yml
+++ b/.github/workflows/sterile-build-and-test.yml
@@ -3,7 +3,14 @@
 # Artifacts produced by this workflow are intended to be used for production.
 name: Sterile build + test
 on:
-  - push
+  push:
+  workflow_dispatch:
+    inputs:
+      debug_enabled:
+        type: boolean
+        description: "Run with tmate enabled"
+        required: false
+        default: false
 
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
@@ -42,41 +49,19 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - run: just rust="${{matrix.rust}}" sterile cargo build --profile=debug --target=x86_64-unknown-linux-gnu
       - run: just rust="${{matrix.rust}}" sterile cargo test --profile=debug --target=x86_64-unknown-linux-gnu
-      - run: just rust="${{matrix.rust}}" sterile cargo build --profile=debug --target=x86_64-unknown-linux-musl
       - run: just rust="${{matrix.rust}}" sterile cargo test --profile=debug --target=x86_64-unknown-linux-musl
-
-      - run: just rust="${{matrix.rust}}" sterile cargo build --profile=release --target=x86_64-unknown-linux-gnu
       - run: just rust="${{matrix.rust}}" sterile cargo test --profile=release --target=x86_64-unknown-linux-gnu
-      - run: just rust="${{matrix.rust}}" sterile cargo build --profile=release --target=x86_64-unknown-linux-musl
       - run: just rust="${{matrix.rust}}" sterile cargo test --profile=release --target=x86_64-unknown-linux-musl
 
-      - run: |
-          set -euxo pipefail
-          tar \
-            --format=posix \
-            --sort=name \
-            --clamp-mtime \
-            --mtime=0 \
-            --numeric-owner \
-            --owner=0 \
-            --group=0 \
-            --mode='go+u,go-w' \
-            -cnf dpdk-sys-${{ matrix.dpdk_sys }}-rust-${{ matrix.rust }}-sterile.tar \
-            sterile
-      - run: |
-          set -euxo pipefail
-          zstd \
-            --single-thread \
-            -15 \
-            --long \
-            dpdk-sys-${{ matrix.dpdk_sys }}-rust-${{ matrix.rust }}-sterile.tar
-      - uses: actions/upload-artifact@v4
+      - run: just rust="${{matrix.rust}}" profile=debug target=x86_64-unknown-linux-gnu push-container
+      - run: just rust="${{matrix.rust}}" profile=release target=x86_64-unknown-linux-gnu push-container
+      - run: just rust="${{matrix.rust}}" profile=debug target=x86_64-unknown-linux-musl push-container
+      - run: just rust="${{matrix.rust}}" profile=release target=x86_64-unknown-linux-musl push-container
+
+      - name: Setup tmate session for debug
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
+        uses: mxschmitt/action-tmate@v3
+        timeout-minutes: 30
         with:
-          name: dpdk-sys-${{ matrix.dpdk_sys }}-rust-${{ matrix.rust }}-sterile.tar.zst
-          path: dpdk-sys-${{ matrix.dpdk_sys }}-rust-${{ matrix.rust }}-sterile.tar.zst
-          include-hidden-files: true
-          retention-days: 1
-          compression-level: 0
-          overwrite: true
+          limit-access-to-actor: true
diff --git a/justfile b/justfile
index 47b1cb91..77eda4a5 100644
--- a/justfile
+++ b/justfile
@@ -103,7 +103,7 @@ _build_time := ```
 
 [private]
 @default:
-  just --list --justfile {{justfile()}}
+    just --list --justfile {{ justfile() }}
 
 [group('ci')]
 [private]
@@ -157,7 +157,7 @@ dev-env *args="": allocate-2M-hugepages allocate-1G-hugepages mount-hugepages fi
       --tty \
       --name dataplane-dev-env \
       --privileged \
-      --network="{{_network}}" \
+      --network="{{ _network }}" \
       --security-opt seccomp=unconfined \
       --mount "type=tmpfs,destination=/home/${USER:-runner},tmpfs-mode=0777" \
       --mount "type=bind,source=${hugemnt2M},destination=/mnt/hugepages/2M,bind-propagation=rprivate" \
@@ -194,7 +194,7 @@ compile-env *args: fill-out-dev-env-template
     sudo -E docker run \
       --rm \
       --name dataplane-compile-env \
-      --network="{{_network}}" \
+      --network="{{ _network }}" \
       --tmpfs "/tmp:uid=$(id -u),gid=$(id -g),nodev,noexec,nosuid" \
       --mount "type=tmpfs,destination=/home/${USER:-runner},tmpfs-mode=1777" \
       --mount "type=bind,source=$(pwd),destination=$(pwd),bind-propagation=rprivate" \
@@ -407,6 +407,24 @@ build-container: sterile-build
         "{{ container_repo }}:{{ _slug }}"
     fi
 
+[script]
+push-container: build-container
+    declare build_date
+    build_date="$(date --utc --iso-8601=date --date="{{ _build_time }}")"
+    declare -r build_date
+    sudo -E docker push \
+          "{{ container_repo }}:${build_date}.{{ _slug }}.{{ target }}.{{ profile }}.{{ _commit }}"
+    sudo -E docker push \
+          "{{ container_repo }}:{{ _slug }}.{{ target }}.{{ profile }}.{{ _commit }}"
+    if [ "{{ target }}" = "x86_64-unknown-linux-gnu" ]; then
+      sudo -E docker push \
+        "{{ container_repo }}:{{ _slug }}.{{ profile }}"
+    fi
+    if [ "{{ target }}" = "x86_64-unknown-linux-gnu" ] && [ "{{ profile }}" = "release" ]; then
+      sudo -E docker tag \
+        "{{ container_repo }}:{{ _slug }}"
+    fi
+
 [script]
 build-docs:
     cd design-docs/src/mdbook