diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index de99a0bc3ce1..a5f2707f6788 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -9,6 +9,9 @@
 
 #include <algorithm>
 #include <dirent.h>
+#include <dlfcn.h>
+#include <elf.h>
+#include <link.h>
 #include <string.h>
 #include <sys/types.h>
 
@@ -24,6 +27,35 @@
  * \brief Image Processing Algorithm module manager
  */
 
+static bool isLibcameraInstalled()
+{
+	/* musl doesn't declare _DYNAMIC in link.h, declare it manually. */
+	extern ElfW(Dyn) _DYNAMIC[];
+
+	/*
+	 * DT_RUNPATH (DT_RPATH when the linker uses old dtags) is removed on
+	 * install.
+	 */
+	for (const ElfW(Dyn) *dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn) {
+		if (dyn->d_tag == DT_RUNPATH || dyn->d_tag == DT_RPATH)
+			return false;
+	}
+
+	return true;
+}
+
+static const char *libcameraPath()
+{
+	Dl_info info;
+
+	/* Look up our own symbol. */
+	int ret = dladdr(reinterpret_cast<void *>(libcameraPath), &info);
+	if (ret == 0)
+		return nullptr;
+
+	return info.dli_fname;
+}
+
 namespace libcamera {
 
 LOG_DEFINE_CATEGORY(IPAManager)
@@ -112,7 +144,25 @@ IPAManager::IPAManager()
 				<< "No IPA found in '" << modulePaths << "'";
 	}
 
-	/* Load IPAs from the installed system path. */
+	/*
+	 * When libcamera is used before it is installed, load IPAs from the
+	 * same build directory as the libcamera library itself. This requires
+	 * identifying the path of the libcamera.so, and referencing a relative
+	 * path for the IPA from that point. We need to recurse one level of
+	 * sub-directories to match the build tree.
+	 */
+	if (!isLibcameraInstalled()) {
+		std::string ipaBuildPath = utils::dirname(libcameraPath()) + "/../ipa";
+		constexpr int maxDepth = 1;
+
+		LOG(IPAManager, Info)
+			<< "libcamera is not installed. Adding '"
+			<< ipaBuildPath << "' to the IPA search path";
+
+		ipaCount += addDir(ipaBuildPath.c_str(), maxDepth);
+	}
+
+	/* Finally try to load IPAs from the installed system path. */
 	ipaCount += addDir(IPA_MODULE_DIR);
 
 	if (!ipaCount)
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 1e5b54b34078..88658ac563f7 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -107,11 +107,17 @@ if get_option('android')
     libcamera_link_with += android_camera_metadata
 endif
 
+# We add '/' to the build_rpath as a 'safe' path to act as a boolean flag.
+# The build_rpath is stripped at install time by meson, so we determine at
+# runtime if the library is running from an installed location by checking
+# for the presence or abscence of the dynamic tag.
+
 libcamera = shared_library('camera',
                            libcamera_sources,
                            install : true,
                            link_with : libcamera_link_with,
                            include_directories : includes,
+                           build_rpath : '/',
                            dependencies : libcamera_deps)
 
 libcamera_dep = declare_dependency(sources : [libcamera_api, libcamera_ipa_api, libcamera_h],
