diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 038eb524e5dee206f8b47bb317d1c6b636c33afd..072d9a470cca087bdce4808fbf7ef47fd4affcf9 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -401,6 +401,7 @@ MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media"))
 NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules"))
 
 YARN_INSTALLED_APPS = [
+    "@fontsource/roboto",
     "datatables",
     "jquery",
     "materialize-css",
@@ -435,6 +436,7 @@ ANY_JS = {
     },
     "sortablejs": {"js_url": JS_URL + "/sortablejs/Sortable.min.js"},
     "jquery-sortablejs": {"js_url": JS_URL + "/jquery-sortablejs/jquery-sortable.js"},
+    "Roboto": {"css_url": JS_URL + "/@fontsource/roboto/index.css"},
 }
 
 merge_app_settings("ANY_JS", ANY_JS, True)
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index 2087829da24c157b7dc0a797da776d7bb1cc9a3b..d34db4f65d05021f1d68e61190bf323c249e5ada 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -18,6 +18,7 @@
 
   {# CSS #}
   {% include_css "material-design-icons" %}
+  {% include_css "Roboto" %}
   <link rel="stylesheet" href="{% sass_src 'style.scss' %}">
 
   {# Add JS URL resolver #}
diff --git a/aleksis/core/templates/core/base_print.html b/aleksis/core/templates/core/base_print.html
index 37868f6f3e2de5d0ca2df6e57d4e111f1e7f1747..9539808f963d541811d9581ca377006bd1ef74a8 100644
--- a/aleksis/core/templates/core/base_print.html
+++ b/aleksis/core/templates/core/base_print.html
@@ -15,6 +15,7 @@
   </title>
 
   {% include_css "material-design-icons" %}
+  {% include_css "Roboto" %}
   {% include_css "paper-css" %}
   <link rel="stylesheet" href="{% sass_src 'style.scss' %}"/>
   <link rel="stylesheet" href="{% static "print.css" %}"/>
diff --git a/docker-startup.sh b/docker-startup.sh
index 673e48fb8e1ab27e756730015b07d928b62b0c30..98705fb16b38d6ad1f4012e25ea4e5768782eb4d 100755
--- a/docker-startup.sh
+++ b/docker-startup.sh
@@ -1,44 +1,112 @@
-#!/bin/bash
+#!/bin/sh
+#-
+# Startup/entrypoint script for deployments based on Docker, vanilla or K8s
+#
+# Designed to be used in Kubernetes in a way such that this container is used
+# in four places:
+#
+#  1. The app pod(s), setting PREPARE = 0
+#  2. The celery-worker pod(s), setting PREPARE = 0 and RUN_MODE = celery-worker
+#  3. One celery-beat pod, setting PREPARE = 0 and RUN_MODE = celery-beat
+#  4. A post-deploy job, setting RUN_MODE = prepare
+#
+# To run as stand-alone Docker container, bundling all components, set
+# ALEKSIS_dev__uwsgi__celery = true.
 
+# Run mode to start container in
+#
+#  uwsgi       - application server
+#  celery-$foo - celery commands (e.g. worker or beat)
+#  *           - Anything else to run arguments verbatim
 RUN_MODE=${RUN_MODE:-uwsgi}
+
+# HTTP port to let uWSGI bind to
 HTTP_PORT=${HTTP_PORT:-8000}
 
-if [[ -z $ALEKSIS_secret_key ]]; then
-    if [[ ! -e /var/lib/aleksis/secret_key ]]; then
-	touch /var/lib/aleksis/secret_key; chmod 600 /var/lib/aleksis/secret_key
-	LC_ALL=C tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' </dev/urandom | head -c 64 >/var/lib/aleksis/secret_key
-    fi
-    ALEKSIS_secret_key=$(</var/lib/aleksis/secret_key)
-fi
+# Run preparation steps before real command
+PREPARE=${PREPARE:-1}
 
-echo -n "Waiting for database."
-while ! aleksis-admin dbshell -- -c "SELECT 1" >/dev/null 2>&1; do
-    sleep 0.5
-    echo -n .
-done
-echo
+wait_migrations() {
+	# Wait for migrations to be applied from elsewhere, e.g. a K8s job
+	echo -n "Waiting for migrations to appear"
+	until aleksis-admin migrate --check >/dev/null 2>&1; do
+		sleep 0.5
+		echo -n .
+	done
+	echo
+}
 
-aleksis-admin compilescss
-aleksis-admin collectstatic --no-input --clear
+wait_database() {
+	# Wait for database to be reachable
+	echo -n "Waiting for database."
+	until aleksis-admin dbshell -- -c "SELECT 1" >/dev/null 2>&1; do
+		sleep 0.5
+		echo -n .
+	done
+	echo
+}
 
-case "$RUN_MODE" in
-    uwsgi)
-	aleksis-admin migrate
-	aleksis-admin createinitialrevisions
+prepare_static() {
+	# Prepare static files; should only be run in app container or job
 	aleksis-admin compilescss
 	aleksis-admin collectstatic --no-input --clear
-	exec aleksis-admin runuwsgi -- --http-socket=:$HTTP_PORT
-        ;;
-    celery-worker)
+}
+
+prepare_database() {
+	# Migrate database; should only be run in app container or job
 	aleksis-admin migrate
 	aleksis-admin createinitialrevisions
-	exec celery -A aleksis.core worker
+}
+
+if [ -z "$ALEKSIS_secret_key" ]; then
+	# Use a random session secret key if none was provided
+	# In K8s, should be provided from a K8s secret
+	if [ ! -e /var/lib/aleksis/secret_key ]; then
+		touch /var/lib/aleksis/secret_key
+		chmod 600 /var/lib/aleksis/secret_key
+		LC_ALL=C tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' </dev/urandom | head -c 64 >/var/lib/aleksis/secret_key
+	fi
+	ALEKSIS_secret_key=$(cat /var/lib/aleksis/secret_key)
+fi
+
+# Wait for database to be reachable under all conditions
+wait_database
+
+case "$RUN_MODE" in
+uwsgi)
+	# uWSGI app server mode
+
+	if [ $PREPARE = 1 ]; then
+		# Responsible for running migratiosn and preparing staticfiles
+		prepare_database
+		prepare_static
+	else
+		# Wait for migrations to be applied elsewhere
+		wait_migrations
+	fi
+
+	exec aleksis-admin runuwsgi -- --http-socket=:$HTTP_PORT
 	;;
-    celery-beat)
-	aleksis-admin migrate
-	exec celery -A aleksis.core beat
+celery-*)
+	# Celery command mode
+
+	if [ $PREPARE = 1 ]; then
+		# Responsible for running migrations
+		prepare_database
+	else
+		# Wait for migrations to be applied elsewhere
+		wait_migrations
+	fi
+
+	exec celery -A aleksis.core ${RUN_MODE#celery-}
+	;;
+prepare)
+	# Preparation only mode
+	prepare_database
+	prepare_static
 	;;
-    *)
+*)
+	# Run arguments as command verbatim
 	exec "$@"
 	;;
 esac