diff --git a/deploy/dev/instance_a/testdata.py b/deploy/dev/instance_a/testdata.py
new file mode 100644
index 0000000..4a5cfbd
--- /dev/null
+++ b/deploy/dev/instance_a/testdata.py
@@ -0,0 +1,108 @@
+import base64
+
+
+def create_test_data():
+    print('Creating test data for instance A')
+
+    from authentication.models import ToolshedUser
+    admin = None
+    try:
+        if ToolshedUser.objects.filter(username='admin').exists():
+            admin = ToolshedUser.objects.get(username='admin')
+        else:
+            admin = ToolshedUser.objects.create_superuser('admin', 'admin@localhost', '')
+            admin.set_password('j7Th5TfCGUZmQ7U')
+            admin.save()
+            print('Created {}@{} with private key {} and public key {}'.format(admin.username, admin.domain,
+                                                                               admin.private_key,
+                                                                               admin.public_identity.public_key))
+    except Exception as e:
+        print('Admin user already exists, skipping')
+
+    from hostadmin.models import Domain
+    try:
+        domain1 = Domain.objects.create(name='localhost', owner=admin, open_registration=False)
+        domain1.save()
+        print('Created domain {} with closed registration'.format(domain1.name))
+    except Exception as e:
+        print('Domain localhost already exists, skipping')
+
+    domain2 = None
+    try:
+        if Domain.objects.filter(name='a.localhost').exists():
+            domain2 = Domain.objects.get(name='a.localhost')
+        domain2 = Domain.objects.create(name='a.localhost', owner=admin, open_registration=True)
+        domain2.save()
+        print('Created domain {} with open registration'.format(domain2.name))
+    except Exception as e:
+        print('Domain b.localhost already exists, skipping')
+
+    user = None
+    try:
+        if ToolshedUser.objects.filter(username='test_a').exists():
+            user = ToolshedUser.objects.get(username='test_a')
+        user = ToolshedUser.objects.create_user('test_a', 'testa@example.com', '', domain=domain2.name,
+                                                private_key='4ab79601edc400fd0263c7d5fd045ea7f5de28f1dd8905e2479f96893f3257d8')
+        user.set_password('test_a')
+        user.save()
+        print('Created user {}@{} with private key {} and public key {}'.format(user.username, user.domain,
+                                                                                user.private_key,
+                                                                                user.public_identity.public_key))
+    except Exception as e:
+        print('User foobar already exists, skipping')
+
+    from authentication.models import KnownIdentity
+    identity = None
+    try:
+        if KnownIdentity.objects.filter(username='test_b').exists():
+            identity = KnownIdentity.objects.get(username='test_b')
+        identity = KnownIdentity.objects.create(username='test_b', domain='b.localhost',
+                                                public_key='4b0dffe21764c591762615ef84cfea7fd4055fab3e9f58ae077fd8c79c34af91')
+        # pk '2ec1e7d5f5b8d5f87233944970d57f942095fe9e6c4fc49edde61fcd3fb1bf40'
+        identity.save()
+        print('Created identity {}@{} with public key {}'.format(identity.username, identity.domain,
+                                                                 identity.public_key))
+    except Exception as e:
+        print('Identity already exists, skipping')
+
+    from toolshed.models import Category
+    category1 = None
+    try:
+        if Category.objects.filter(name='Test Category').exists():
+            category1 = Category.objects.get(name='Test Category')
+        category1 = Category.objects.get_or_create(name='Test Category', origin='test')[0]
+    except Exception as e:
+        print('Category already exists, skipping')
+
+    from toolshed.models import InventoryItem
+    item1 = None
+    try:
+        if InventoryItem.objects.filter(name='Test Item').exists():
+            item1 = InventoryItem.objects.get(name='Test Item')
+        item1 = InventoryItem.objects.create(name='Test Item', description='This is a test item', category=category1,
+                                             availability_policy='rent', owned_quantity=1, owner=user)
+        item1.save()
+    except Exception as e:
+        print('Item already exists, skipping')
+
+    try:
+        item2 = InventoryItem.objects.create(name='Test Item 2', description='This is a test item', category=category1,
+                                             availability_policy='share', owned_quantity=1, owner=user)
+        item2.save()
+        print('Created test items with IDs "{}" and "{}"'.format(item1.id, item2.id))
+    except Exception as e:
+        print('Item already exists, skipping')
+
+    try:
+        user.friends.add(identity)
+        print('Added identity {} to friends list of user {}'.format(identity.username, user.username))
+    except Exception as e:
+        print('Identity already in friends list, skipping')
+
+    from files.models import File
+    try:
+        file1 = File.objects.create(mime_type='text/plain', data=base64.b64encode(b'testcontent1').decode('utf-8'))
+        file1.save()
+        print(f'Created file at /{file1.hash[:2]}/{file1.hash[2:4]}/{file1.hash[4:6]}/{file1.hash[6:]}')
+    except Exception as e:
+        print('File already exists, skipping')
diff --git a/deploy/dev/instance_b/testdata.py b/deploy/dev/instance_b/testdata.py
new file mode 100644
index 0000000..2a30680
--- /dev/null
+++ b/deploy/dev/instance_b/testdata.py
@@ -0,0 +1,108 @@
+import base64
+
+
+def create_test_data():
+    print('Creating test data for instance B')
+
+    from authentication.models import ToolshedUser
+    admin = None
+    try:
+        if ToolshedUser.objects.filter(username='admin').exists():
+            admin = ToolshedUser.objects.get(username='admin')
+        else:
+            admin = ToolshedUser.objects.create_superuser('admin', 'admin@localhost', '')
+            admin.set_password('j7Th5TfCGUZmQ7U')
+            admin.save()
+            print('Created {}@{} with private key {} and public key {}'.format(admin.username, admin.domain,
+                                                                               admin.private_key,
+                                                                               admin.public_identity.public_key))
+    except Exception as e:
+        print('Admin user already exists, skipping')
+
+    from hostadmin.models import Domain
+    try:
+        domain1 = Domain.objects.create(name='localhost', owner=admin, open_registration=False)
+        domain1.save()
+        print('Created domain {} with closed registration'.format(domain1.name))
+    except Exception as e:
+        print('Domain localhost already exists, skipping')
+
+    domain2 = None
+    try:
+        if Domain.objects.filter(name='b.localhost').exists():
+            domain2 = Domain.objects.get(name='b.localhost')
+        domain2 = Domain.objects.create(name='b.localhost', owner=admin, open_registration=True)
+        domain2.save()
+        print('Created domain {} with open registration'.format(domain2.name))
+    except Exception as e:
+        print('Domain b.localhost already exists, skipping')
+
+    user = None
+    try:
+        if ToolshedUser.objects.filter(username='test_b').exists():
+            user = ToolshedUser.objects.get(username='test_b')
+        user = ToolshedUser.objects.create_user('test_b', 'testb@example.com', '', domain=domain2.name,
+                                                private_key='2ec1e7d5f5b8d5f87233944970d57f942095fe9e6c4fc49edde61fcd3fb1bf40')
+        user.set_password('test_b')
+        user.save()
+        print('Created user {}@{} with private key {} and public key {}'.format(user.username, user.domain,
+                                                                                user.private_key,
+                                                                                user.public_identity.public_key))
+    except Exception as e:
+        print('User foobar already exists, skipping')
+
+    from authentication.models import KnownIdentity
+    identity = None
+    try:
+        if KnownIdentity.objects.filter(username='test_a').exists():
+            identity = KnownIdentity.objects.get(username='test_a')
+        identity = KnownIdentity.objects.create(username='test_a', domain='a.localhost',
+                                                public_key='cdcf6f1897b0ab3a123047d9b707d4123f01daf41921dbe787d1d0697f0ee42b')
+        # pk '4ab79601edc400fd0263c7d5fd045ea7f5de28f1dd8905e2479f96893f3257d8'
+        identity.save()
+        print('Created identity {}@{} with public key {}'.format(identity.username, identity.domain,
+                                                                 identity.public_key))
+    except Exception as e:
+        print('Identity already exists, skipping')
+
+    from toolshed.models import Category
+    category1 = None
+    try:
+        if Category.objects.filter(name='Test Category').exists():
+            category1 = Category.objects.get(name='Test Category')
+        category1 = Category.objects.get_or_create(name='Test Category', origin='test')[0]
+    except Exception as e:
+        print('Category already exists, skipping')
+
+    from toolshed.models import InventoryItem
+    item1 = None
+    try:
+        if InventoryItem.objects.filter(name='Test Item 3').exists():
+            item1 = InventoryItem.objects.get(name='Test Item 3')
+        item1 = InventoryItem.objects.create(name='Test Item 3', description='This is a test item', category=category1,
+                                             availability_policy='sell', owned_quantity=1, owner=user)
+        item1.save()
+    except Exception as e:
+        print('Item already exists, skipping')
+
+    try:
+        item2 = InventoryItem.objects.create(name='Test Item 4', description='This is a test item', category=category1,
+                                             availability_policy='lend', owned_quantity=1, owner=user)
+        item2.save()
+        print('Created test items with IDs "{}" and "{}"'.format(item1.id, item2.id))
+    except Exception as e:
+        print('Item already exists, skipping')
+
+    try:
+        user.friends.add(identity)
+        print('Added identity {} to friends list of user {}'.format(identity.username, user.username))
+    except Exception as e:
+        print('Identity already in friends list, skipping')
+
+    from files.models import File
+    try:
+        file1 = File.objects.create(mime_type='text/plain', data=base64.b64encode(b'testcontent1').decode('utf-8'))
+        file1.save()
+        print(f'Created file at /{file1.hash[:2]}/{file1.hash[2:4]}/{file1.hash[4:6]}/{file1.hash[6:]}')
+    except Exception as e:
+        print('File already exists, skipping')
diff --git a/deploy/docker-compose.override.yml b/deploy/docker-compose.override.yml
index 30b4de9..6ed6978 100644
--- a/deploy/docker-compose.override.yml
+++ b/deploy/docker-compose.override.yml
@@ -8,6 +8,7 @@ services:
     volumes:
       - ../backend:/code
       - ../deploy/dev/instance_a/a.env:/code/.env
