<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amit Singh</title>
    <description>The latest articles on DEV Community by Amit Singh (@semmet).</description>
    <link>https://dev.to/semmet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3682841%2F21248b3c-8911-43c2-8e5c-56a182a45193.jpg</url>
      <title>DEV Community: Amit Singh</title>
      <link>https://dev.to/semmet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/semmet"/>
    <language>en</language>
    <item>
      <title>Sharing music with Navidrome, Filebrowser and Civo</title>
      <dc:creator>Amit Singh</dc:creator>
      <pubDate>Thu, 05 Mar 2026 13:24:36 +0000</pubDate>
      <link>https://dev.to/semmet/sharing-music-with-navidrome-filebrowser-and-civo-17e5</link>
      <guid>https://dev.to/semmet/sharing-music-with-navidrome-filebrowser-and-civo-17e5</guid>
      <description>&lt;p&gt;&lt;em&gt;This post builds on top of my &lt;a href="https://singhamit.medium.com/learning-longhorn-64e0127d0314" rel="noopener noreferrer"&gt;last one&lt;/a&gt; where I deployed Navidrome on a K3S cluster with Longhorn volumes mounted to store data and music files.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before we get started, all my code is stored in the following repo.&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/semmet95" rel="noopener noreferrer"&gt;
        semmet95
      &lt;/a&gt; / &lt;a href="https://github.com/semmet95/navidrome-deployer" rel="noopener noreferrer"&gt;
        navidrome-deployer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A simple chart to deploy Navidrome on a Kubernetes cluster
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Navidrome Deployer&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/semmet95/navidrome-deployer/actions/workflows/e2e-tests.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/semmet95/navidrome-deployer/actions/workflows/e2e-tests.yml/badge.svg" alt="E2E Tests"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Before deploying &lt;code&gt;navidrome-deployer&lt;/code&gt;, ensure the following tools are installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="nofollow noopener noreferrer"&gt;kubectl&lt;/a&gt; - Kubernetes command-line tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://golang.org/doc/install" rel="nofollow noopener noreferrer"&gt;go&lt;/a&gt; - Go programming language&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://magefile.org/" rel="nofollow noopener noreferrer"&gt;mage&lt;/a&gt; - Go-based task runner&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://helm.sh/docs/intro/install/" rel="nofollow noopener noreferrer"&gt;helm&lt;/a&gt; - Kubernetes package manager&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/roboll/helmfile#installation" rel="noopener noreferrer"&gt;helmfile&lt;/a&gt; - Helm values file manager&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation on a cluster&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;To install the latest release&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;helmfile apply -f https://github.com/semmet95/navidrome-deployer/releases/latest/download/helmfile.yaml&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;To install a specific version&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;helmfile apply -f https://github.com/semmet95/navidrome-deployer/releases/download/&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;version&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;/helmfile.yaml&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Local Setup and Deployment&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;To deploy navidrome-deployer locally, execute the following command:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;./scripts/local-deployment.sh&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/semmet95/navidrome-deployer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;








&lt;p&gt;In this post I'll share how I deployed Navidrome on a Civo cluster and exposed interfaces to upload and play music.&lt;br&gt;
The last release I made of &lt;code&gt;navidrome-deployer&lt;/code&gt; deployed Navidrome on a K3S cluster and exposed Navidrome UI to play music. However, I was still using &lt;code&gt;kubectl&lt;/code&gt; commands to copy music files into Navidrome pods. This will be our starting point.&lt;br&gt;
To begin with, we need some way to mount the volume that stores these music files and provide a UI that lets users upload and manage them. This search led me to &lt;a href="https://github.com/filebrowser/filebrowser" rel="noopener noreferrer"&gt;Filebrowser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I must say, integrating Filebrowser was a lot easier than I expected. All I had to do was create a Deployment to run the &lt;code&gt;filebrowser/filebrowser:v2.61.0&lt;/code&gt; image and mount the &lt;code&gt;music-volume&lt;/code&gt; PVC that was already being used by Navidrome deployment. Filebrowser lets you manage files under the &lt;code&gt;/srv&lt;/code&gt; path, mount that music volume on this path and voilà, you now have a UI to upload music to Navidrome.&lt;/p&gt;

&lt;p&gt;I did have to make some config changes though. By default, Filebrowser pod logs the &lt;code&gt;admin&lt;/code&gt; user password that you can use to log in and access admin-level settings, but letting everyone log in as the admin is probably not a good idea. Luckily, Filebrowser has an option to let users sign up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3sj2tkq9ue8jd75jtitk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3sj2tkq9ue8jd75jtitk.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I could deploy Filebrowser, log in as the admin, change the password, and enable user sign up. But there's a way to slightly decrease the manual setup by using the &lt;code&gt;filebrowser&lt;/code&gt; cli. When I run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;filebrowser config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--createUserDir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filebrowser lets users create new accounts and generates their home directory automatically. This comes with a catch, but we'll dive into that when we get to the code changes section.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating public endpoints
&lt;/h2&gt;

