<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Series-Yacy on ln --help</title>
    <link>https://blog.mei-home.net/tags/series-yacy/</link>
    <description>Recent content in Series-Yacy on ln --help</description>
    <generator>Hugo -- 0.152.2</generator>
    <language>en</language>
    <lastBuildDate>Sun, 21 Jun 2026 23:15:01 +0200</lastBuildDate>
    <atom:link href="https://blog.mei-home.net/tags/series-yacy/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Yacy Part 2: Crawling and Putting it on the Backburner</title>
      <link>https://blog.mei-home.net/posts/yacy-part-2-crawling-backburner/</link>
      <pubDate>Sun, 21 Jun 2026 23:15:01 +0200</pubDate>
      <guid>https://blog.mei-home.net/posts/yacy-part-2-crawling-backburner/</guid>
      <description>It has gotten to be a bit more than a small weekend project</description>
      <content:encoded><![CDATA[<p>This is the second post in my <a href="https://blog.mei-home.net/tags/series-yacy/">series</a> on the
YaCy distributed, self-hosted search engine.
The main topic this time is getting pages into the search index via crawling.</p>
<p>In contrast to search engines like Google, Bing or Kagi, the content of the
search index in YaCy is driven by its users. YaCy has an integrated web crawler
to crawl pages and add them to the search index. It can be invoked in one of
three ways:</p>
<ol>
<li>Manually via a web interface in YaCy</li>
<li>Automatically via scheduled (re-) crawls</li>
<li>Automatically by using YaCy as a local web proxy</li>
</ol>
<p>I will concentrate on the first way in this post. The second way, with scheduled
repeats of a previous manual crawl will be described later, when I get into properly
setting up and using YaCy longer term.</p>
<p>The third way is somewhat interesting. It avoids having to manually crawl
interesting pages by configuring YaCy as a local web proxy and using it for your
normal internet browsing. YaCy will then automatically launch crawls for any
pages you visit. I find that a little bit too Big Brother, even though it&rsquo;s all
running in my own Homelab and won&rsquo;t use this approach.</p>
<p>The manual crawl start page looks like this:
<figure>
    <img loading="lazy" src="yacy-crawling-page.png"
         alt="A screenshot of YaCy&#39;s crawler launch page. It is headed &#39;Expert Crawl Start&#39;. Below that follow a lot of input fields for the many options. First comes the &#39;Start Point&#39; section. It has multiple radio buttons to configure the starting point of the crawl in different ways. The first one is &#39;One Start URL or a list of URLs&#39;, which is a text area for entering one or more URLs. Next comes &#39;From Link-List of URL&#39;, then &#39;From Sitemap&#39; and finally &#39;From File (enter a path within your local file system)&#39;. The next section configures the search index any results will be added to, headed &#39;Index Attributes&#39;. The first option is the &#39;Add Crawl result to collection&#39;, with an input field which is pre-filled with the default &#39;user&#39; collection. Next comes the &#39;Time Zone Offset&#39;, which is pre-filled with &#39;-120&#39; in my case. Next comes the &#39;Crawl Filter&#39; section, controlling the crawler&#39;s crawling behavior. First, there are checkboxes for enabling/disabling indexing of text and media. Then comes a checkbox for enabling/disabling remote indexing, followed by the &#39;Crawling Depth&#39; config, pre-filled by the default of 3. Then comes an &#39;Unlimited crawl depth for URLs matching with&#39; text field, followed by a checkbox for enabling restrictions on page count per domain to be indexed. Then come a few more check boxes, &#39;Accept URLs with query-part (?)&#39;, &#39;obey html-robots-noindex&#39; and &#39;Obey html-robots-nofollow&#39;. Then comes the &#39;Media Type detection&#39; radio button selector, with options &#39;Do not load URLs with unsupported file extension&#39; and &#39;Always cross check file extension against Content-Type header&#39;. Next follow a few options for &#39;Load Filter on URLs&#39;. It starts with three radio buttons, &#39;Restrict to start domain(s)&#39;, &#39;Restrict to sub-path(s)&#39; and &#39;Use filter&#39;, with the last one having a text field for entering the filter. Finally, there is another text box labeled &#39;must-not-match&#39;. Then come two more options for filtering, &#39;Load Filter on URL origin of links&#39; and &#39;Load Filter on IPs&#39;. Both have two text input fields, one labeled &#39;must-match&#39; and one labeled &#39;must-not-match&#39;. Last in this section is the &#39;Must-Match List for Country Codes&#39; radio button group, with options &#39;no country code restriction&#39; and &#39;Use filter&#39;, where &#39;Use filter&#39; allows setting a list of country codes to allow. The next section is headed &#39;Document Filter&#39;, and contains additional options for filtering which documents are actually added to the index. There are four filter groups, &#39;Filter on URLs&#39;, &#39;Filter on Content of Document&#39;, &#39;Filter on Document Media Type&#39; and &#39;Solr query filter on any active indexed fields&#39;. Each of those groups has two input text fields, labeled &#39;must-match&#39; and &#39;must-not-match&#39;. The next one is the &#39;Content Filter&#39; section. This section has two radio buttons, &#39;Evaluate by default&#39; and &#39;Ignore by default&#39;, with a final text field labeled &#39;Filter div or nav class names&#39;. Next comes the &#39;Clean-Up before Crawl Start&#39; section. It starts with a &#39;Clean up search events cache&#39; checkbox. Then follow three radio buttons, &#39;No deletion&#39;, &#39;Delete sub-path&#39; and &#39;Delete only old&#39;. The &#39;Delete only old&#39; options contains two dropdowns, one for choosing a number and the other for choosing the unit, e.g. &#39;days&#39; or &#39;weeks&#39;. Next up, the &#39;Double-Check Rules&#39; section, with the radio buttons to chose either &#39;No doubles&#39; or &#39;Re-load&#39;, with the latter again having drop downs to chose the number and unit. Then there&#39;s the &#39;Document Cache&#39; section, having a checkbox for &#39;Store to Web Cache&#39; and a radio button group for &#39;Policy for usage of Web Cache&#39;, with options &#39;no cache&#39;, &#39;if fresh&#39;, &#39;if exist&#39; and &#39;cache only&#39;. Then comes the &#39;Robot Behaviour&#39;, having only one dropdown option labeled &#39;Use Special User Agent and robot identification&#39;. And finally, there&#39;s the &#39;Snapshot Creation&#39; section. It starts with a &#39;Max Depth for Snapshots&#39; input field, followed by a &#39;Multiple Snapshot Versions&#39; radio button group with options &#39;replace old snapshots with new one&#39; and &#39;add new versions for each crawl&#39;. Then there&#39;s a text input field labeled &#39;must-not-match filter for snapshot generation&#39; and finally a &#39;Image Creation&#39; checkbox. At the very end is then the &#39;Start New Crawl Job&#39; button. Is this kind of description actually useful? I always want to just provide a rough overview, but that always feels like withholding information from my vision impaired readers."/> <figcaption>
            <p>YaCy&rsquo;s crawl launch page</p>
        </figcaption>
</figure>
</p>
<p>That&rsquo;s certainly a lot of options. Let&rsquo;s go through the most important ones.
The first setting to configure is the starting point. This is a list of URLs, which
can also contain paths to restrict the crawl to sub-paths. Once a URL is
provided, YaCy will reach out to the page and see whether it contains a Sitemap.
If so, it will allow using said Sitemap to start the crawl. Finally, the crawl
can also be started from a file containing a list of URLs.</p>
<p>The index attributes are not too interesting, I just always used the default
index.</p>
<p>The crawl filter section is where it really gets interesting. The configuration
of the crawl depth is quite important, as it sets how deep the crawl will actually
go. I will talk more about it later. I never used the page count configs, but
I guess it&rsquo;s a tool to get out of some sort of endless loop? The constraints will
be their own topic later on. The load filter for URLs is a lot more interesting.
It can either restrict to the initial domains or even sub-paths of the initial
list of URLs. Instead, any regex can also be used to manually restrict the URLs.
More on this also later on.</p>
<p>I never touched the &lsquo;Document Filter&rsquo; at all, never saw a reason to. Same for
the content filter.</p>
<p>The &lsquo;Clean-Up before Crawl Start&rsquo; is more interesting. It takes the crawl start
URLs and can delete all pages for those domains or sub-paths from the index. This
also makes sure that they&rsquo;re all re-crawled. Instead of deleting the content
outright, the &lsquo;Double-Check Rules&rsquo; section can be used to re-crawl only pages
of a certain age.</p>
<p>I never did anything with the &lsquo;Document Cache&rsquo; up to now. And the Robot Behaviour
will be discussed in the next section.</p>
<p>So let&rsquo;s dive a bit deeper into what happens when we click the &lsquo;Start New Crawl Job&rsquo;
button.</p>
<h2 id="crawling-basics">Crawling Basics</h2>
<p>To start with: The crawler implementation of YaCy has a default delay between
crawling the same domain of 250 ms, meaning it will produce at most 4 req/s and
240 req/min. This value can be increased by crawling multiple sites in parallel,
because the restriction is only per domain, not for all crawling.</p>
<p>Another thing I find a little bit sad is that by default, YaCy uses random
browser user agents. But there is an option to change that in the &ldquo;Robot Behaviour&rdquo;
section. It does respect the <code>robots.txt</code>, including the <code>Crawl-delay</code> directive.</p>
<p>As the existence of the &ldquo;Double-Check Rules&rdquo; section indicates, YaCy does not
normally visit pages which it already has in its index again. This can be worked
around in two ways. The first is to delete all pages for the domain or path
given in the crawl URL list. The second way is via re-crawling only pages which
have last been crawled a certain time ago.</p>
<p>During my own crawls up to this point, I always decided to only crawl within
a certain domain or even just a certain subpath. I don&rsquo;t see too much usefulness
in doing wide Internet crawls. For the few times I tried that, e.g. for this
blog, there were a lot of random pages added to the index pretty fast. To make
a per-domain crawl, I would advise not to use <code>Restrict to start domains</code> option
directly. That&rsquo;s because that option will also exclude subdomains, which I at
least always want to include in the crawl. For example, when crawling <code>ceph.io</code>
I would definitely also want to crawl <code>docs.ceph.io</code>. Instead, I use the
<code>Use filter</code> option, with a filter of <code>^(.*\.)?ceph\.io.*$</code>. You might be
wondering about the <code>(.*\.)?</code> at the beginning. This is to make sure that only
subdomains are caught, not variations on the domain. I came up with this when
I wanted to crawl the homepage of my old University town, <code>paderborn.de</code>. I first
launched it with a filter of <code>.*paderborn.de</code>. But this also caught stuff like
<code>company-paderborn.de</code>, which I didn&rsquo;t want.</p>
<p>When it comes to the crawling depth, I always use <code>.*</code> in the &ldquo;Unlimited crawl depth for URLs matching with&rdquo;
field. Through the use of the filter, I already ensured that I will only get
results from the one domain I want to crawl, so just using <code>.*</code> here is fine
because I really want to crawl the entire page. One important thing regarding
the crawl depth I still have to look up in the code is whether YaCy somehow
stores the depth for pages which have already been crawled. As I&rsquo;ve noted above,
YaCy does normally not re-crawl pages it has already seen. But this would of
course be unfortunate if the same page is later visited with a greater crawl
depth, but then gets ignored because it was already seen.</p>
<p>Another point I learned pretty quickly: I have to keep an eye on the crawl and
the URLs it visits. One example is GitHub, where my initial crawls happily
crawled the <code>/commits</code>, <code>/blobs</code> and <code>/tree</code> endpoints, resulting in a very
large amount of pages in the index which I really wasn&rsquo;t interested in. Another
example is <code>faz.net</code>, a German newspaper. Their page has the subpath <code>/kaufkompass</code>.
It contains product pages for hundreds of thousands of products, and is again not
something I&rsquo;m even remotely interested in crawling. Yet another example was the
homepage of the city I&rsquo;m living it. It has an event calendar going back over a
decade. Also lots and lots of pages to crawl. So it&rsquo;s always worth it to click
around a bit on any given page before launching the crawl, to see whether there&rsquo;s
something to exclude.</p>
<p>Let&rsquo;s next take a deeper look at two additional examples.</p>
<h2 id="crawling-a-github-project">Crawling a GitHub project</h2>
<p>One of the places my Google searches lead me quite often are the issues and
pull request pages of GitHub, especially when searching for error messages.
So I wanted to crawl the GitHub pages of all of the projects I&rsquo;m using, for
example everything under <a href="https://github.com/yacy/yacy_search_server">https://github.com/yacy/yacy_search_server</a>.
As I&rsquo;ve noted above, this needs some exclusions to avoid crawling the Git repository.
In my case, what worked quite nicely was adding <code>.*/(tree|commits|blob|changes|actions|commit)/.*</code>
as a <code>must-not-match</code> URL filter. One issue I came across while trying to
crawl GitHub was that the pagination system has the pages as query parameters.
This is a problem because I normally disable URLs with query parameters, as they
generally represent dynamic content. But in the case of pagination for e.g.
issues or pull requests, I do of course want the crawler to follow them. So I
enabled the <code>Accept URLs with query-part</code> checkbox. This worked as expected, but
now I had a new problem: The crawler started fetching all possible filter permutations
for the issue and pull request filters, for example <code>https://github.com/yacy/yacy_search_server/issues?q=is%3Aissue%20state%3Aopen%20author%3Apdstefan%20label%3Aindex</code>.</p>
<p>These end up in the crawl queue because the links to the different filters are
delivered by GitHub when opening the issues or PR pages. I will want to work
around this, but haven&rsquo;t had the time to come up with a proper regex yet. Another
thing I need to check is whether those links might have the <code>nofollow</code> tag, which
I&rsquo;ve currently got the crawler ignoring.</p>
<p>Then there&rsquo;s the question whether doing crawling via the GitHub API might not
be &ldquo;nicer&rdquo; to GitHub than fetching the actual pages and listing all the time.
Of course, I really couldn&rsquo;t care less how much my crawling costs the LLM pushers
from Microsoft GitHub. But then again, your ethics are determined by how you
treat your enemies, not by how you treat your friends.
This leads directly to the next question: How to handle this? I&rsquo;m assuming that
GitHub might not be the only page which might be easier to crawl via its API.
But I also don&rsquo;t think it&rsquo;s really worth it to implement all sorts of special
cases into YaCy&rsquo;s crawler directly. This is something which obviously calls for
some sort of plugin model. So I will have to figure out how to feed already
crawled pages into YaCy&rsquo;s index via an API. Or perhaps to introduce a YaCy API
for crawling plugins.</p>
<h2 id="respecting-http-code-429">Respecting HTTP code 429</h2>
<p>Another target I tried to crawl was <a href="https://pkg.go.dev/">pkg.go.dev</a>, because
I&rsquo;m currently <a href="https://blog.mei-home.net/tags/smoking/">working on a Go webapp</a> and I find
myself searching for Go packages and functions quite often. During this crawl,
I was surprisingly getting back HTTP code 429, too many requests. Of course as
I mentioned above, I want to be a well-behaved crawler. So I first verified again
whether the YaCy crawler was properly crawling at, at most, a rate of 4 req/s.
And it was. It just seems that <code>pkg.go.dev</code> has quite a low ceiling for engaging
its rate limiters.</p>
<p>To my surprise, instead of simply slowing down, YaCy just retried the error&rsquo;ing
pages a few more times and then just gave up on them, meaning I was missing some
pages and getting others.</p>
<p>This lead me to spelunking through the YaCy code again, and finding that
it just didn&rsquo;t have any special 429 return code handling. At all. Here is what
I believe to be the relevant code, see <a href="https://github.com/yacy/yacy_search_server/blob/master/source/net/yacy/crawler/retrieval/HTTPLoader.java#L159-L263">the code on GitHub</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (statusCode <span style="color:#f92672">&gt;</span> 299 <span style="color:#f92672">&amp;&amp;</span> statusCode <span style="color:#f92672">&lt;</span> 310) {
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> (statusCode <span style="color:#f92672">==</span> HttpStatus.<span style="color:#a6e22e">SC_OK</span> <span style="color:#f92672">||</span> statusCode <span style="color:#f92672">==</span> HttpStatus.<span style="color:#a6e22e">SC_NON_AUTHORITATIVE_INFORMATION</span>) {
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// client.close(); // explicit close caused: warning: [try] explicit call to close() on an auto-closeable resource</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// if the response has not the right response type then reject file</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">sb</span>.<span style="color:#a6e22e">crawlQueues</span>.<span style="color:#a6e22e">errorURL</span>.<span style="color:#a6e22e">push</span>(request.<span style="color:#a6e22e">url</span>(), request.<span style="color:#a6e22e">depth</span>(), profile,
</span></span><span style="display:flex;"><span>            FailCategory.<span style="color:#a6e22e">TEMPORARY_NETWORK_FAILURE</span>, <span style="color:#e6db74">&#34;wrong http status code&#34;</span>, statusCode);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> IOException(<span style="color:#e6db74">&#34;REJECTED WRONG STATUS TYPE &#39;&#34;</span> <span style="color:#f92672">+</span> statusline
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;&#39; for URL &#39;&#34;</span> <span style="color:#f92672">+</span> requestURLString <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;&#39;$&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>So this behavior is imminently fixable by implementing exponential back-off once
429 return codes start showing up.</p>
<p>Special thanks also to <a href="https://mastodon.social/@remyzandwijk">Remy on mastodon.social</a>,
who pointed out the existence of the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After">Retry-After</a>
header. Sadly, <code>pkg.go.dev</code> does not send along that particular header with the
429 responses.</p>
<h2 id="thoughts-on-launching-crawls">Thoughts on launching crawls</h2>
<p>As I&rsquo;ve noted in the introduction, there are a few ways to launch a crawl. The
main problem I see is that they&rsquo;re all not too great for daily, long-term use.</p>
<p>Sure, I can launch crawls for all of the pages I regularly visit and fill the
index up front. But what about re-crawls, e.g. for new versions of documentations, or
catching the content of new issues in GitHub projects? How to make sure that I
don&rsquo;t have to somehow remember all the configurations for all of the pages?</p>
<p>YaCy does have a page for repeating previous crawls, and it supposedly also can
create and edit crawling profiles, but I at least haven&rsquo;t been able to actually
make that work. Just for the reason that I haven&rsquo;t found the button for actually
creating a profile yet. &#x1f937;</p>
<p>Beyond that, what I would really like is a way to trigger crawl with a specific
profile for the page I&rsquo;ve currently open in a browser. Because having to stop
reading a page and switching to YaCy to trigger a crawl would probably get old
pretty fast. So this is another thing I&rsquo;d like to look into: First, making sure
I can properly create and manage crawling profiles, and then perhaps introducing
a browser extension from which to trigger a crawl for the current page, allowing
me to chose the profile.</p>
<p>Another interesting feature to look into regarding automating (re-)crawls would
be YaCy&rsquo;s RSS import feature. It can seemingly ingest an RSS feed and then
regularly fetch it and crawl all new entries. This could be very nice to not have
to regularly re-crawl entire blogs, and would also be pretty nice for news sites.
And it might even partially work for GitHub issues and PRs, as I think there are
RSS feeds available for those as well. The main problem here would be that one
would want to re-crawl issues from time to time to catch newer comments.</p>
<h2 id="some-general-thoughts-on-crawling">Some general thoughts on crawling</h2>
<p>It&rsquo;s not the early 2010s anymore. The Internet has become a lot more hostile to
anything self-hosted since the advent of LLMs. Personal sites and open source projects
alike are getting buried under the indiscriminate incompetence of the LLM bros
with more money than brains, sending their scrapers to download the exact same
site 1000x times in a row, or traversing the entirety of a Git repository via
HTTP - multiple times per minute. As a consequence, many people and projects
have started erecting barriers, reaching from CloudFlare to projects like
Iocaine or Anubis to block crawlers. But of course the same blockers also
work perfectly fine against my crawling.</p>
<p>So even the general viability of any search engine which doesn&rsquo;t have the heft
of Google or Microsoft behind it seems pretty questionable now.</p>
<p>Besides that, I&rsquo;m also wondering about how YaCy handles the distributed nature
of the crawling. Let&rsquo;s take <a href="https://codeberg.org/">codeberg.org</a> as an example.
Considering the type of people who would be interested in a distributed search
engine, I&rsquo;d judge the probability that multiple of us will try to crawl codeberg
reasonably high. But this seems patently unnecessary, as we would just add the
same pages twice to the global index. While at the same time adding undue load
on the infrastructure of a non-profit doing good things in providing an alternative
to GitHub.
Probably not an issue with the currently very low number of running YaCy instances.
But still something to think about if it ever gets more popular.</p>
<h2 id="backburner">Backburner</h2>
<p>As you can see from all of the ideas for improvements from above, there&rsquo;s a lot
to potentially do to make YaCy more useful. But especially the receding willingness
to allow crawling by random crawlers has given me pause. Plus, I would have to
invest a lot more time and make more contributions to YaCy&rsquo;s code. Which is fine,
but introducing YaCy to my stack was only supposed to be a short break from my
<a href="https://blog.mei-home.net/tags/smoking/">Smokeweb</a> project.</p>
<p>So for now, I will put the project on the backburner, and return to it once I&rsquo;ve
finished the majority of my work on Smokeweb.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Yacy Part 1: Deployment</title>
      <link>https://blog.mei-home.net/posts/yacy-part-1-deployment/</link>
      <pubDate>Sun, 07 Jun 2026 00:25:36 +0200</pubDate>
      <guid>https://blog.mei-home.net/posts/yacy-part-1-deployment/</guid>
      <description>Deploying the YaCy search engine</description>
      <content:encoded><![CDATA[<p>Welcome to the newest rabbit hole I&rsquo;ve found myself in. This post starts a new
series where I&rsquo;m taking a look at the <a href="https://yacy.net/">YaCy</a>
self-hosted, distributed peer to peer search engine. And probably web crawling
and search ranking algorithms.</p>
<p>In this post, I will concentrate on how I deployed YaCy into my Kubernetes
cluster, and a few pieces about my first steps with it. You won&rsquo;t find answers
to questions like &ldquo;how good is it as a Google replacement?&rdquo; in this post. There&rsquo;s
a lot more work ahead for me to actually make that judgment.</p>
<p>You can find this post and any future ones in the series under the <a href="https://blog.mei-home.net/tags/series-yacy/">YaCy tag</a>.</p>
<p>A fair warning before I continue: The project accepts slopcoded contributions.</p>
<p>It currently doesn&rsquo;t look like there&rsquo;s a large team behind it, but there&rsquo;s a
<a href="https://community.searchlab.eu/">community forum</a> with some activity, although
new signups are currently broken due to an issue with the mail server.
The last release was in March, and PRs are regularly getting reviewed and merged.</p>
<h2 id="what-is-yacy">What is YaCy?</h2>
<p>While I&rsquo;ve said this post is mostly about deployment, it&rsquo;s probably a good idea
to tell you all a bit about what YaCy is, so you know whether you actually want
to read on.</p>
<p>YaCy is a self-hosted, peer to peer search engine. It has entirely its own index
and does not rely on the likes of Bing or Google. On its main page, it presents
a simple search mask:</p>
<figure>
    <img loading="lazy" src="search-root.png"
         alt="A screenshot of YaCy&#39;s homepage. In the middle, it shows the YaCy logo, and beneath it an individualized greeting reading &#39;Meiers Search&#39;, which I gave it. Below that is the search mask, with an input field and a search button next to it. Below it are two radio buttons, one for text, one for images, and a link to &#39;more options&#39;. In the top left corner of the page is a login button. In the top right button is a dropdown called &#39;Search Interfaces&#39;, a button with a question mark containing links to the docs and bug tracker and finally a button labeled &#39;Administration&#39; which gets us to the admin pages."/> <figcaption>
            <p>YaCy&rsquo;s main search page.</p>
        </figcaption>
</figure>

<p>When searching, the results page should also be pretty familiar:
<figure>
    <img loading="lazy" src="search-example.png"
         alt="A screenshot of YaCy&#39;s search results page. At the very top is search field, showing the query &#39;migrating from nomad to k8s&#39;, with a &#39;search again&#39; button next to it. On the left side is a separate area with options for the search. At the top, the user can switch between &#39;Peer-to-Peer&#39; and &#39;Privacy&#39;, and below it are a few options for the search ranking. Current selected are &#39;Context Ranking&#39; and &#39;Documents&#39;. Other options are &#39;Sort by Date&#39;, &#39;Images&#39;, and a choice to filter for only &#39;http&#39; or only &#39;https&#39; pages. Below that is a small word cloud, containing related words which don&#39;t show up in the search query yet but are related, like &#39;github&#39;, &#39;ubuntu&#39; or &#39;cncf&#39;. Then follow some more filtering options in dropdowns. The first one is &#39;Domain&#39;, which allows filtering by specific domains to search. Next is &#39;Authors&#39;, then &#39;Filetype&#39; and finally Language. Next back to the main area, which at the top contains some general infos about the search. It shows 178k results for the search, with 178k from local and 74 from remote sources, specifically 13 YaCy peers. Then there&#39;s finally the search results themselves. They very much look like Google many, many years ago. First comes the title of the page, followed by the full link. Then comes the last modified date of the page and a link to citations. Only ten results are shown on the page, but at the bottom are buttons to show the next pages of results. The results themselves mostly show posts to this blog&#39;s Nomad to k8s series, see link in the main text. Besides that are also articles from dev.to about migrating applications from VMs to k8s as well as an Ubuntu docs page with the title &#39;Migrating From The Livepatch Machine Charm to the K8s Charm&#39;."/> <figcaption>
            <p>YaCy search result example</p>
        </figcaption>
</figure>
</p>
<p>This result was a bit unexpected right now. I hadn&rsquo;t actually crawled my own blog
yet, and it still found my posts. Looks like somebody has been pointing a YaCy
crawl at it at some point. So this is actually what I would call a good-ish search
result. It mostly found a series of blog posts about exactly what I was interested
in - <a href="https://blog.mei-home.net/tags/k8s-migration/">migrating from Nomad to Kubernetes</a>,
plus a few other results also related to migrating from something to Kubernetes.</p>
<p>The way YaCy works is that there is an index held locally in an embedded
<a href="https://solr.apache.org/">Apache Solr</a> instance, which is also used for searching.
This search index is filled by the instance&rsquo;s own crawling of websites. I will
go into more detail on crawling in a future post. By the way, if you&rsquo;ve got any
good blog posts or articles which explain how web crawling works these days, what
to look out for and how to behave properly, I&rsquo;d be very happy to hear about them,
for example <a href="@mmeier@social.mei-home.net">via the Fediverse</a>.</p>
<p>The P2P aspect of YaCy is used in two different ways. The first one is during
searching. In the screenshot above, on the left side, you can choose between
&lsquo;Peer-to-Peer&rsquo; or &lsquo;Privacy&rsquo; mode. Privacy mode here means to only search the local
instance&rsquo;s index. The Peer-to-Peer mode searches the local index and goes out to
other instances to do a remote search. The second way is via a constant gossip
protocol which exchanges pieces of the local index with other instances, both
sending and receiving. This is always ongoing in the background, without user
intervention. This way, you will end up with a lot more entries in your local
index than just what you yourself crawled, and the remote search adds to that
on top.</p>
<p>I&rsquo;m also of a mind to look into this a bit more deeply, because the official docs
and what exactly is exchanged is not too detailed, and I want to look at the code
a bit more.</p>
<p>Let&rsquo;s end this section with a bit of a general vibe: The project does work. I
do get search results from pages I&rsquo;ve crawled myself, and I&rsquo;m also getting results
for pages which I definitely have not crawled myself. The network is active,
showing about 600 peers seen over the past week, and I&rsquo;m getting quite a few
remote searches in. I&rsquo;ve also had some success with a few of my searches. The
example above was a pleasant surprise, getting served my own blog for a relevant
search query. But there have also been other queries which were not too useful.
I&rsquo;ve for example just done a quick search for <a href="https://cloudnative-pg.io/">CloudNativePG</a>.
This did show CNPG&rsquo;s GitHub page, but the home page was not in the index at all.</p>
<p>There are in the main two areas I will want to research more deeply. One being
crawling. Most important to me is to make sure that YaCy&rsquo;s crawler really
respects all the rules around web crawling, like respecting robots.txt and
keeping the per-site request rates low. I think it already does that, but I will
need some testing. Then there&rsquo;s the question of what to crawl? How deep to crawl?
What&rsquo;s the right way to get a breadth-first crawl going, instead of just indexing
pages I already know? But without filling the index with too much garbage?</p>
<p>Then there&rsquo;s search ranking. It doesn&rsquo;t come out in the example search from
above, but the ranking is really not great sometimes. But it&rsquo;s also highly configurable.
And there is an extended ranking called CitationRank, similar to Google&rsquo;s
PageRank. I really want to dig into that and how it&rsquo;s implemented.</p>
<p>One nice thing to note: YaCy implements the necessary APIs to be used as a
search provider in Firefox.</p>
<h2 id="deploying-yacy">Deploying YaCy</h2>
<p>YaCy is a Java application and comes with multiple ways of deploying it, both
with and without Docker. It has a few warts, though.</p>
<p>Before I get to my Kubernetes deployment, a quick note: You can also run it
locally on your own desktop machine. It works perfectly nice there, even without
an externally open port. You won&rsquo;t be fully participating in the P2P network,
but you will be able to do remote searches. And when you&rsquo;re triggering crawls,
your resulting index will even be shared with other peers. But your instance
won&rsquo;t be serving other peer&rsquo;s remote searches, and you won&rsquo;t be able to receive
index updates from other peers via the background gossip protocol.</p>
<p>Let&rsquo;s start with the Docker images. At the moment, the newest versioned releases
for the Docker image <a href="https://hub.docker.com/r/yacy/yacy_search_server/tags">on Dockerhub</a>
are from 12 months ago, even though there was a YaCy release in March. The only
current images are in the <code>latest</code> tag, which I don&rsquo;t really like. So my first
step was building the YaCy image myself. Here is the Containerfile:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#75715e">## builder image</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ARG</span> alpine_ver<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ARG</span> jdk_ver<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ARG</span> wkhtmltopdf_ver<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">eclipse-temurin:${jdk_ver}-jdk-alpine-${alpine_ver</span><span style="color:#f92672">}</span> AS builder<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Install needed packages not in base image</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apk add --no-cache curl git apache-ant<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># set current working dir &amp; copy sources</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span> <span style="color:#e6db74">/opt</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> . /opt/yacy_search_server/<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> ant compile -f /opt/yacy_search_server/build.xml <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> rm -fr /opt/yacy_search_server/.git<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Set initial admin password: &#34;yacy&#34; (encoded with custom yacy md5 function net.yacy.cora.order.Digest.encodeMD5Hex())</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> sed -i <span style="color:#e6db74">&#34;/adminAccountBase64MD5=/c\adminAccountBase64MD5=MD5:8cffbc0d66567a0987a4aba1ec46d63c&#34;</span> /opt/yacy_search_server/defaults/yacy.init <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	sed -i <span style="color:#e6db74">&#34;/adminAccountForLocalhost=/c\adminAccountForLocalhost=false&#34;</span> /opt/yacy_search_server/defaults/yacy.init <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	sed -i <span style="color:#e6db74">&#34;/server.https=false/c\server.https=true&#34;</span> /opt/yacy_search_server/defaults/yacy.init<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e">## build final image</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">surnet/alpine-wkhtmltopdf:${wkhtmltopdf_ver</span><span style="color:#f92672">}</span> AS wkhtmltopdf<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">eclipse-temurin:${jdk_ver}-jre-alpine-${alpine_ver</span><span style="color:#f92672">}</span> AS app<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apk add --no-cache <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	imagemagick <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	xvfb <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	ghostscript <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#75715e"># Install dependencies for wkhtmltopdf</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>	libstdc++ <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	libx11 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	libxrender <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	libxext <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	libssl3 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	ca-certificates <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	fontconfig <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	freetype <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	ttf-dejavu <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	ttf-droid <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	ttf-freefont <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	ttf-liberation <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#75715e"># more fonts</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>	<span style="color:#f92672">&amp;&amp;</span> apk add --no-cache --virtual .build-deps <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	msttcorefonts-installer <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#75715e"># Install microsoft fonts</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>	<span style="color:#f92672">&amp;&amp;</span> update-ms-fonts <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#f92672">&amp;&amp;</span> fc-cache -f <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#75715e"># Clean up when done</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>	<span style="color:#f92672">&amp;&amp;</span> rm -rf /tmp/* <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#f92672">&amp;&amp;</span> apk del .build-deps<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Copy wkhtmltopdf files from docker-wkhtmltopdf image</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> --from<span style="color:#f92672">=</span>wkhtmltopdf /bin/wkhtmltopdf /bin/wkhtmltopdf<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># copy YaCy to app image</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> addgroup yacy <span style="color:#f92672">&amp;&amp;</span> adduser -S -G yacy -H -D yacy<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span> <span style="color:#e6db74">/opt</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> --chown<span style="color:#f92672">=</span>yacy:yacy --from<span style="color:#f92672">=</span>builder /opt/yacy_search_server /opt/yacy_search_server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Expose HTTP and HTTPS default ports</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">EXPOSE</span> <span style="color:#e6db74">8090</span> <span style="color:#ae81ff">8443</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Set data volume: yacy data and configuration will persist even after container stop or destruction</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">VOLUME</span> [<span style="color:#e6db74">&#34;/opt/yacy_search_server/DATA&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Next commands run as yacy as non-root user for improved security</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">USER</span> <span style="color:#e6db74">yacy</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Start yacy as a foreground process (-f) to display console logs and to wait for yacy process</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;/bin/sh&#34;</span>,<span style="color:#e6db74">&#34;/opt/yacy_search_server/startYACY.sh&#34;</span>,<span style="color:#e6db74">&#34;-f&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>It&rsquo;s a light adaption of the <a href="https://github.com/yacy/yacy_search_server/blob/master/docker/Dockerfile.alpine">official Alpine Dockerfile</a>,
with the only change being that I introduced configurable versions for the JDK,
Alpine and other tooling. I build this via my internal pipeline. If you&rsquo;re
interested, have a look at <a href="https://blog.mei-home.net/posts/improving-container-image-build-perf-with-buildah/">this post</a>.</p>
<p>With that done, I could create the Kubernetes deployment:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">yacy</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">yacy</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#e6db74">&#34;Recreate&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">yacy</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">automountServiceAccountToken</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">securityContext</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">fsGroup</span>: <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">runAsNonRoot</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">runAsUser</span>: <span style="color:#ae81ff">100</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">runAsGroup</span>: <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">yacy</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">securityContext</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">allowPrivilegeEscalation</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Can&#39;t be done because htroot/ is written to and is outside DATA/ dir</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># At the same time, this dir contains files already, so can&#39;t just be remapped</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">#readOnlyRootFilesystem: true</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">capabilities</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">drop</span>:
</span></span><span style="display:flex;"><span>                - <span style="color:#ae81ff">ALL</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">containers.homelab.example/homelab/yacy:{{ .Values.appVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: {{ <span style="color:#ae81ff">.Values.mountDir }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">limits</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">2000m</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">3200M</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">requests</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">2000m</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">3200M</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_PORT</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;{{ .Values.ports.bind }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_PORT_PUBLIC</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;{{ .Values.ports.public }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_STATICIP</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;{{ .Values.domain }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_JAVASTART_XMX</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;Xmx3000m&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_UPNP_ENABLED</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_SERVER_HTTPS</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_NETWORK_UNIT_PROTOCOL_HTTPS_PREFERRED</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;true&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_UPDATE_PROCESS</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;manual&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_PROMOTESEARCHPAGEGREETING</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;Meier&#39;s Search&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_PROXYCLIENT</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_ADMINACCOUNTFORLOCALHOST</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_SCAN_ENABLED</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_BROWSERPOPUPTRIGGER</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_TRAY_ICON_ENABLED</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">YACY_NETWORK_UNIT_AGENT</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;mei-home-search&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">livenessProbe</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">httpGet</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">port</span>: {{ <span style="color:#ae81ff">.Values.ports.bind }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">initialDelaySeconds</span>: <span style="color:#ae81ff">30</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">periodSeconds</span>: <span style="color:#ae81ff">30</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">startupProbe</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">httpGet</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">port</span>: {{ <span style="color:#ae81ff">.Values.ports.bind }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">periodSeconds</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">failureThreshold</span>: <span style="color:#ae81ff">24</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">initialDelaySeconds</span>: <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">yacy-http</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.bind }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">persistentVolumeClaim</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">claimName</span>: <span style="color:#ae81ff">yacy-volume</span>
</span></span></code></pre></div><p>So there&rsquo;s a few things to say about the config. First, note that the Container
cannot be configured with <code>readOnlyRootFilesystem: false</code>. This is because files
are written to directories which already contain other files as part of the
image, so it can&rsquo;t be re-mounted.</p>
<p>Another thing worth mentioning is the rather long startup probe duration. This
is due to the fact that sometimes, YaCy does some cleanup/compression during
startup. Especially after a crash instead of a proper shutdown. This can take
quite a while, especially when you&rsquo;re working with a Pi 4 class CPU.</p>
<p>Then there&rsquo;s a general problem with the setup of the environment variables.
The issue is that the env variables correspond to settings in the YaCy config
file. To set those via env variables, you have to prepend <code>YACY_</code> to the front
of the config key&rsquo;s name and also upper case it and replace <code>.</code> with <code>_</code>. This
works for values like <code>some.setting.name</code>, but fails for settings like <code>someSettingName</code>.
That&rsquo;s due to how a check to see whether a config key corresponding to the
env variable name exists works, lower casing the whole env variable name and
then searching for it in the configs.
See <a href="https://github.com/yacy/yacy_search_server/issues/794">this issue</a> and my
accompanying fix <a href="https://github.com/yacy/yacy_search_server/pull/797">here</a>.</p>
<p>Also note that the <code>YACY_PORT_PUBLIC</code> setting is not currently supported upstream,
it&rsquo;s a fix for an issue I&rsquo;ve discovered earlier. I will go into more detail
in the next section.</p>
<p>The <code>YACY_PORT</code> and <code>YACY_PORT_PUBLIC</code> settings set the port YaCy binds to and
the port it communicates to other peers for connections, respectively. I&rsquo;m
disabling UPnP as well as HTTPS, as I don&rsquo;t have UPnP enabled in my firewall
and YaCy is fronted by a reverse proxy terminating TLS. The <code>YACY_STATICIP</code>
setting defines the address reported to other peers trying to contact this
one. Despite its name, it also happily takes a domain, not just an IP.</p>
<p>The <code>YACY_PROMOTESEARCHPAGEGREETING</code>
setting configures the subheading shown under the YaCy logo on the search page.</p>
<p><code>YACY_ADMINACCOUNTFORLOCALHOST</code> is an important setting. By default, YaCy
launches with a configuration which allows all connections coming from local to
do anything they want without any further authentication. Not a good default in
my view, but likely intended to make it easier to handle when running an instance
locally.</p>
<p><code>YACY_SCAN_ENABLED</code> is disabled here, as that setting would scan the local network
for other YaCy instances. Not useful, as this is the only instance I&rsquo;m running.
Well, right now at least.</p>
<p>Both <code>YACY_BROWSERPOPUPTRIGGER</code> and <code>YACY_TRAY_ICON_ENABLED</code> are irrelevant for
deployments on Kubernetes, as they enable features only useful for local desktop
deployments.</p>
<p>And finally, <code>YACY_NETWORK_UNIT_AGENT</code> defines the name of the peer in the YaCy P2P
network. If not given, a name will be generated randomly.</p>
<p>One note on the volume I&rsquo;m attaching here: This is a Ceph RBD from an SSD pool.
I figured that Solr would likely not be happy with a network attached HDD
volume. I haven&rsquo;t had any performance or stability issues with this.</p>
<p>It&rsquo;s also worth noting that YaCy can be configured via a config file as well,
but it&rsquo;s not really cloud native. There is the <a href="https://github.com/yacy/yacy_search_server/blob/Release_1.941/defaults/yacy.init">yacy.init</a>
file to begin with. During first startup, that&rsquo;s copied to get the initial
config file. It should work perfectly well to override this with e.g. a ConfigMap
to change some settings.
Any changes after that initial setup are more complicated without using env
variables though. That&rsquo;s because the real config file under <code>DATA/SETTINGS/yacy.conf</code>
is also written to by YaCy when changes are made via the web UI. Those would be
lost upon restart with a ConfigMap.</p>
<h2 id="external-accessibility-and-peering">External accessibility and peering</h2>
<p>Before getting into the details, it&rsquo;s worth noting that YaCy has different peer
levels an instance can have, ranging from &ldquo;Virgin&rdquo; (yes, I know &#x1f614;), where
it hasn&rsquo;t had contact to any outside instance, to &ldquo;Principal&rdquo;. Virgin instances
are for example instances which are really only for local use, e.g. providing
search for an intranet and its sites only. In this mode, the instance doesn&rsquo;t
connect to any other peers and doesn&rsquo;t participate in the global search index.</p>
<p>The next level is &ldquo;Junior&rdquo;. Here, the instance can connect to external peers
at least outgoing. This allows usage of P2P search and outgoing transfers of
pieces of the local index, but not receipt of index pieces from other peers.</p>
<p>Next comes the &ldquo;Senior&rdquo; mode. This is what my instance is currently running in.
It means full participation in the P2P index, being able to be contacted by
external peers. If you&rsquo;re curious, my instance is <code>mei-home-search</code>.</p>
<p>The final level, &ldquo;Principal&rdquo;, is a Senior instance which also provides an initial
seed list. Some of those are hardcoded into the YaCy binary as a starting point
for new instances. This is only required during initial setup. Afterwards, each
instance keeps its own seed list and uses that after restarts.</p>
<p>To take part in the peer to peer aspect of YaCy, external peers need to have
access to my instance. I was a bit apprehensive about just hanging the entire
thing out in public. But I found that just making the <code>/yacy/</code> path available
seems to be enough to make peering work.</p>
<p>So the next thing to look at is the address and port YaCy hands to other peers
for the P2P connection. Here, the YaCy docs in the <a href="https://github.com/yacy/yacy_search_server/blob/Release_1.941/defaults/yacy.init">yacy.init file</a>
are a bit confusing and don&rsquo;t really work, at least for me. They document three
different ports:</p>
<pre tabindex="0"><code># port number where the server should bind to
port = 8090
[...]
#sometimes you may want yacy to bind to another port, than the one reachable from outside.
#then set bindPort to the port yacy should bind on, and port to the port, visible from outside
#to run yacy on port 8090, reachable from port 80, set bindPort=8090, port=80 and use
#iptables -t nat -A PREROUTING -p tcp -s 192.168.24.0/16 --dport 80 -j DNAT --to 192.168.24.1:8090
#(of course you need to customize the ips)
bindPort =
[...]
#publicPort if you use a different port to access YaCy than the one it listens on, you can use this setting
publicPort=
</code></pre><p>The <code>port</code> config is the expected configuration for the port YaCy actually binds
to. Reading the <code>bindPort</code> config, you might expect that you could set the
<code>bindPort</code> instead, and then YaCy would bind to that and only set the <code>port</code> as
the port communicated to external peers for connections. I tried it with a setting
like this:</p>
<pre tabindex="0"><code>port = 443
bindPort = 8090
</code></pre><p>This lead to errors during startup, because now YaCy was trying to bind to <code>443</code>,
which failed because it&rsquo;s not running as root.
After some searching, I found that <code>bindPort</code> doesn&rsquo;t show up anywhere in the
code. It seems to simply be unused. I&rsquo;ve created <a href="https://github.com/yacy/yacy_search_server/pull/793">a PR</a>
to remove it.</p>
<p>Then there&rsquo;s the <code>publicPort</code> setting. I couldn&rsquo;t use it via env variables due
to the aforementioned issues with camelCase settings. But I tried setting it
through the UI as well as manually editing the config file. Neither worked.
External requests still shattered on my firewall, trying to access port <code>8090</code>,
or any other port I set in the <code>port</code> setting. But what I wanted here was a way
to set the listening port of YaCy itself separate from the port that YaCy tells
other peers to connect to. I also didn&rsquo;t want to set the <code>port</code> setting to <code>443</code>,
because that would have meant extended permissions for the YaCy container.</p>
<p>I could have opened port 8090, but I also didn&rsquo;t want to do that. I already have
ports 80 and 443 open and wanted to use them. So I looked into the code instead.
See <a href="https://github.com/yacy/yacy_search_server/issues/791">this issue</a> and the
<a href="https://github.com/yacy/yacy_search_server/pull/792">accompanying pull request</a>.
With that (as of yet unmerged) PR, there is now a new <code>port.public</code> setting, which
only configures which port is send to other peers for external connections.</p>
<p>With that set to <code>80</code>, I was hoping everything to work now. But other peers were still
unable to reach mine. This time though, the issue was entirely of my own making.
In my Bastion Traefik, I had two open ports, one NAT&rsquo;ed to my external port 80 and
one to external 443. But to again keep permissions for that Traefik instance
restricted, those ports on the bastion host were not 80 and 443, but higher
ports. But I used Traefik&rsquo;s <a href="https://doc.traefik.io/traefik/reference/install-configuration/entrypoints/#opt-http-redirections-entryPoint-to">entrypoint redirection</a> to point the HTTP entrypoint to the HTTPS entrypoint. This,
of course, did never actually work. As this setting would reply to any request
to the HTTP port with a permanent redirect. But not to port 443, but to the port
where the internal HTTPS endpoint was listening. Which isn&rsquo;t accessible publicly.</p>
<p>That took me quite a while to figure out.</p>
<p>But once I had finally configured that correctly, my peer started peering properly.</p>
<p>There&rsquo;s still something hinky though. YaCy regularly reports the peering status
in the logs, here&rsquo;s an example from my peer:</p>
<pre tabindex="0"><code>PeerPing: I am accessible for 31 peer(s), not accessible for 24 peer(s).
</code></pre><p>So peering definitely works for some peers. I know that because I&rsquo;m receiving
remote search queries with no issue. But some other peers still cannot connect to me.
And I just can&rsquo;t figure out why not. Something to look into at a later date.</p>
<h2 id="resource-consumption">Resource consumption</h2>
<p>Before I end this post, a short look at the resource needs is in order. My usage
has been rather restricted up to now, but I have done at least a few crawls already,
of a few random pages. I&rsquo;ve for example crawled <a href="kubernetes.io">kubernetes.io</a>,
the German newspaper <a href="faz.net">faz.net</a> and the official pages of a few cities
I&rsquo;ve lived in the past, just to get a feeling. In total, that lead to a
disk usage of about 13 GB. The top was at 16 GB, but I&rsquo;m not sure why it&rsquo;s suddenly
so much reduced.</p>
<p>When it comes to networking, the need is not too much. The few crawls I&rsquo;ve done
up to now haven&rsquo;t even saturated my &ldquo;my country is a bit shit at the internet&rdquo;
250 MBit/s connection.</p>
<p>Then there&rsquo;s CPU usage. Here is the usage of the YaCy Pod since its launch:
<figure>
    <img loading="lazy" src="cpu-usage.png"
         alt="A screenshot of a Grafana time series chart. It shows the YaCy container&#39;s CPU use, with core usage on the Y axis and time on the X axis, going from 00:00 on 2026-05-27 to 2026-06-06 at 14:00. For most of the time, the utilization moves in the band around 0.2 at most. There are two marked phases with higher use. The first one being from 2026-06-01 around 00:00 to 2026-06-02 around 00:00. In this phase, the utilization varies a lot, but stay above 1.4 and hovers around 2.1 for the most part. In the second phase, from 2026-06-04 00:00 to 08:30, it maxes out at 2.0, visibly throttled."/> <figcaption>
            <p>YaCy&rsquo;s CPU utilization.</p>
        </figcaption>
</figure>
</p>
<p>Note that that this is the Kubernetes way of measuring CPU, meaning a utilization
of 1.0 means one core fully used. Also, this is running on Raspberry Pi CM4. The
two phases with higher utilization than 0.2 were while I was running crawls.
Everything else is normal use, and the smaller/shorter spikes are restarts. So
CPU power seems to be mostly used during crawling and ingestion of the results
into the local index.</p>
<p>Here is the memory usage:
<figure>
    <img loading="lazy" src="mem-usage.png"
         alt="A screenshot of a Grafana time series chart. It shows the memory consumption in GB, ranging from 00:00 on 2026-05-27 to 14:00 at 2026-06-06. For the first few days, until 2026-05-30 around 17:00, it stayed somewhere around 400 MB to 600 MB. Afterwards, it slowly climbs up, until it reaches up to 3.80 to over 4 GB from 2026-06-01 00:00 to 2026-06-02 00:00. After that, it settles back down to around 1.6 GB, with variations of about 400 MB around that value. Towards the end of the chart, the variations become higher, now around over 1 GB, with the average being somewhere around 2 GB, but never reaching over 3GB."/> <figcaption>
            <p>YaCy&rsquo;s memory consumption.</p>
        </figcaption>
</figure>
</p>
<p>As is typical for a garbage collected language like Java, the memory consumption
fluctuates a lot. Once I had gathered a bit of an index through my first crawls,
the average consumption rose quite a bit. I only reached stability without OOM
after increasing the limit all the way up to 3 GB and setting the JVM&rsquo;s <code>Xmx</code>
option to 3000 MB. I expect to have to increase the volume once the size of the
local index increases when I crawl more sites.</p>
<p>After I&rsquo;ve used YaCy properly for a while, I will likely write up another post
on its scaling behavior, because I&rsquo;m rather curious about that. But for now it
seems to run quite happily in that 3 GB limit.</p>
<h2 id="whats-next">What&rsquo;s next</h2>
<p>The next part will be a deep dive into crawling. I&rsquo;ve already found that just
choosing a starting point and just doing a depth 3 crawl isn&rsquo;t really that great.
I&rsquo;ve also found that you pretty much need to first explore the page you&rsquo;re crawling
a bit. Two examples: The <a href="https://faz.net">faz.net</a> page has a gigantic
<code>/kaufkompass/</code> category full of products which is really not worth crawling.
And when crawling a GitHub project, you will likely want to exclude <code>/tree</code>,
<code>/commits</code> and <code>/blobs</code>.</p>
<p>There also seem to be features for importing e.g. <a href="https://en.wikipedia.org/wiki/ZIM_(file_format)">ZIM files</a>
or RSS feeds.</p>
<p>And there&rsquo;s the question of how to get better search result ranking.</p>
<p>And finally,
there&rsquo;s quite some interesting metrics available, like crawled pages, number of
peers, number of remote searches and so on. I&rsquo;m feeling very tempted to either
implement Prometheus metrics directly in YaCy, or writing an external exporter
which scrapes YaCy&rsquo;s existing APIs.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
