<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Qdrant on Cloudron - Community Package]]></title><description><![CDATA[<h1>[Packaging Notes] Qdrant 1.18.2</h1>
<h2>Thank You Everybody!</h2>
<p dir="auto">We want to thank everybody in the Cloudron community for your excellent examples and inspiration.<br />
You are amazing. This wouldn't have happened without you!</p>
<p dir="auto">Please take a look in the Docs in the repo, especially the integrations. We have tried to setup this package so that it will work well with other applications on Cloudron. We also wanted it to be a bit easier to maintain.</p>
<p dir="auto">We hope you like it.</p>
<p dir="auto">What do you think of this format for announcing a new Community Package?</p>
<h2>At a glance</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Application</td>
<td>Qdrant</td>
</tr>
<tr>
<td>Upstream repo</td>
<td><a href="https://github.com/qdrant/qdrant" target="_blank" rel="noopener noreferrer nofollow ugc">https://github.com/qdrant/qdrant</a></td>
</tr>
<tr>
<td>Upstream version packaged</td>
<td>1.18.2 (pinned: yes, by sha256 digest)</td>
</tr>
<tr>
<td>Package repo</td>
<td><a href="https://github.com/OrcVole/qdrant-cloudron" target="_blank" rel="noopener noreferrer nofollow ugc">https://github.com/OrcVole/qdrant-cloudron</a></td>
</tr>
<tr>
<td>Packaging type</td>
<td>Community (written to official-track standard)</td>
</tr>
<tr>
<td>Current status</td>
<td>Working</td>
</tr>
<tr>
<td>Cloudron version tested</td>
<td>9.1.x</td>
</tr>
<tr>
<td>Base image</td>
<td>cloudron/base:5.0.0</td>
</tr>
<tr>
<td>Addons used</td>
<td>localstorage, proxyAuth, scheduler</td>
</tr>
<tr>
<td>Process model</td>
<td>single (one binary)</td>
</tr>
<tr>
<td>Memory limit set</td>
<td>2048 MB</td>
</tr>
<tr>
<td>Time invested</td>
<td>about one focused day</td>
</tr>
<tr>
<td>Difficulty</td>
<td>3</td>
</tr>
<tr>
<td>Would package again</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<h2>TL;DR</h2>
<p dir="auto">Qdrant is a vector database written in Rust (similarity search over REST and gRPC, for RAG and<br />
semantic search). Packaging succeeded and is verified live on a box: the SSO topology, a cross<br />
version update, and a real backup then restore into a clean app all pass. The single most important<br />
thing to know: Qdrant serves its web dashboard and its API on the same HTTP port, split only by path,<br />
so scope the <code>proxyAuth</code> wall to the dashboard path only. A whole-domain auth wall would redirect<br />
every API client to a login page and break all integrations.</p>
<h2>Installation</h2>
<p dir="auto">Click on the Add custom app drop down top right in the App Store and choose Community app:<br />
Then paste in the CloudronVersions.json URL into the box that pops up:</p>
<p dir="auto"><img src="/assets/uploads/files/1782552307466-community-package-resized.jpeg" alt="Community Package.jpeg" class=" img-fluid img-markdown" /> <img src="/assets/uploads/files/1782552321764-cloudronversions.jpeg" alt="CloudronVersions.jpeg" class=" img-fluid img-markdown" /></p>
<h2>What worked well</h2>
<ul>
<li>A single dynamically linked binary copied onto <code>cloudron/base</code> with a multi-stage build. The<br />
runtime surface is tiny (libc, libm, libgcc_s, libunwind, liblzma), all present on the base.</li>
<li>Putting 100 percent of state under <code>/app/data</code> made the <code>localstorage</code> backup just work. A real<br />
<code>backup create</code> then <code>clone</code> into a fresh app carried the data, the config, and the API key.</li>
<li><code>proxyAuth</code> with a <code>path</code> restriction cleanly separates the two surfaces (dashboard behind SSO,<br />
API and gRPC left to the app's own key).</li>
<li>Qdrant 1.18's <code>strict_mode.max_resident_memory_percent</code> (a percent of the cgroup limit) lets the<br />
package make Qdrant reject writes and stay alive under memory pressure, rather than be OOM killed.</li>
<li>gRPC over a <code>tcpPort</code> with server reflection enabled means <code>grpcurl</code> and Rust clients (rig-qdrant)<br />
work against it with just the API key.</li>
</ul>
<h2>Gotchas and difficulties</h2>
<h3>Gotcha: manifest validation rejects the proxyAuth addon</h3>
<ul>
<li><strong>Symptom:</strong> <code>cloudron install</code> rejects the manifest at <code>/addons</code> before any build.</li>
<li><strong>Cause:</strong> the proxy-auth addon key is camelCase <code>proxyAuth</code>. The Cloudron packaging skill's addon<br />
reference shows it lowercase <code>proxyauth</code>, which the box rejects.</li>
<li><strong>Fix:</strong> <code>"addons": { "proxyAuth": { "path": "/dashboard", "supportsBearerAuth": true } }</code>.</li>
<li><strong>Routes to:</strong> documentation</li>
<li><strong>Tags:</strong> #manifest #addon-proxyauth #healthcheck</li>
</ul>
<h3>Gotcha: read-only filesystem warning at boot</h3>
<ul>
<li><strong>Symptom:</strong> boot log shows <code>Failed to create init file indicator: .qdrant-initialized: Permission denied</code>.</li>
<li><strong>Cause:</strong> Qdrant writes a marker into its working directory, which by default is the read-only<br />
<code>/app/code</code>.</li>
<li><strong>Fix:</strong> run Qdrant from a writable working directory (<code>/run/qdrant</code>) with the dashboard <code>static</code><br />
dir and the config symlinked in. This also lets you set <code>RUN_MODE=production</code> and link the operator<br />
config as <code>config/production.yaml</code>, which silences a second <code>config/development not found</code> warning.</li>
<li><strong>Routes to:</strong> packaging-only (and a gentle upstream ask: let the marker path follow the data dir)</li>
<li><strong>Tags:</strong> #readonly-fs #permissions</li>
</ul>
<h3>Gotcha: app OOM-killed on the first real collection</h3>
<ul>
<li><strong>Symptom:</strong> the app restarts as soon as a collection is created or grown.</li>
<li><strong>Cause:</strong> the platform default memoryLimit is 256 MB; Qdrant keeps the HNSW graph and vectors in<br />
RAM by default.</li>
<li><strong>Fix:</strong> set <code>memoryLimit</code> to 2 GB and ship <code>strict_mode</code> enabled with<br />
<code>max_resident_memory_percent</code> so writes are rejected rather than the process killed. Document<br />
on-disk vectors and TurboQuant for larger collections. The guard counts heap, not page cache.</li>
<li><strong>Routes to:</strong> packaging-only</li>
<li><strong>Tags:</strong> #memory</li>
</ul>
<h3>Gotcha: /metrics returns 401</h3>
<ul>
<li><strong>Symptom:</strong> a Prometheus scrape of <code>/metrics</code> returns 401.</li>
<li><strong>Cause:</strong> <code>/metrics</code> is behind the API key; only <code>/</code>, <code>/healthz</code>, <code>/livez</code>, <code>/readyz</code> are open.</li>
<li><strong>Fix:</strong> scrape with the API key (a read-only key is enough).</li>
<li><strong>Routes to:</strong> documentation</li>
<li><strong>Tags:</strong> #auth #metrics</li>
</ul>
<h3>Gotcha: gRPC port unreachable behind Cloudflare</h3>
<ul>
<li><strong>Symptom:</strong> a gRPC client times out on the data-plane TCP port.</li>
<li><strong>Cause:</strong> a Cloudflare-proxied (orange-cloud) domain proxies only HTTP, so the raw TCP port does<br />
not pass.</li>
<li><strong>Fix:</strong> use a DNS-only (grey-cloud) record for the host serving the gRPC port. The channel is<br />
plaintext (the tcpPort is not TLS terminated), so use <code>-plaintext</code> and a trusted network.</li>
<li><strong>Routes to:</strong> Cloudron platform / documentation</li>
<li><strong>Tags:</strong> #tcpport #cloudflare #grpc</li>
</ul>
<h3>Gotcha: cloudron clone is interactive and "latest" did not resolve</h3>
<ul>
<li><strong>Symptom:</strong> <code>cloudron clone ... --backup latest</code> in a non-interactive shell dies with<br />
<code>process.stdin.setRawMode is not a function</code>, and even interactively <code>latest</code> returned<br />
<code>Backup not found</code>.</li>
<li><strong>Cause:</strong> clone prompts for a new TCP host port (the source's port is taken), which needs a TTY,<br />
and on a box with several backup sites the <code>latest</code> alias did not resolve.</li>
<li><strong>Fix:</strong> drive it through a pseudo-TTY (<code>script -qefc "cloudron clone ..." /dev/null</code>), feed the<br />
new port, and pass a concrete backup id from <code>cloudron backup list</code> instead of <code>latest</code>.</li>
<li><strong>Routes to:</strong> Cloudron platform (CLI)</li>
<li><strong>Tags:</strong> #cli #backup #clone</li>
</ul>
<h3>Gotcha: live backup of a running datastore is only crash-consistent</h3>
<ul>
<li><strong>Symptom:</strong> none in practice, but a concern: Cloudron copies <code>/app/data</code> live while the app runs.</li>
<li><strong>Cause:</strong> a live copy of an embedded datastore is crash-consistent, not transactionally<br />
consistent. <code>backupCommand</code> runs in a separate temp container and cannot quiesce the live process.</li>
<li><strong>Fix:</strong> rely on Qdrant's write-ahead log (it replays on restore; the empirical backup-restore<br />
cycle passed), and offer an opt-in in-container snapshot cron (scheduler addon, off by default)<br />
that writes a full Qdrant snapshot into the backup for a transactionally consistent artifact.</li>
<li><strong>Routes to:</strong> Cloudron platform / documentation</li>
<li><strong>Tags:</strong> #backup #consistency #wal</li>
</ul>
<h3>Gotcha: /app/data ownership resets across restart and restore</h3>
<ul>
<li><strong>Symptom:</strong> permission errors after a restore or update if ownership is assumed.</li>
<li><strong>Cause:</strong> ownership under <code>/app/data</code> is not preserved across backup and restore.</li>
<li><strong>Fix:</strong> <code>chown -R cloudron:cloudron /app/data</code> at the top of <code>start.sh</code>, every boot, before<br />
touching anything.</li>
<li><strong>Routes to:</strong> documentation</li>
<li><strong>Tags:</strong> #permissions</li>
</ul>
<h3>Gotcha: insecure-by-default upstream settings</h3>
<ul>
<li><strong>Symptom:</strong> none visible, but the defaults are unsafe in a container.</li>
<li><strong>Cause:</strong> Qdrant's API is open by default, telemetry is on, and snapshot recovery from arbitrary<br />
remote URLs is enabled (an SSRF risk).</li>
<li><strong>Fix:</strong> generate API keys on first run and inject them via <code>QDRANT__SERVICE__API_KEY</code> and<br />
<code>QDRANT__SERVICE__READ_ONLY_API_KEY</code>; set <code>jwt_rbac: true</code>; set <code>telemetry_disabled: true</code>; set<br />
<code>service.enable_snapshot_url_recovery: false</code>.</li>
<li><strong>Routes to:</strong> upstream app (secure defaults) / packaging-only</li>
<li><strong>Tags:</strong> #security #ssrf</li>
</ul>
<h3>Gotcha: io_uring under the container seccomp profile</h3>
<ul>
<li><strong>Symptom:</strong> none by default; relevant only if you enable the async scorer.</li>
<li><strong>Cause:</strong> Qdrant can use io_uring for quantized multi-vector rescoring, but Docker's default<br />
seccomp profile commonly restricts io_uring syscalls and there is no confirmed graceful fallback.</li>
<li><strong>Fix:</strong> leave <code>storage.performance.async_scorer</code> off (its default). Treat io_uring as an<br />
opportunistic speedup only, and check the container seccomp posture before enabling it.</li>
<li><strong>Routes to:</strong> Cloudron platform / upstream app</li>
<li><strong>Tags:</strong> #performance #seccomp #io_uring</li>
</ul>
<h2>Cloudron specifics</h2>
<ul>
<li><strong>Filesystem and persistence:</strong> <code>/app/code</code> is read-only, so Qdrant runs from a writable<br />
<code>/run/qdrant</code> working directory with <code>static</code> and <code>config</code> symlinked in. <code>/app/data</code> is the only<br />
persistent path; storage, snapshots, the operator config, and the keys are all forced there with<br />
<code>QDRANT__STORAGE__STORAGE_PATH</code>, <code>QDRANT__STORAGE__SNAPSHOTS_PATH</code>, and a seeded<br />
<code>/app/data/config/production.yaml</code>. No <code>persistentDirs</code> are needed.</li>
<li><strong>Addons:</strong> <code>localstorage</code> for the backup; <code>proxyAuth</code> scoped to <code>path: /dashboard</code> with<br />
<code>supportsBearerAuth</code>; <code>scheduler</code> for an opt-in snapshot task. The proxyAuth key is camelCase.</li>
<li><strong>Healthcheck:</strong> <code>/healthz</code>, which returns 200 as soon as the listener binds and bypasses the API<br />
key. Not <code>/readyz</code>, which returns 503 until shards load and would risk a restart loop during a<br />
large collection load.</li>
<li><strong>Manifest quirks:</strong> one <code>httpPort</code> (6333) carries both the dashboard and the API split by path; a<br />
<code>tcpPort</code> carries gRPC (6334), with a default host port outside the Linux ephemeral range;<br />
<code>memoryLimit</code> 2 GB; <code>optionalSso: true</code>; <code>configurePath: /dashboard</code>.</li>
<li><strong>Build and CLI:</strong> on-server build works with no local Docker; rootless podman works locally. The<br />
image is roughly 2.7 GB because <code>cloudron/base</code> is a full Ubuntu. The icon is not baked into the<br />
image; the CLI uploads <code>logo.png</code> at install or update, and a community (<code>--versions-url</code>) install<br />
takes its icon from <code>iconUrl</code>.</li>
</ul>
<h2>Notes for the Cloudron maintainers</h2>
<p dir="auto">Friendly asks for the platform, from packaging this app:</p>
<ul>
<li><strong><code>iconUrl</code> couples to the <code>minBoxVersion</code> floor.</strong> In a <code>CloudronVersions.json</code>, including <code>iconUrl</code><br />
forces <code>minBoxVersion &gt;= 9.1.0</code>, but <em>omitting</em> <code>iconUrl</code> makes the versions-url install fail<br />
validation (<code>Invalid manifest: iconUrl is missing in manifest</code>). So there is no 8.3.0-compatible<br />
versions-url manifest at all: any community-channel app is pinned to box 9.1.0+ purely by the icon<br />
requirement, even when the app itself runs fine on 8.3. Please either document this coupling, or<br />
decouple the icon from the version floor (accept a versions-url manifest without <code>iconUrl</code> and fall<br />
back to the <code>file://</code> icon, or stop gating <code>iconUrl</code> at 9.1.0). Only the real <code>--versions-url</code> path<br />
surfaces this; on-server <code>cloudron install</code> validates a looser schema and accepts the same manifest<br />
at 8.3.0, so the failure is easy to miss until publish. (This vindicates the reference package's<br />
choice of 9.1.0.)</li>
<li><strong>Document the <code>proxyAuth</code> addon key casing.</strong> It is camelCase <code>proxyAuth</code>; the packaging skill's<br />
addon reference shows it lowercase, which fails manifest validation at <code>/addons</code>.</li>
<li><strong>A pre-backup hook that runs inside the live app container</strong> would let a stateful app quiesce or<br />
snapshot itself before the live <code>/app/data</code> copy. <code>backupCommand</code> runs in a separate temporary<br />
container and cannot reach the running process, so it cannot make a busy datastore's live copy<br />
transactionally consistent.</li>
<li><strong>Clarify <code>proxyAuth</code> path arrays and exclusions.</strong> The docs show a single <code>path</code> string; whether<br />
multiple positive paths or <code>!</code>-exclusions can be combined is undocumented.</li>
</ul>
<h2>Notes for the upstream developers</h2>
<p dir="auto">Friendly asks that would make Qdrant easier to run in any container:</p>
<ul>
<li>Document a minimum glibc version per release. The binary is dynamically linked, and a slim base<br />
image needs to know the floor; a toolchain bump that raises it fails at runtime, not build time.</li>
<li>Provide a single-command, atomic "snapshot the whole storage to a directory" that is safe to run<br />
continuously, so a platform backup can capture a consistent artifact without quiescing the process.</li>
<li>Clarify whether a live filesystem copy (rsync or tar) of the storage directory is restore-safe and<br />
exactly what the write-ahead log guarantees on recovery. This is the crux for managed-backup<br />
platforms.</li>
<li>Consider a readiness signal on the main listener, or clearer health-versus-ready semantics, so a<br />
reverse proxy can health-check without choosing between "restart loops during load" and "reports<br />
healthy before ready".</li>
<li>Default <code>service.enable_snapshot_url_recovery</code> to false; recovering snapshots from arbitrary URLs<br />
is a server-side request forgery risk in a networked container.</li>
</ul>
<h2>Reusable snippets</h2>
<p dir="auto">&lt;details&gt;<br />
&lt;summary&gt;Dockerfile fragment (multi-stage copy plus build-time linkage gate)&lt;/summary&gt;</p>
<pre><code class="language-dockerfile">ARG QDRANT_VERSION=v1.18.2
FROM qdrant/qdrant:v1.18.2@sha256:&lt;digest&gt; AS upstream
FROM cloudron/base:5.0.0@sha256:&lt;digest&gt;

COPY --from=upstream /qdrant/qdrant /app/code/qdrant
COPY --from=upstream /qdrant/static /app/code/static
COPY --from=upstream /qdrant/config/config.yaml /app/code/config/config.yaml
COPY start.sh /app/code/start.sh

# Fail the BUILD if the binary cannot resolve its libs or glibc on this base.
RUN set -eux; ldd /app/code/qdrant; \
    if ldd /app/code/qdrant 2&gt;&amp;1 | grep -qE 'not found'; then exit 1; fi; \
    /app/code/qdrant --version
WORKDIR /app/code
CMD [ "/app/code/start.sh" ]
</code></pre>
<p dir="auto">&lt;/details&gt;</p>
<p dir="auto">&lt;details&gt;<br />
&lt;summary&gt;<a href="http://start.sh" target="_blank" rel="noopener noreferrer nofollow ugc">start.sh</a> fragment (ownership, key generation, run from a writable workdir)&lt;/summary&gt;</p>
<pre><code class="language-bash">#!/bin/bash
set -euo pipefail
chown -R cloudron:cloudron /app/data            # ownership is not preserved across restore

# generate keys once, inject via env (env overrides Qdrant's config files)
[ -f /app/data/.secrets/keys.env ] || { mkdir -p /app/data/.secrets; \
  printf 'QDRANT_ADMIN_API_KEY=%s\n' "$(openssl rand -hex 32)" &gt; /app/data/.secrets/keys.env; }
. /app/data/.secrets/keys.env
export QDRANT__SERVICE__API_KEY="$QDRANT_ADMIN_API_KEY"
export QDRANT__STORAGE__STORAGE_PATH=/app/data/storage

# run from a writable cwd so Qdrant's marker file does not hit the read-only /app/code
mkdir -p /run/qdrant/config
ln -sfn /app/code/static /run/qdrant/static
ln -sf /app/data/config/production.yaml /run/qdrant/config/production.yaml
chown cloudron:cloudron /run/qdrant /run/qdrant/config
export RUN_MODE=production
cd /run/qdrant
exec gosu cloudron:cloudron /app/code/qdrant
</code></pre>
<p dir="auto">&lt;/details&gt;</p>
<h2>Open questions</h2>
<ul>
<li>The best practice for a transactionally consistent live backup of a running embedded datastore on<br />
Cloudron. A pre-backup hook that ran inside the live app container (rather than the separate temp<br />
container that <code>backupCommand</code> uses) would let the app quiesce or snapshot itself first.</li>
<li>Whether <code>proxyAuth</code> supports an array of paths or path exclusions in one app; the docs show a<br />
single string. A single positive path was enough here.</li>
</ul>
<h2>References</h2>
<ul>
<li>Package source: <a href="https://github.com/OrcVole/qdrant-cloudron" target="_blank" rel="noopener noreferrer nofollow ugc">https://github.com/OrcVole/qdrant-cloudron</a></li>
<li>Upstream docs: <a href="https://qdrant.tech/documentation/" target="_blank" rel="noopener noreferrer nofollow ugc">https://qdrant.tech/documentation/</a> and the repo <code>config/config.yaml</code></li>
<li>Cloudron packaging docs: <a href="https://docs.cloudron.io/packaging/" target="_blank" rel="noopener noreferrer nofollow ugc">https://docs.cloudron.io/packaging/</a></li>
</ul>
<h2>Verdict</h2>
<p dir="auto">Official-app candidate. It is a clean single-binary multi-stage build on the current base, with<br />
correct read-only filesystem handling, a working unauthenticated health check, instant usability with<br />
no setup screen, secure-by-default settings, a complete manifest with the official icon, and full<br />
documentation, including verified upgrade and backup gates. Ship it on the community channel now; it<br />
is written to the standard the official track expects.</p>
<hr />
<p dir="auto">#packaging-notes #qdrant #cloudron-9.1 #status-working</p>
]]></description><link>https://forum.cloudron.io/topic/15645/qdrant-on-cloudron-community-package</link><generator>RSS for Node</generator><lastBuildDate>Fri, 03 Jul 2026 02:22:27 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/15645.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 25 Jun 2026 17:41:02 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Qdrant on Cloudron - Community Package on Fri, 26 Jun 2026 00:11:35 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/timconsidine" aria-label="Profile: timconsidine">@<bdi>timconsidine</bdi></a> The one who asks <img src="https://forum.cloudron.io/assets/plugins/nodebb-plugin-emoji/emoji/android/1f609.png?v=8b6d81684d0" class="not-responsive emoji emoji-android emoji--wink" style="height:23px;width:auto;vertical-align:middle" title=";-)" alt="😉" /></p>
]]></description><link>https://forum.cloudron.io/post/126136</link><guid isPermaLink="true">https://forum.cloudron.io/post/126136</guid><dc:creator><![CDATA[robi]]></dc:creator><pubDate>Fri, 26 Jun 2026 00:11:35 GMT</pubDate></item><item><title><![CDATA[Reply to Qdrant on Cloudron - Community Package on Thu, 25 Jun 2026 18:54:01 GMT]]></title><description><![CDATA[<p dir="auto">Who is Friendly ?</p>
]]></description><link>https://forum.cloudron.io/post/126128</link><guid isPermaLink="true">https://forum.cloudron.io/post/126128</guid><dc:creator><![CDATA[timconsidine]]></dc:creator><pubDate>Thu, 25 Jun 2026 18:54:01 GMT</pubDate></item></channel></rss>