&lt;p&gt;Now that I had interfaces to play and upload music, ready to be exposed, I needed to figure out a way to expose them safely. This was a problem because networking is not my strongest suit. Thankfully Civo has a &lt;a href="https://www.civo.com/learn/exposing-applications-https-traefik" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; that tackles this exact problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Traefik LoadBalancer
&lt;/h3&gt;

&lt;p&gt;When creating a Civo cluster, you can select the &lt;code&gt;Traefik v2 (LoadBalancer)&lt;/code&gt; addon, and it will provision a load balancer that will handle all the public traffic. The part that surprised me, pleasantly, was how easy it was to get a self-signed certificate. Just combine &lt;code&gt;cert-manager&lt;/code&gt; and Let's Encrypt to issue a &lt;code&gt;Certificate&lt;/code&gt; and then use it in Traefik's &lt;code&gt;Ingressroute&lt;/code&gt; for incoming HTTPS traffic. I also learned about some "best practices" type middlewares that you can add to the &lt;code&gt;Ingressroute&lt;/code&gt; to make it more secure, we'll go over them down the line (as you scroll).&lt;/p&gt;




&lt;p&gt;Alright, before we jump into the code, time to see Filebrowser and Navidrome in action.&lt;br&gt;
&lt;a href="https://navidrome-uploader.cf1539b6-7d51-4af3-8c87-140a1a3252dd.lb.civo.com/login?redirect=/files/" rel="noopener noreferrer"&gt;This&lt;/a&gt; is the Filebrowser endpoint which is the uploader interface. In my head, it's like the interface artists could interact with to upload their music (not that it's even remotely close, but I like the idea). You can create a new account, log in and start uploading &lt;code&gt;.mp3&lt;/code&gt; files. It might take a few seconds, but the song will appear on Navidrome UI, ready for you to play and enjoy.&lt;br&gt;
&lt;a href="https://navidrome.cf1539b6-7d51-4af3-8c87-140a1a3252dd.lb.civo.com/app/#/login" rel="noopener noreferrer"&gt;This&lt;/a&gt; is the Navidrome interface where you can play the uploaded music. I was hoping to create an "open Spotify web" type experience where you can play music without having to create an account but since I couldn't achieve that, I created a guest account with the following credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username: guest
password: guest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log in using the guest account, and you can play music uploaded by all the users through the Filebrowser interface.&lt;br&gt;
&lt;em&gt;P.S.: Don't worry, I have &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/config.yml#L10" rel="noopener noreferrer"&gt;configured&lt;/a&gt; Navidrome so this password cannot be changed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Putting it all together, this is what it looks like.&lt;br&gt;


  &lt;iframe src="https://www.youtube.com/embed/1JpiseVvjyI"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  Into the code
&lt;/h2&gt;

&lt;p&gt;And now, we finally dive into the code changes. There's a lot to cover, so I'll group them into sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating Filebrowser
&lt;/h3&gt;

&lt;p&gt;When you use Filebrowser UI to upload files, all of them are stored in the &lt;code&gt;/srv&lt;/code&gt; directory (or subdirectories under it). This is where our music files will be uploaded, meaning this where Navidrome needs to fetch music from. Navidrome deployment already &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/main/charts/navidrome-deployer/templates/deployment.yml#L53-L54" rel="noopener noreferrer"&gt;uses&lt;/a&gt; a Longhorn volume to store music files. If we set the access mode of this volume's &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/music-volume.yml" rel="noopener noreferrer"&gt;PVC&lt;/a&gt;, &lt;code&gt;music-volume&lt;/code&gt;, to &lt;code&gt;ReadWriteMany&lt;/code&gt; and mount it in the Filebrowser deployment on the &lt;code&gt;/srv&lt;/code&gt; path, we are good to go. All the music files will be shared by both the deployments, Filebrowser manages them, Navidrome plays them.&lt;br&gt;
Now that we have figured out what Filebrowser's integration point is going to be, time to configure it based on our requirements. We need to run the following commands to allow user signups, create user directories, and lock the admin account password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;filebrowser config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--createUserDir&lt;/span&gt;
filebrowser &lt;span class="nb"&gt;users &lt;/span&gt;update admin &lt;span class="nt"&gt;--lockPassword&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now here comes the tricky part, you cannot configure Filebrowser database while it's being used, which is not surprising but on top of that, you need to run these commands only after Filebrowser has booted up once and created the admin account and the database. It was at this moment I remembered how useful Helm hooks are.&lt;br&gt;
So following is the strategy I ended up with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store Filebrowser database in a Longhorn &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/filebrowser/db-volume.yml" rel="noopener noreferrer"&gt;volume&lt;/a&gt; and mount it in the Filebrowser &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/filebrowser/deployment.yml#L52-L53" rel="noopener noreferrer"&gt;deployment&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/filebrowser/reconfig.yml" rel="noopener noreferrer"&gt;job&lt;/a&gt;, &lt;code&gt;filebrowser-reconfig&lt;/code&gt;, with &lt;code&gt;post-install,post-upgrade&lt;/code&gt; helm hooks that also mounts this volume. This way the job will only run after the Filebrowser deployment is ready, and the database is created.&lt;/li&gt;
&lt;li&gt;Run the following commands in the job container to safely update Filebrowser database.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DEPLOYMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"filebrowser"&lt;/span&gt;
&lt;span class="c"&gt;# ensure filebrowser deployment is ready&lt;/span&gt;
kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Available deployment/&lt;span class="nv"&gt;$DEPLOYMENT&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt; &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300s