+      - ../deploy/dev/instance_a/testdata.py:/code/testdata.py
       - ../deploy/dev/instance_a/a.sqlite3:/code/db.sqlite3
     expose:
       - 8000
@@ -20,6 +21,7 @@ services:
     volumes:
       - ../backend:/code
       - ../deploy/dev/instance_b/b.env:/code/.env
+      - ../deploy/dev/instance_b/testdata.py:/code/testdata.py
       - ../deploy/dev/instance_b/b.sqlite3:/code/db.sqlite3
     expose:
       - 8000
@@ -30,11 +32,11 @@ services:
       context: ../frontend/
       dockerfile: ../deploy/dev/Dockerfile.frontend
     volumes:
-      - ../frontend:/app:ro
+      - ../frontend:/app
       - /app/node_modules
     expose:
       - 5173
-    command: npm run dev -- --host
+    command: bash -c "npm install && npm run dev -- --host"
 
   wiki:
     build:
diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue
index 136556f..38c4b13 100644
--- a/frontend/src/components/Sidebar.vue
+++ b/frontend/src/components/Sidebar.vue
@@ -9,6 +9,12 @@
                 <li class="sidebar-header">
                     Tools & Components
                 </li>
+                <li class="sidebar-item">
+                    <router-link to="/inventory" class="sidebar-link">
+                        <b-icon-archive class="bi-valign-middle"></b-icon-archive>
+                        <span class="align-middle">Inventory</span>
+                    </router-link>
+                </li>
                 <li class="sidebar-item">
                     <router-link to="/friends" class="sidebar-link">
                         <b-icon-people class="bi-valign-middle"></b-icon-people>
