diff --git a/ara/api/filters.py b/ara/api/filters.py
index c0f42430..6ced353b 100644
--- a/ara/api/filters.py
+++ b/ara/api/filters.py
@@ -56,6 +56,7 @@ class LabelFilter(BaseFilter):
 
 
 class PlaybookFilter(DateFilter):
+    controller = django_filters.CharFilter(field_name="controller", lookup_expr="icontains")
     name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
     path = django_filters.CharFilter(field_name="path", lookup_expr="icontains")
     status = django_filters.MultipleChoiceFilter(
diff --git a/ara/api/migrations/0008_playbook_controller.py b/ara/api/migrations/0008_playbook_controller.py
new file mode 100644
index 00000000..a46ed712
--- /dev/null
+++ b/ara/api/migrations/0008_playbook_controller.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.17 on 2020-12-04 04:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0007_add_expired_status'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='playbook',
+            name='controller',
+            field=models.CharField(default='localhost', max_length=255),
+        ),
+    ]
diff --git a/ara/api/models.py b/ara/api/models.py
index 703245b0..75d20434 100644
--- a/ara/api/models.py
+++ b/ara/api/models.py
@@ -102,6 +102,7 @@ class Playbook(Duration):
     arguments = models.BinaryField(max_length=(2 ** 32) - 1)
     path = models.CharField(max_length=255)
     labels = models.ManyToManyField(Label)
+    controller = models.CharField(max_length=255, default="localhost")
 
     def __str__(self):
         return "<Playbook %s>" % self.id
diff --git a/ara/api/tests/factories.py b/ara/api/tests/factories.py
index 7fcf753d..7f9397e4 100644
--- a/ara/api/tests/factories.py
+++ b/ara/api/tests/factories.py
@@ -43,6 +43,7 @@ class PlaybookFactory(DjangoModelFactory):
     class Meta:
         model = models.Playbook
 
+    controller = "localhost"
     name = "test-playbook"
     ansible_version = "2.4.0"
     status = "running"
diff --git a/ara/api/tests/tests_playbook.py b/ara/api/tests/tests_playbook.py
index f23496e1..ee92e62c 100644
--- a/ara/api/tests/tests_playbook.py
+++ b/ara/api/tests/tests_playbook.py
@@ -32,11 +32,17 @@ class PlaybookTestCase(APITestCase):
 
     def test_playbook_serializer(self):
         serializer = serializers.PlaybookSerializer(
-            data={"name": "serializer-playbook", "ansible_version": "2.4.0", "path": "/path/playbook.yml"}
+            data={
+                "controller": "serializer",
+                "name": "serializer-playbook",
+                "ansible_version": "2.4.0",
+                "path": "/path/playbook.yml",
+            }
         )
         serializer.is_valid()
         playbook = serializer.save()
         playbook.refresh_from_db()
+        self.assertEqual(playbook.controller, "serializer")
         self.assertEqual(playbook.name, "serializer-playbook")
         self.assertEqual(playbook.ansible_version, "2.4.0")
         self.assertEqual(playbook.status, "unknown")
@@ -125,6 +131,20 @@ class PlaybookTestCase(APITestCase):
         request = self.client.get("/api/v1/playbooks/%s" % playbook.id)
         self.assertEqual(playbook.ansible_version, request.data["ansible_version"])
 
+    def test_get_playbook_by_controller(self):
+        playbook = factories.PlaybookFactory(name="playbook1", controller="controller-one")
+        factories.PlaybookFactory(name="playbook2", controller="controller-two")
+
+        # Test exact match
+        request = self.client.get("/api/v1/playbooks?controller=controller-one")
+        self.assertEqual(1, len(request.data["results"]))
+        self.assertEqual(playbook.name, request.data["results"][0]["name"])
+        self.assertEqual(playbook.controller, request.data["results"][0]["controller"])
+
+        # Test partial match
+        request = self.client.get("/api/v1/playbooks?controller=controller")
+        self.assertEqual(len(request.data["results"]), 2)
+
     def test_get_playbook_by_name(self):
         playbook = factories.PlaybookFactory(name="playbook1")
         factories.PlaybookFactory(name="playbook2")
diff --git a/ara/cli/playbook.py b/ara/cli/playbook.py
index 962b2aac..83299184 100644
--- a/ara/cli/playbook.py
+++ b/ara/cli/playbook.py
@@ -31,6 +31,12 @@ class PlaybookList(Lister):
             default=None,
             help=("List playbooks matching the provided label"),
         )