&lt;span class="c"&gt;# store the original replica count&lt;/span&gt;
&lt;span class="nv"&gt;ORIGINAL_REPLICAS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get deployment &lt;span class="nv"&gt;$DEPLOYMENT&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.replicas}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# scale the deployment down to 0 replicas&lt;/span&gt;
kubectl scale deployment &lt;span class="nv"&gt;$DEPLOYMENT&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt; &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;delete pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;filebrowser &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt; &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120s

&lt;span class="c"&gt;# run the filebrowser cli commands&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;database
filebrowser config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--createUserDir&lt;/span&gt;
filebrowser &lt;span class="nb"&gt;users &lt;/span&gt;update admin &lt;span class="nt"&gt;--lockPassword&lt;/span&gt;

&lt;span class="c"&gt;# scale the deployment back up to the original replica count&lt;/span&gt;
kubectl scale deployment &lt;span class="nv"&gt;$DEPLOYMENT&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt; &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ORIGINAL_REPLICAS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One last thing, this job runs &lt;code&gt;filebrowser&lt;/code&gt; and &lt;code&gt;kubectl&lt;/code&gt; CLI commands, so I had to create an image that contains them both. &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/Dockerfile.filebrowser" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is the Dockerfile and the image is &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/.github/workflows/release-packages.yml#L57-L62" rel="noopener noreferrer"&gt;built and published&lt;/a&gt; every time a new release is created.&lt;/p&gt;

&lt;p&gt;There was one more issue I kept running into. Filebrowser pods would often crash with issues related to accessing the database, so as a quick fix I add an &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/filebrowser/deployment.yml#L20-L31" rel="noopener noreferrer"&gt;init-container&lt;/a&gt; to run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 777 /database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I'm running this init-container and the filebrowser container as root. It's far from ideal but it did fix the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing Filebrowser and Navidrome deployments to public traffic
&lt;/h3&gt;

&lt;p&gt;Earlier I mentioned I was following a Civo tutorial to issue certificates using Let's Encrypt which in turn were supposed to be used by Ingress. However, I wanted to add some security to the public endpoints. After doing a little research I found that if I use Traefik's &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/ingressroute.yml" rel="noopener noreferrer"&gt;IngressRoute&lt;/a&gt;, I could add &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/middleware.yml" rel="noopener noreferrer"&gt;Middleware&lt;/a&gt; layers to the routes that harden the endpoints against common browser-based attacks. To be specific, I  added rate limiting and HSTS enforcement. I doubt that's enough but I think it's a good starting point.&lt;/p&gt;