diff --git a/frontend/src/dns.js b/frontend/src/dns.js
index f07ea22..9446ccb 100644
--- a/frontend/src/dns.js
+++ b/frontend/src/dns.js
@@ -13,9 +13,13 @@ function get_prefered_server() {
     request.open('GET', '/local/dns', false);
     request.send(null);
     if (request.status === 200) {
-        const servers = JSON.parse(request.responseText);
-        if (servers && servers.length > 0) {
-            return servers;
+        try {
+            const servers = JSON.parse(request.responseText);
+            if (servers && servers.length > 0) {
+                return servers;
+            }
+        } catch (e) {
+            console.error(e);
         }
     }
     return ['1.1.1.1', '8.8.8.8'];
diff --git a/frontend/src/router.js b/frontend/src/router.js
index ee7cf6e..d8d97fa 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router.js
@@ -4,10 +4,12 @@ import Login from '@/views/Login.vue';
 import Register from '@/views/Register.vue';
 import store from '@/store';
 import Friends from "@/views/Friends.vue";
+import Inventory from '@/views/Inventory.vue';
 
 
 const routes = [
     {path: '/', component: Dashboard, meta: {requiresAuth: true}},
+    {path: '/inventory', component: Inventory, meta: {requiresAuth: true}},
     {path: '/friends', component: Friends, meta: {requiresAuth: true}},
     {path: '/login', component: Login, meta: {requiresAuth: false}},
     {path: '/register', component: Register, meta: {requiresAuth: false}},
diff --git a/frontend/src/store.js b/frontend/src/store.js
index 071004d..5b4450a 100644
--- a/frontend/src/store.js
+++ b/frontend/src/store.js
@@ -13,10 +13,13 @@ export default createStore({
         token: null,
         keypair: null,
         remember: false,
+        friends: [],
+        item_map: {},
         home_servers: null,
         all_friends_servers: null,
         resolver: new FallBackResolver(),
         unreachable_neighbors: new NeighborsCache(),
+        availability_policies: [],
     },
     mutations: {
         setUser(state, user) {
@@ -43,6 +46,9 @@ export default createStore({
             }
             localStorage.setItem('remember', remember);
         },
+        setInventoryItems(state, {url, items}) {
+            state.item_map[url] = items;
+        },
         setFriends(state, friends) {
             state.friends = friends;
         },
@@ -52,6 +58,9 @@ export default createStore({
         setAllFriendsServers(state, servers) {
             state.all_friends_servers = servers;
         },
+        setAvailabilityPolicies(state, availability_policies) {
+            state.availability_policies = availability_policies;
+        },
         logout(state) {
             state.user = null;
             state.token = null;
@@ -128,6 +137,13 @@ export default createStore({
         async getFriendServers({state, dispatch, commit}, {username}) {
             return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
         },
+        async fetchInventoryItems({commit, dispatch, getters}) {
+            const servers = await dispatch('getHomeServers')
+            const items = await servers.get(getters.signAuth, '/api/inventory_items/')
+            items.map(item => item.files.map(file => file.owner = item.owner))
+            commit('setInventoryItems', {url: '/', items})
+            return items
+        },
         async fetchFriends({commit, dispatch, getters, state}) {
             const servers = await dispatch('getHomeServers')
             const data = await servers.get(getters.signAuth, '/api/friends/')
@@ -205,5 +221,13 @@ export default createStore({
         nullAuth(state) {
             return createNullAuth({})
         },
+        inventory_items(state) {
+            return state.item_map['/'] || []
+        },
+        loaded_items(state) {
+            return Object.entries(state.item_map).reduce((acc, [url, items]) => {
+                return acc.concat(items)
+            }, [])
+        },
     }
 })
\ No newline at end of file
diff --git a/frontend/src/views/Inventory.vue b/frontend/src/views/Inventory.vue
new file mode 100644
index 0000000..9159df9
--- /dev/null
+++ b/frontend/src/views/Inventory.vue
@@ -0,0 +1,70 @@
+<template>
+    <BaseLayout>
+        <main class="content">
+            <div class="container-fluid p-0">
+                <div class="row">
+                    <div class="col-12 col-xl-6">
+                        <div class="card">
+                            <div class="card-header">
+                                <h5 class="card-title">{{ user }}'s Inventory</h5>
+                            </div>
+                            <table class="table table-striped">
+                                <thead>
+                                <tr>
+                                    <th style="width:40%;">Name</th>
+                                    <th style="width:25%">Availability Policy</th>
+                                    <th class="d-none d-md-table-cell" style="width:25%">Amount</th>
+                                    <th>Actions</th>
+                                </tr>
+                                </thead>
+                                <tbody>
+                                <tr v-for="item in inventory_items" :key="item.id">
+                                    <td>
+                                        {{ item.name }}
+                                    </td>
+                                    <td class="d-none d-md-table-cell">{{ item.availability_policy }}</td>
+                                    <td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
+                                    <td class="table-action">
+                                    </td>
+                                </tr>
+                                </tbody>
+                            </table>
+                        </div>
+                        <div class="card">
+                            <button class="btn" @click="fetchInventoryItems">Refresh</button>
+                            <router-link to="/inventory/new" class="btn btn-primary">Add</router-link>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </main>
+    </BaseLayout>
+</template>
+
+<script>
+import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
+import * as BIcons from "bootstrap-icons-vue";
+import BaseLayout from "@/components/BaseLayout.vue";
+
+export default {
+    name: "Inventory",
+    components: {
+        BaseLayout,
+        ...BIcons
+    },
+    computed: {
+        ...mapGetters(["inventory_items", "loaded_items"]),
+        ...mapState(["user"]),
+    },
+    methods: {
+        ...mapActions(["fetchInventoryItems", "deleteInventoryItem"]),
+    },
+    async mounted() {
+        await this.fetchInventoryItems()
+    }
+}
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file