diff --git a/src/backend/settings.py b/src/backend/settings.py
index f1cbad5acc80025fc13ddf8bce18a2477c1cf793..2fe93d2edada3bdd18894f1d904a1fedb22d7960 100644
--- a/src/backend/settings.py
+++ b/src/backend/settings.py
@@ -226,7 +226,7 @@ CORS_ORIGIN_ALLOW_ALL = True
 
 AUTH_USER_MODEL = "users.CustomUser"
 REST_FRAMEWORK = {
-    "DEFAULT_AUTHENTICATION_CLASSES": (
+    "DEFAULT_AUTHENTICATION_CLASSES": (  
         "rest_framework_simplejwt.authentication.JWTAuthentication",
     ),
     'DEFAULT_THROTTLE_CLASSES': [
diff --git a/src/scheduler/authentication.py b/src/scheduler/authentication.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fca841e98dd902ef0d6a02131a0ef2e5f669bb5
--- /dev/null
+++ b/src/scheduler/authentication.py
@@ -0,0 +1,15 @@
+from rest_framework import authentication, exceptions
+from .models import Runner
+
+class APIKeyAuthentication(authentication.BaseAuthentication):
+    """Authenticate requests using the runner API key."""
+    def authenticate(self, request):
+        auth = request.META.get('HTTP_AUTHORIZATION', '')
+        if not auth.lower().startswith('bearer '):
+            return None
+        token = auth.split()[1]
+        try:
+            runner = Runner.objects.get(api_key=token)
+        except Runner.DoesNotExist:
+            raise exceptions.AuthenticationFailed('Invalid API key')
+        return (runner, None)
\ No newline at end of file
diff --git a/src/scheduler/views.py b/src/scheduler/views.py
index 456898af415c9130b06e061cb69e127dfa0cce8d..06e648b8e49344bef790c67dcb0100e497c7ec4a 100644
--- a/src/scheduler/views.py
+++ b/src/scheduler/views.py
@@ -11,10 +11,13 @@ from .serializers import (
     RunnerSerializer,
     AdminRunnerSerializer
 )
+from .authentication import APIKeyAuthentication
 
 
 class NextAnalyticsJobView(APIView):
     """GET /jobs/next/ → returns the next pending analytics job or 204."""
+    authentication_classes = [APIKeyAuthentication]
+    permission_classes = []
     def get(self, request):
         job = AnalyticsJob.objects.filter(status=AnalyticsJob.STATUS_PENDING).order_by('created_at').first()
         if not job:
diff --git a/tools/local-runner/docker-compose.yml b/tools/local-runner/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d4b46c86c0300749684826cf71d3db59a8c36c73
--- /dev/null
+++ b/tools/local-runner/docker-compose.yml
@@ -0,0 +1,10 @@
+services:
+  runner:
+    image: registry.git.rwth-aachen.de/polaris/runner-v2/main:latest
+    environment:
+      - API_URL=http://host.docker.internal:8000
+      - API_KEY=4ef8d05a2b3340508d7b55a12a8630d6         # from your host env or .env file
+      - POLL_INTERVAL=5
+    volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
+    restart: always