&lt;p&gt;Coming to the &lt;code&gt;IngressRoute&lt;/code&gt; itself, I have created 2 routes, one for the Filebrowser deployment and the other for Navidrome. Here I ran into a little challenge. The host name for each route is dynamic because the base domain includes Traefik LoadBalancer ID. Luckily, this ID is stored as an annotation added to the &lt;code&gt;traefik&lt;/code&gt; service in &lt;code&gt;kube-system&lt;/code&gt; namespace. I'm using the &lt;code&gt;lookup&lt;/code&gt; function in &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/_helpers.tpl#L17-L37" rel="noopener noreferrer"&gt;_helpers.tpl&lt;/a&gt; to fetch this service and in turn, the loadbalancer ID from the annotations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{- define "baseDomain" -}}
{{- $svc := (lookup "v1" "Service" "kube-system" "traefik") -}}
{{- $annotations := $svc.metadata.annotations -}}
{{- $loadbalancerId := index $annotations "kubernetes.civo.com/loadbalancer-id" -}}
{{- printf "%s.lb.civo.com" $loadbalancerId -}}
{{- end -}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I'm referring to the &lt;code&gt;baseDomain&lt;/code&gt; in my &lt;code&gt;Ingressroute&lt;/code&gt;. I also found out that you can specify path patterns in the routes to accept, deny, or redirect traffic. I'm currently using it to deny requests targeting API endpoints and admin level settings. So everything combined, this is how the routes are defined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Host(`navidrome.{{ include "baseDomain" . }}`) &amp;amp;&amp;amp; ! PathPrefix(`/api/user`)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Host(`navidrome-uploader.{{ include "baseDomain" . }}`) &amp;amp;&amp;amp; ! PathPrefix(`/api/settings`) &amp;amp;&amp;amp; ! PathPrefix(`/settings/global`) &amp;amp;&amp;amp; ! PathPrefix(`/settings/users`)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Minor test updates
&lt;/h3&gt;

&lt;p&gt;To ensure I have basic smoke tests to cover these changes, I have &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/magefiles/test.go" rel="noopener noreferrer"&gt;updated&lt;/a&gt; them to verify cert-manager deployments are healthy and that the Filebrowser DB configuration job is completed successfully.&lt;br&gt;
In my last article I mentioned that I was disabling firewall service to create a K3S cluster and install Navidrome Deployer chart locally. This was not ideal and after reading K3S docs I found out that I could just add some exceptions to the &lt;code&gt;firewalld&lt;/code&gt; service to whitelist K3S cluster and allow inter-pod communication by running the following commands in my test setup &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/scripts/test-setup.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6443/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;trusted &lt;span class="nt"&gt;--add-source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.42.0.0/16
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;trusted &lt;span class="nt"&gt;--add-source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.43.0.0/16
firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Having covered everything, this is the final version of my helmfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;helmDefaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;
  &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;waitForJobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;longhorn&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://charts.longhorn.io&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quay.io/jetstack/charts&lt;/span&gt;
  &lt;span class="na"&gt;oci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;navidrome&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/semmet95/navidrome-deployer/charts&lt;/span&gt;
  &lt;span class="na"&gt;oci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager/cert-manager&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.19.4&lt;/span&gt;
  &lt;span class="na"&gt;createNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;crds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cainjector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;50m&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256Mi&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;longhorn&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;longhorn-system&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;longhorn/longhorn&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.11.0&lt;/span&gt;
  &lt;span class="na"&gt;createNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;longhornUI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;navidrome&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;navidrome-system&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;navidrome/navidrome-deployer&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.21.2&lt;/span&gt;
  &lt;span class="na"&gt;createNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;disableValidationOnInstall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cert-manager/cert-manager&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;longhorn-system/longhorn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have a cluster with Traefik installed, you can install Navidrome Deployer with this one-liner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helmfile apply -f https://github.com/semmet95/navidrome-deployer/releases/download/v0.21.2/helmfile.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Even though it's just meant to be a demo instance, I know there's a lot of room for improvement, so that's what I would end this article with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Currently accessing Navidrome requires sharing credentials. I need to find a way to either let users access the music without requiring an account, or to automate that process behind the scenes in a way that scales up well.&lt;/li&gt;
&lt;li&gt;Filebrowser and Navidrome containers are running as root. They are sharing a PVC so I need to switch to a non-root user without having them interfere with each other's access to the shared files.&lt;/li&gt;
&lt;li&gt;I have also observed significant delay (up to 30 seconds) on Navidrome's side before it imports newly uploaded music. Some &lt;a href="https://gitmemories.com/navidrome/navidrome/issues/4354" rel="noopener noreferrer"&gt;discussions&lt;/a&gt; around this issue suggest that it could be because of auto generated directories like &lt;code&gt;lost+found&lt;/code&gt; that Navidrome watcher might not have access to, leading to it crashing. I've added a  &lt;code&gt;postStart&lt;/code&gt; &lt;a href="https://github.com/semmet95/navidrome-deployer/blob/v0.21.2/charts/navidrome-deployer/templates/deployment.yml#L26-L31" rel="noopener noreferrer"&gt;hook&lt;/a&gt; to Navidrome and Filebrowser containers to delete this directory but that doesn't seem to have any effect.&lt;/li&gt;
&lt;li&gt;I would also like to add some restrictions to Filebrowser so users may only upload MP3/audio files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully the next time I write about this project all of these would be resolved. If you, the reader, have any suggestions for me feel free to drop them in the comment section. Until next time 🫡&lt;/p&gt;

</description>
      <category>navidrome</category>
      <category>civo</category>
      <category>filebrowser</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