+        parser.add_argument(
+            "--controller",
+            metavar="<controller>",
+            default=None,
+            help=("List playbooks that ran from the provided controller (full or partial)"),
+        )
         parser.add_argument(
             "--name",
             metavar="<name>",
@@ -88,6 +94,9 @@ class PlaybookList(Lister):
         if args.label is not None:
             query["label"] = args.label
 
+        if args.controller is not None:
+            query["controller"] = args.controller
+
         if args.name is not None:
             query["name"] = args.name
 
@@ -118,6 +127,7 @@ class PlaybookList(Lister):
             columns = (
                 "id",
                 "status",
+                "controller",
                 "name",
                 "path",
                 "plays",
@@ -133,6 +143,7 @@ class PlaybookList(Lister):
             columns = (
                 "id",
                 "status",
+                "controller",
                 "path",
                 "tasks",
                 "results",
@@ -191,6 +202,7 @@ class PlaybookShow(ShowOne):
         columns = (
             "id",
             "report",
+            "controller",
             "status",
             "path",
             "started",
@@ -262,6 +274,12 @@ class PlaybookPrune(Command):
             default=None,
             help=("Only delete playbooks matching the provided name (full or partial)"),
         )
+        parser.add_argument(
+            "--controller",
+            metavar="<controller>",
+            default=None,
+            help=("Only delete playbooks that ran from the provided controller (full or partial)"),
+        )
         parser.add_argument(
             "--path",
             metavar="<path>",
@@ -316,6 +334,9 @@ class PlaybookPrune(Command):
         if args.label is not None:
             query["label"] = args.label
 
+        if args.controller is not None:
+            query["controller"] = args.controller
+
         if args.name is not None:
             query["name"] = args.name
 
diff --git a/ara/plugins/callback/ara_default.py b/ara/plugins/callback/ara_default.py
index bbbaf3c9..8981d0da 100644
--- a/ara/plugins/callback/ara_default.py
+++ b/ara/plugins/callback/ara_default.py
@@ -21,6 +21,7 @@ import datetime
 import json
 import logging
 import os
+import socket
 from concurrent.futures import ThreadPoolExecutor
 
 from ansible import __version__ as ansible_version
@@ -292,6 +293,7 @@ class CallbackModule(CallbackBase):
             arguments=cli_options,
             status="running",
             path=path,
+            controller=socket.getfqdn(),
             started=datetime.datetime.now(datetime.timezone.utc).isoformat(),
         )
 
diff --git a/ara/ui/forms.py b/ara/ui/forms.py
index 5b7af91e..0f3126ff 100644
--- a/ara/ui/forms.py
+++ b/ara/ui/forms.py
@@ -21,6 +21,7 @@ from ara.api import models
 
 
 class PlaybookSearchForm(forms.Form):
+    controller = forms.CharField(label="Playbook controller", max_length=255, required=False)
     name = forms.CharField(label="Playbook name", max_length=255, required=False)
     path = forms.CharField(label="Playbook path", max_length=255, required=False)
     status = forms.MultipleChoiceField(
diff --git a/ara/ui/templates/index.html b/ara/ui/templates/index.html
index d4da9fad..58c4010e 100644
--- a/ara/ui/templates/index.html
+++ b/ara/ui/templates/index.html
@@ -7,6 +7,14 @@
     <div class="pf-l-flex">
         <form novalidate action="/" method="get" class="pf-c-form">
         <div class="pf-c-form__group pf-m-inline">
+            <div class="pf-l-flex__item pf-m-flex-1">
+                <label class="pf-c-form__label" for="controller">
+                    <span class="pf-c-form__label-text">Controller</span>
+                </label>
+                <div class="pf-c-form__horizontal-group">
+                    <input class="pf-c-form-control" type="text" id="controller" name="controller" value="{% if search_form.controller.value is not null %}{{ search_form.controller.value }}{% endif %}" />
+                </div>
+            </div>
             <div class="pf-l-flex__item pf-m-flex-1">
                 <label class="pf-c-form__label" for="name">
                     <span class="pf-c-form__label-text">Name</span>
@@ -102,6 +110,7 @@
                     {% include "partials/sort_by_duration.html" %}
                 </th>
                 <th role="columnheader" scope="col" class="pf-m-fit-content">Ansible version</th>
+                <th role="columnheader" scope="col">Controller</th>
                 <th role="columnheader" scope="col">Name (or path)</th>
                 <th role="columnheader" scope="col">Labels</th>
                 <th role="columnheader" scope="col" class="pf-m-fit-content">Hosts</th>
@@ -142,6 +151,9 @@
                 <td role="cell" data-label="Ansible version" class="pf-m-fit-content">
                     {{ playbook.ansible_version }}
                 </td>
+                <td role="cell" data-label="Controller" class="pf-m-fit-content">
+                    {{ playbook.controller }}
+                </td>
                 <td role="cell" data-label="Name (or path)" class="pf-m-fit-content">
                     {% if static_generation %}
                         <a href="{% if page != "index" %}../{% endif %}playbooks/{{ playbook.id }}.html" title="{{ playbook.path }}">
diff --git a/ara/ui/views.py b/ara/ui/views.py
index 825f8290..f21b0368 100644
--- a/ara/ui/views.py
+++ b/ara/ui/views.py
@@ -22,7 +22,7 @@ class Index(generics.ListAPIView):
 
     def get(self, request, *args, **kwargs):
         # TODO: Can we retrieve those fields automatically ?
-        fields = ["order", "name", "started_after", "status", "label"]
+        fields = ["order", "controller", "name", "started_after", "status", "label"]
         search_query = False
         for field in fields:
             if field in request.GET:
diff --git a/tests/container_test_tasks.yaml b/tests/container_test_tasks.yaml
index 148f0855..cd2e8e5d 100644
--- a/tests/container_test_tasks.yaml
+++ b/tests/container_test_tasks.yaml
@@ -47,6 +47,7 @@
           ansible_version: "9.0.0.1"
           started: "{{ ansible_date_time.iso8601_micro }}"
           status: running
+          controller: localhost
           labels:
             - "{{ _get_root.json['version'] }}"
             - "{{ item.name }}:{{ item.tag }}"