<?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: Cyber</title>
    <description>The latest articles on DEV Community by Cyber (@cyberdev_).</description>
    <link>https://dev.to/cyberdev_</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%2F1428945%2Fb266b83f-eebc-42a8-adf5-64c6eadb8690.jpg</url>
      <title>DEV Community: Cyber</title>
      <link>https://dev.to/cyberdev_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cyberdev_"/>
    <language>en</language>
    <item>
      <title>I Built a Dashboard to Monitor My Self-Hosted Docker Services</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Wed, 27 May 2026 14:16:18 +0000</pubDate>
      <link>https://dev.to/cyberdev_/i-built-a-dashboard-to-monitor-my-self-hosted-docker-services-7ee</link>
      <guid>https://dev.to/cyberdev_/i-built-a-dashboard-to-monitor-my-self-hosted-docker-services-7ee</guid>
      <description>&lt;p&gt;I run a bunch of self-hosted services on my home server. Vaultwarden, Linkwarden, Nextcloud, Anynote, AFFiNE, Vikunja - the list keeps growing. At some point I had over 30 containers running and I realized I had a problem.&lt;/p&gt;

&lt;p&gt;I couldn't remember what was running where.&lt;/p&gt;

&lt;p&gt;Which port was Vikunja on again? Is Nextcloud on &lt;code&gt;.local&lt;/code&gt; or the IP? Did Anynote go down after that update? Let me SSH in and check...&lt;/p&gt;

&lt;p&gt;I wanted one place where I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See all my services at a glance&lt;/li&gt;
&lt;li&gt;Know instantly if something is down&lt;/li&gt;
&lt;li&gt;Access my DevOps tutorials and notes&lt;/li&gt;
&lt;li&gt;Not need to bookmark 40 different URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;Cyberboard&lt;/strong&gt; - a self-hosted homelab dashboard that runs in Docker alongside everything else.&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%2Fkq85qgdg996yz6mji475.gif" 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%2Fkq85qgdg996yz6mji475.gif" alt=" " width="560" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you run a homelab, you probably know the feeling. You start with one or two containers. Then you add Vaultwarden because managing passwords yourself is the right thing to do. Then Nextcloud because Google Drive feels wrong now. Then a media stack. Then monitoring. Then a reverse proxy. Then DNS.&lt;/p&gt;

&lt;p&gt;Before you know it, you're managing a small data center from your closet.&lt;/p&gt;

&lt;p&gt;My pain points were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scattered bookmarks&lt;/strong&gt; - services spread across browser profiles and devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No health visibility&lt;/strong&gt; - I'd only find out something crashed when I tried to use it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wasted time&lt;/strong&gt; - looking up IPs and ports in Docker Compose files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost notes&lt;/strong&gt; - network configs and IPs scattered across random text files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Existing dashboards like Homer and Heimdall looked nice but felt static. I wanted something I could edit live, that checked if my services were actually responding, and where I could keep my learning resources next to my infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Cyberboard is a single-page dashboard with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service cards&lt;/strong&gt; with live health status (green/red dots)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tutorial cards&lt;/strong&gt; for my DevOps notes (Terraform, K8s, Docker, CI/CD, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick links&lt;/strong&gt; to GitHub, Docker Hub, K8s docs, and other references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A scratchpad&lt;/strong&gt; for quick notes and network info&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in editor&lt;/strong&gt; to add, edit, remove, and reorder everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; across all sections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category filters&lt;/strong&gt; (Productivity, Media, Infrastructure, Security, etc.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dark/light theme&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is stored in a single &lt;code&gt;data.json&lt;/code&gt; file. No database, no accounts, no complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Health Check Problem
&lt;/h2&gt;

&lt;p&gt;This was the most interesting part to build. My first attempt used browser-side &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;mode: 'no-cors'&lt;/code&gt; to ping each service. It sort of worked - but had major issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS services with self-signed certs&lt;/strong&gt; always showed as down (browser blocks them)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth-protected services&lt;/strong&gt; (like ones behind Authentik) showed as down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;no-cors&lt;/code&gt; responses are opaque&lt;/strong&gt; - you can't tell if a service actually responded or not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix was to move health checks server-side. The Python server pings each service URL directly using &lt;code&gt;HEAD&lt;/code&gt; requests, with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-signed cert support&lt;/strong&gt; (common in homelabs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any HTTP response = online&lt;/strong&gt; (even 401/403 - the service is running, it just wants auth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel checks&lt;/strong&gt; (20 threads, all services pinged simultaneously)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5-second timeout&lt;/strong&gt; per service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now a service behind HTTPS with a self-signed cert and basic auth shows as green. Because it is up - it just has security in front of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SSL_CTX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# 401, 403, 500 — service is running
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Connection refused, timeout, DNS failure — actually down
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Tech Stack (No Build Step)
&lt;/h2&gt;

&lt;p&gt;I wanted to keep it simple. No &lt;code&gt;npm install&lt;/code&gt;, no webpack, no build pipeline. Just files that a browser can run directly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preact&lt;/strong&gt; - 3KB React alternative, loaded from CDN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTM&lt;/strong&gt; - tagged template literals instead of JSX (no transpiler needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lucide&lt;/strong&gt; - SVG icons for the UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python stdlib&lt;/strong&gt; - the entire server is one file with zero pip dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import Maps&lt;/strong&gt; - browser-native module resolution, no bundler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The import map in the HTML tells the browser where to find each library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"importmap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imports&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/preact@10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/preact@10/hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/htm@3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lucide-preact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/lucide-preact@0.474.0?external=preact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every component uses clean imports like &lt;code&gt;import { useState } from 'preact/hooks'&lt;/code&gt; - same as any React project, just without the 200MB &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;For Docker deployments, I have two Dockerfiles. The default one uses CDN imports (smaller image). The offline one (&lt;code&gt;Dockerfile.offline&lt;/code&gt;) downloads and vendors all JS dependencies at build time - so the container runs on isolated networks with zero internet access. You pick which one fits your setup:&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="c"&gt;# Default - uses CDN&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Offline - fully self-contained&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; offline up &lt;span class="nt"&gt;-d&lt;/span&gt; cyberboard-offline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It's Structured
&lt;/h2&gt;

&lt;p&gt;The whole project is ~20 files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cyberboard/
├── index.html              # HTML shell + import map
├── style.css               # All styles
├── app.js                  # Root component
├── lib/                    # Shared modules
│   ├── preact.js           # Preact/HTM re-exports
│   ├── icons.js            # Lucide icons
│   ├── context.js          # App state context
│   ├── constants.js        # Emojis, colors, categories
│   └── data.js             # Load/save helpers
├── components/             # 14 Preact components
├── data.json               # Your data (gitignored)
├── data.json.default       # Factory defaults
├── server.py               # The entire backend
├── Dockerfile
└── docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is 100 lines of Python. It serves static files, exposes &lt;code&gt;GET /api/health&lt;/code&gt; for health checks, and &lt;code&gt;POST /api/save&lt;/code&gt; to write changes back to &lt;code&gt;data.json&lt;/code&gt;. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Editor
&lt;/h2&gt;

&lt;p&gt;One thing I didn't want was to edit a JSON file every time I added a new service. So I built an editor mode directly into the dashboard.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;E&lt;/code&gt; (or click the pencil icon) and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete buttons appear on every card&lt;/li&gt;
&lt;li&gt;Edit buttons let you modify existing entries&lt;/li&gt;
&lt;li&gt;"Add Service / Add Tutorial / Add Link" cards appear&lt;/li&gt;
&lt;li&gt;Drag and drop to reorder cards&lt;/li&gt;
&lt;li&gt;Export/import JSON backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The add/edit modal has a searchable emoji picker with 250+ emojis organized by category (Tech, Security, Cloud, Media, etc.). Search "docker" and the whale emoji pops up. Search "lock" and you get all the security emojis.&lt;/p&gt;

&lt;p&gt;All changes save to &lt;code&gt;data.json&lt;/code&gt; on the server automatically (debounced 400ms). No save button needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ppaczk0wsk1/cyberboard
&lt;span class="nb"&gt;cd &lt;/span&gt;cyberboard
python3 server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8900&lt;/code&gt; and you're done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount &lt;code&gt;data.json&lt;/code&gt; as a volume so your customizations persist:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cyberboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8900:8900"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data.json:/app/data.json&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first run, the server copies &lt;code&gt;data.json.default&lt;/code&gt; into place so you start with a full set of example services and tutorials. Then you customize from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Building this was a good exercise in restraint. It would have been easy to add user accounts, uptime history graphs, Docker API integration, notification systems... but none of that is needed for a personal dashboard.&lt;/p&gt;

&lt;p&gt;The things that actually mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Health checks that work with real homelab setups&lt;/strong&gt; (self-signed certs, auth, weird ports)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An editor that's fast enough to not be annoying&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search that finds things instantly&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A single JSON file&lt;/strong&gt; that's easy to back up and version control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run a homelab and you're tired of forgetting which port Sonarr is on, give it a try. The repo is at &lt;a href="https://github.com/ppaczk0wsk1/cyberboard" rel="noopener noreferrer"&gt;github.com/ppaczk0wsk1/cyberboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Some things I'm considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Response time display&lt;/strong&gt; on service cards (the health check already pings them - just need to time it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Favorites pinned to top&lt;/strong&gt; for the services I use most&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom service groups&lt;/strong&gt; beyond already existing categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But honestly, it does everything I need right now. And that's kind of the point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're interested in self-hosting, check out my other post: &lt;a href="https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5"&gt;My Top 5 Self-Hosted Tools Running on My Home Server via Docker&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cyberboard is open source and MIT licensed. PRs welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>opensource</category>
      <category>docker</category>
      <category>homelab</category>
    </item>
    <item>
      <title>My Top 5 Self-Hosted Tools Running on My Home Server via Docker (Part 2)</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Sat, 21 Feb 2026 03:47:57 +0000</pubDate>
      <link>https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-part-2-4aln</link>
      <guid>https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-part-2-4aln</guid>
      <description>&lt;p&gt;After sharing my first list of self-hosted tools, I realized something interesting:&lt;br&gt;
my home server isn't just replacing SaaS apps anymore, it’s replacing &lt;em&gt;habits&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead of doom-scrolling feeds, losing track of articles, or fighting with resume builders, I now rely on a second set of self-hosted tools that handle &lt;strong&gt;content consumption, learning, and utility tasks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are five more services I run daily, all self-hosted using Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/FreshRSS/FreshRSS" rel="noopener noreferrer"&gt;FreshRSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/booklore-app/booklore" rel="noopener noreferrer"&gt;Booklore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/advplyr/audiobookshelf" rel="noopener noreferrer"&gt;Audiobookshelf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CorentinTh/it-tools" rel="noopener noreferrer"&gt;IT-Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/amruthpillai/reactive-resume" rel="noopener noreferrer"&gt;Reactive Resume&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Keep Expanding My Self-Hosted Stack
&lt;/h2&gt;

&lt;p&gt;At this point, self-hosting is less about saving money and more about &lt;strong&gt;intentional software&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What I care about now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No algorithms deciding what I read&lt;/li&gt;
&lt;li&gt;Offline-friendly content&lt;/li&gt;
&lt;li&gt;Tools that do &lt;em&gt;one thing well&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Full control over my data&lt;/li&gt;
&lt;li&gt;Apps that don’t disappear behind a paywall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These five tools fit that philosophy perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. FreshRSS – Taking Back Control of My Reading
&lt;/h2&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%2Fw2g5mdqoz74yj8l5shpz.jpg" 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%2Fw2g5mdqoz74yj8l5shpz.jpg" alt=" " width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Feedly, Inoreader, Twitter/X, Google News, etc&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; News, blogs, YouTube channels, Reddit, updates&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser, Android (via FeedMe)&lt;/p&gt;

&lt;p&gt;FreshRSS is the backbone of how I consume information.&lt;/p&gt;

&lt;p&gt;Instead of relying on social media or algorithm-driven feeds, I subscribe directly to blogs, YouTube channels, and news sources I actually care about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why FreshRSS Is a Game Changer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chronological, distraction-free reading&lt;/li&gt;
&lt;li&gt;Tags, filters, and categories&lt;/li&gt;
&lt;li&gt;Supports thousands of feeds&lt;/li&gt;
&lt;li&gt;Extremely lightweight&lt;/li&gt;
&lt;li&gt;Works with almost any RSS client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it to follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tech blogs&lt;/li&gt;
&lt;li&gt;Open-source projects&lt;/li&gt;
&lt;li&gt;DevOps updates&lt;/li&gt;
&lt;li&gt;Personal blogs&lt;/li&gt;
&lt;li&gt;And more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No rage-bait. No engagement tricks. Just content - on my terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Booklore - My Personal Digital Library
&lt;/h2&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%2Fpaqaibzg52hl6icvzwbv.jpg" 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%2Fpaqaibzg52hl6icvzwbv.jpg" alt=" " width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Goodreads, Kindle notes, random folders&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Tracking and organizing books&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser &lt;/p&gt;

&lt;p&gt;Booklore is one of those tools that feels small - until you start using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I Love It&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean and simple UI&lt;/li&gt;
&lt;li&gt;Self-hosted book tracking&lt;/li&gt;
&lt;li&gt;No ads, no social pressure&lt;/li&gt;
&lt;li&gt;Full control over metadata&lt;/li&gt;
&lt;li&gt;Easy backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I plan to use Booklore to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track reading progress (just 2 books for now haha)&lt;/li&gt;
&lt;li&gt;Plan future reading&lt;/li&gt;
&lt;li&gt;Keep notes on technical books&lt;/li&gt;
&lt;li&gt;Maintain a long-term reading archive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's calm, focused, and does exactly what I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Audiobookshelf - Your Own Audible, Minus the Lock-In
&lt;/h2&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%2Fd3blikwl272hdcyyszfi.jpg" 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%2Fd3blikwl272hdcyyszfi.jpg" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Audible, cloud-based audiobook apps&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Audiobooks and podcasts&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, iOS, Browser&lt;/p&gt;

&lt;p&gt;It turns your audiobook collection into a polished streaming platform, complete with progress syncing and mobile apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Amazing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports audiobooks &lt;em&gt;and&lt;/em&gt; podcasts&lt;/li&gt;
&lt;li&gt;Beautiful UI&lt;/li&gt;
&lt;li&gt;Remembers listening position&lt;/li&gt;
&lt;li&gt;Mobile apps + web player&lt;/li&gt;
&lt;li&gt;Metadata fetching and organization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I plan to use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tech and productivity audiobooks&lt;/li&gt;
&lt;li&gt;Long-form learning&lt;/li&gt;
&lt;li&gt;Podcasts I want to archive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No DRM. No subscriptions. Just my library, everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. IT-Tools – The Swiss Army Knife for Developers
&lt;/h2&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%2Fui7dkctmkvn1wokf3lwu.jpg" 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%2Fui7dkctmkvn1wokf3lwu.jpg" alt=" " width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Random online tools, shady websites&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Dev utilities and quick conversions&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser&lt;/p&gt;

&lt;p&gt;IT-Tools is one of those things you don’t realize you need… until you have it.&lt;/p&gt;

&lt;p&gt;It’s a collection of dozens of small utilities that developers constantly Google for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools I Use the Most&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON formatter&lt;/li&gt;
&lt;li&gt;Base64 encoder/decoder&lt;/li&gt;
&lt;li&gt;UUID generator&lt;/li&gt;
&lt;li&gt;Hash generators&lt;/li&gt;
&lt;li&gt;Date/time tools&lt;/li&gt;
&lt;li&gt;Regex helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why I Self-Host It&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works offline&lt;/li&gt;
&lt;li&gt;No tracking&lt;/li&gt;
&lt;li&gt;No ads&lt;/li&gt;
&lt;li&gt;Instant access on my network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s bookmarked on every device I own.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reactive Resume - My Resume, Always Ready
&lt;/h2&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%2Ffbvbue85wmmytvawtwul.jpg" 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%2Ffbvbue85wmmytvawtwul.jpg" alt=" " width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Online resume builders&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Resume creation and versioning&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser&lt;/p&gt;

&lt;p&gt;Reactive Resume solves a very specific problem:&lt;br&gt;
&lt;em&gt;I want a great resume, but I don’t want my career data locked behind a SaaS platform.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It's Worth Hosting&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean, modern resume templates&lt;/li&gt;
&lt;li&gt;JSON-based resume data&lt;/li&gt;
&lt;li&gt;Multiple versions for different roles&lt;/li&gt;
&lt;li&gt;Export to PDF&lt;/li&gt;
&lt;li&gt;Fully self-hosted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain different resumes&lt;/li&gt;
&lt;li&gt;Quickly tailor applications&lt;/li&gt;
&lt;li&gt;Keep my career history backed up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No watermarks. No subscriptions. No surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Setup (Same Philosophy, Same Simplicity)
&lt;/h2&gt;

&lt;p&gt;Just like my first stack, everything here runs in Docker using Docker Compose.&lt;/p&gt;

&lt;p&gt;Each service has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own container&lt;/li&gt;
&lt;li&gt;Persistent volumes&lt;/li&gt;
&lt;li&gt;Reverse proxy routing&lt;/li&gt;
&lt;li&gt;HTTPS via Let's Encrypt (if needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the base setup exists, adding services like these takes minutes - not hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Usage (Still Very Reasonable)
&lt;/h2&gt;

&lt;p&gt;Even with this expanded stack, resource usage stays low.&lt;/p&gt;

&lt;p&gt;Typical requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FreshRSS: extremely light&lt;/li&gt;
&lt;li&gt;IT-Tools: negligible&lt;/li&gt;
&lt;li&gt;Booklore: very light&lt;/li&gt;
&lt;li&gt;Audiobookshelf: moderate (storage-heavy, CPU-light)&lt;/li&gt;
&lt;li&gt;Reactive Resume: minimal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This entire setup runs comfortably on the same home server without breaking a sweat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Self-hosting isn't about running &lt;em&gt;everything&lt;/em&gt; yourself.&lt;/p&gt;

&lt;p&gt;It's about choosing the right things to own.&lt;/p&gt;

&lt;p&gt;With these five tools, I’ve taken control of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What I read&lt;/li&gt;
&lt;li&gt;What I listen to&lt;/li&gt;
&lt;li&gt;How I manage my knowledge&lt;/li&gt;
&lt;li&gt;How I apply for jobs&lt;/li&gt;
&lt;li&gt;How I work day-to-day as a developer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Combined with &lt;a href="https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5"&gt;my first list&lt;/a&gt;, my home server now replaces:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notes / journaling → &lt;strong&gt;Anynote&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Password managers → &lt;strong&gt;Vaultwarden&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Knowledge bases / documentation → &lt;strong&gt;AFFiNE&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Bookmarks / link management → &lt;strong&gt;Linkwarden&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Task management / Kanban → &lt;strong&gt;Vikunja&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;RSS readers / feed aggregators → &lt;strong&gt;FreshRSS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Book tracking / library management → &lt;strong&gt;Booklore&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Audiobooks / podcasts → &lt;strong&gt;Audiobookshelf&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Dev utilities / quick tools → &lt;strong&gt;IT-Tools&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Resume builders / CV tools → &lt;strong&gt;Reactive Resume&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly?&lt;br&gt;
I wouldn't go back.&lt;/p&gt;

&lt;p&gt;If you're building your own self-hosted stack, this is a &lt;em&gt;fantastic&lt;/em&gt; second wave of services to try. &lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>docker</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My Top 5 Self-Hosted Tools Running on My Home Server via Docker (Part 1)</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Wed, 28 Jan 2026 16:17:39 +0000</pubDate>
      <link>https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5</link>
      <guid>https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5</guid>
      <description>&lt;p&gt;Running your own home server is one of the most rewarding things you can do if you care about privacy, control, and learning real-world infrastructure skills. Over the past year, I've been slowly replacing SaaS tools with self-hosted alternatives - all running neatly inside Docker containers.&lt;/p&gt;

&lt;p&gt;Today I want to share my top 5 self-hosted apps that I use daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/ychisbest/AnyNote" rel="noopener noreferrer"&gt;Anynote&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/dani-garcia/vaultwarden" rel="noopener noreferrer"&gt;Vaultwarden&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/toeverything/AFFiNE" rel="noopener noreferrer"&gt;AFFiNE&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/linkwarden/linkwarden" rel="noopener noreferrer"&gt;Linkwarden&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/go-vikunja/vikunja" rel="noopener noreferrer"&gt;Vikunja&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools cover notes, passwords, project management, and knowledge organization - basically my entire productivity stack.&lt;/p&gt;

&lt;p&gt;Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Self-Host Everything
&lt;/h2&gt;

&lt;p&gt;Before getting into the tools, here's why I host my own services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data ownership&lt;/strong&gt; - My data stays on my hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No subscriptions&lt;/strong&gt; - One-time setup beats monthly fees&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization&lt;/strong&gt; - I control updates, storage, and access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning experience&lt;/strong&gt; - Docker, networking, reverse proxies, backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below runs using Docker and Docker Compose on my home server, which makes upgrades, backups, and migrations painless.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Anynote - Simple, Fast Personal Notes
&lt;/h2&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%2Fxqv2bvzl91hak7cl5rki.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%2Fxqv2bvzl91hak7cl5rki.png" alt=" " width="515" height="885"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Google Keep / Apple Notes&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Lightweight note-taking and quick ideas&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Desktop (Windows) app&lt;/p&gt;

&lt;p&gt;Anynote is a minimal and fast note-taking app that I use for quick thoughts, daily logs, shopping lists, and scratch notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I Like It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clean UI with no distractions&lt;/li&gt;
&lt;li&gt;Lightweight and fast even on low-power servers&lt;/li&gt;
&lt;li&gt;Markdown support&lt;/li&gt;
&lt;li&gt;Easy backups via mounted volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It loads instantly and doesn't try to become a "second brain system." Sometimes simple is exactly what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Vaultwarden (Bitwarden) - Password Manager That Just Works
&lt;/h2&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%2F9b5x0d7xv65uzxjt8jao.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%2F9b5x0d7xv65uzxjt8jao.png" alt=" " width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Bitwarden Cloud (or any password manager)&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Secure password management&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Desktop (Windows) App + Browser&lt;/p&gt;

&lt;p&gt;Vaultwarden is hands down one of the best self-hosted services you can run.&lt;/p&gt;

&lt;p&gt;It's a lightweight Rust implementation of Bitwarden's server and works perfectly with official Bitwarden clients on desktop and mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It's Essential
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end encryption&lt;/li&gt;
&lt;li&gt;Works with browser extensions and mobile apps&lt;/li&gt;
&lt;li&gt;Extremely low resource usage&lt;/li&gt;
&lt;li&gt;Easy to secure behind HTTPS and 2FA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the first service I ever self-hosted - and I've never looked back.&lt;br&gt;
If you host only one thing at home, make it this.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. AFFiNE - My Notion Replacement
&lt;/h2&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%2F8pyj3m7tsgi7s9n9l5zo.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%2F8pyj3m7tsgi7s9n9l5zo.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Notion&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Knowledge bases, documentation, personal wikis&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Desktop (Windows) App + Browser&lt;/p&gt;

&lt;p&gt;AFFiNE is an open-source workspace tool that combines documents, whiteboards, and databases.&lt;br&gt;
Think Notion + Obsidian + Miro - but self-hosted.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes AFFiNE Awesome
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Block-based editing&lt;/li&gt;
&lt;li&gt;Whiteboard mode&lt;/li&gt;
&lt;li&gt;Local-first with sync&lt;/li&gt;
&lt;li&gt;Clean modern UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use AFFiNE for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project documentation&lt;/li&gt;
&lt;li&gt;Learning notes&lt;/li&gt;
&lt;li&gt;Long-term knowledge storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's actively developed and improving rapidly, which makes it exciting to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Linkwarden - Bookmark Manager but better
&lt;/h2&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%2Fmc12wrgcuwy7f22wkc19.jpg" 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%2Fmc12wrgcuwy7f22wkc19.jpg" alt=" " width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Browser bookmarks, Pocket, Raindrop&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Saving articles and resources&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Browser&lt;/p&gt;

&lt;p&gt;Linkwarden solved a problem I didn't realize I had: bookmark chaos.&lt;/p&gt;

&lt;p&gt;Instead of losing links across browsers and devices, I now have a centralized searchable archive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I Use It Daily
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tagging and collections&lt;/li&gt;
&lt;li&gt;Full-page snapshots&lt;/li&gt;
&lt;li&gt;Searchable content&lt;/li&gt;
&lt;li&gt;Beautiful UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development resources&lt;/li&gt;
&lt;li&gt;Tutorials&lt;/li&gt;
&lt;li&gt;Articles I want to revisit&lt;/li&gt;
&lt;li&gt;Reference material&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also integrates nicely with browser extensions for one-click saving.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Vikunja - Open-Source Task Management
&lt;/h2&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%2Fxrb5a4o5b65lxlvz4hcj.jpg" 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%2Fxrb5a4o5b65lxlvz4hcj.jpg" alt=" " width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Todoist / Trello&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Tasks, projects, kanban boards&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Browser&lt;/p&gt;

&lt;p&gt;Vikunja is my go-to task manager. It's flexible enough to replace both simple to-do apps and full kanban boards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features I Love
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lists, projects, and namespaces&lt;/li&gt;
&lt;li&gt;Kanban board view&lt;/li&gt;
&lt;li&gt;Due dates and reminders&lt;/li&gt;
&lt;li&gt;API and mobile app support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal tasks&lt;/li&gt;
&lt;li&gt;Home server maintenance&lt;/li&gt;
&lt;li&gt;Learning goals&lt;/li&gt;
&lt;li&gt;Long-term projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's fast, reliable, and easy to self-host.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Docker Setup (High Level)
&lt;/h2&gt;

&lt;p&gt;All of these services run using Docker Compose.&lt;/p&gt;

&lt;p&gt;Each service has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own container&lt;/li&gt;
&lt;li&gt;Mounted volumes for persistent data&lt;/li&gt;
&lt;li&gt;Reverse proxy routing (Nginx/Traefik)&lt;/li&gt;
&lt;li&gt;Automatic HTTPS via Let's Encrypt x&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy backups&lt;/li&gt;
&lt;li&gt;Simple upgrades&lt;/li&gt;
&lt;li&gt;Isolation between services&lt;/li&gt;
&lt;li&gt;Minimal downtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you learn Docker basics, adding new services becomes trivial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Usage (Surprisingly Low)
&lt;/h2&gt;

&lt;p&gt;One of the biggest myths is that self-hosting needs powerful hardware.&lt;/p&gt;

&lt;p&gt;My stack runs comfortably on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12GB RAM&lt;/li&gt;
&lt;li&gt;Low-power CPU&lt;/li&gt;
&lt;li&gt;SSD storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vaultwarden and Anynote barely consume resources, while AFFiNE and Linkwarden are the heavier ones - but still very manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Self-hosting changed how I think about software.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which app should I subscribe to?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I now ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which service should I host next?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These five tools cover almost everything I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notes&lt;/li&gt;
&lt;li&gt;Passwords&lt;/li&gt;
&lt;li&gt;Knowledge base&lt;/li&gt;
&lt;li&gt;Bookmarks&lt;/li&gt;
&lt;li&gt;Tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're just starting out, I recommend this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vaultwarden&lt;/li&gt;
&lt;li&gt;Vikunja&lt;/li&gt;
&lt;li&gt;Linkwarden&lt;/li&gt;
&lt;li&gt;AFFiNE&lt;/li&gt;
&lt;li&gt;Anynote&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you get comfortable, your home server quickly becomes your personal cloud.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>🏠☠️ Home Alone: Exposing My Home Server to the Internet (and Judgment) with FRP + Jenkins + Bash</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Tue, 08 Jul 2025 11:42:11 +0000</pubDate>
      <link>https://dev.to/cyberdev_/home-alone-exposing-my-home-server-to-the-internet-and-judgment-with-frp-jenkins-bash-1lo0</link>
      <guid>https://dev.to/cyberdev_/home-alone-exposing-my-home-server-to-the-internet-and-judgment-with-frp-jenkins-bash-1lo0</guid>
      <description>&lt;p&gt;TL;DR:&lt;/p&gt;

&lt;p&gt;I turned my home server into a publicly available chaos machine by automating FRP tunnels from Jenkins… running on a remote VPS… like some kind of over-caffeinated Bond villain.&lt;/p&gt;

&lt;p&gt;After all, who doesn't want to broadcast their cat cam to friends?&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The Setup (a.k.a. the Overkill Stack)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🧠 Home Server (Worker Jenkins Node) on private network - Raspberry Pi, old laptop, toaster with Linux - whatever you have.&lt;/li&gt;
&lt;li&gt;🌍 VPS (Main Jenkins Node) - Runs Jenkins jobs to control FRP clients.&lt;/li&gt;
&lt;li&gt;🚇 FRP (Fast Reverse Proxy) - An open-source alternative to Ngrok.&lt;/li&gt;
&lt;li&gt;🤖 Jenkins - Because we like suffering, but reproducible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌟 Goal
&lt;/h2&gt;

&lt;p&gt;From Jenkins (already connected to the home server via a build agent), we want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spin up secure FRP tunnels that expose ports from the home server.&lt;/li&gt;
&lt;li&gt;Use Jenkins jobs like a dashboard - "Expose internal port 3000 as port 1000 externally on VPS", "Shut it down", etc.&lt;/li&gt;
&lt;li&gt;Avoid manual interaction unless something literally catches fire.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🛠️ Use Cases (Excuses to Build This)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💻 Show off local dev apps to clients/friends&lt;/li&gt;
&lt;li&gt;📦 Share self-hosted dashboards (Grafana, Prometheus, whatever)&lt;/li&gt;
&lt;li&gt;🎥 Let your dog cam stream to the world&lt;/li&gt;
&lt;li&gt;⛔ Temporarily expose private tools during demos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;FRP server runs on your VPS&lt;/li&gt;
&lt;li&gt;FRP client lives on your home server&lt;/li&gt;
&lt;li&gt;Jenkins jobs (run on a connected build agent) generate config files + launch tunnels&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📦 FRP Server Setup on Jenkins VPS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Steps to Create a Freestyle Job in Jenkins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Jenkins Dashboard

&lt;ul&gt;
&lt;li&gt;Go to your Jenkins instance in the browser (e.g. &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Click on "New Item"

&lt;ul&gt;
&lt;li&gt;(Top-left of the dashboard)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Enter a Job Name&lt;/li&gt;

&lt;li&gt;Select "Freestyle Project"

&lt;ul&gt;
&lt;li&gt;Then click OK.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Configure the Job

&lt;ul&gt;
&lt;li&gt;Copy and paste the script provided below.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Click "Save"&lt;/li&gt;

&lt;li&gt;Run the Job

&lt;ul&gt;
&lt;li&gt;Click "Build Now" on the job page.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅ Here's a minimal script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# --- SETTINGS ---
FRP_VERSION=0.63.0
FRP_PLATFORM=linux_amd64
FRP_DIR="frp_${FRP_VERSION}_${FRP_PLATFORM}"
FRP_DOWNLOAD_URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_DIR}.tar.gz"
FRP_INSTALL_DIR="/opt/frp"
FRPS_BINARY="${FRP_INSTALL_DIR}/frps"

# --- ENSURE INSTALL DIR EXISTS ---
mkdir -p "$FRP_INSTALL_DIR"

# --- DOWNLOAD + INSTALL IF NEEDED ---
if [[ ! -f "$FRPS_BINARY" ]]; then
  echo "📦 Downloading FRP server to $FRP_INSTALL_DIR..."
  wget "$FRP_DOWNLOAD_URL" -O frp.tar.gz || {
    echo "❌ Download failed. Check URL or network."
    exit 1
  }

  tar -xzf frp.tar.gz
  mv "${FRP_DIR}/frps" "$FRPS_BINARY"
  chmod +x "$FRPS_BINARY"
  rm -rf "$FRP_DIR" frp.tar.gz
fi

# --- CREATE CONFIG IF NEEDED ---
cat &amp;gt; "$FRP_INSTALL_DIR/frps.ini" &amp;lt;&amp;lt;EOF
[common]
bind_port = 1000
dashboard_port = 1500
dashboard_user = admin
dashboard_pwd = admin
EOF

# --- START FRP SERVER ---
cd "$FRP_INSTALL_DIR"
export BUILD_ID=dontKillMe
nohup "$FRPS_BINARY" -c frps.ini &amp;gt; frps.log 2&amp;gt;&amp;amp;1 &amp;amp;

echo "✅ FRP server started on port 1000, dashboard at :1500"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;After that you have:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frps running on port 1000&lt;/li&gt;
&lt;li&gt;A dashboard on &lt;a href="http://vps-ip:1500" rel="noopener noreferrer"&gt;http://vps-ip:1500&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Config in /opt/frp/frps.ini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ How To Kill FTP Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I set up a different freestyle job - just like in the thrilling saga above, that shuts down the server when I don't need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
FRP_PID=$(pgrep -f frps)

if [ -n "$FRP_PID" ]; then
  echo "Killing frps with PID $FRP_PID"
  kill "$FRP_PID"
else
  echo "No frps process found"
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 FRP Client Setup on Home Server via Jenkins
&lt;/h2&gt;

&lt;p&gt;The next step is to create a third freestyle job in Jenkins, which will set up the FRP client on the worker node of Jenkins running on my home server. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep in mind, you'll need to use the "Build with Parameters" option, where the parameters are $LOCAL_PORT (the port on your private server) and $REMOTE_PORT (the port on your public VPS server).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Here's another bash script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

if [[ -z "$LOCAL_PORT" || -z "$REMOTE_PORT" ]]; then
  echo "❌ Missing required parameters."
  echo "Please provide: LOCAL_PORT, REMOTE_PORT"
  exit 1
fi

FRP_SERVER=&amp;lt;VPS IP&amp;gt;
FRP_SERVER_PORT=1000
FRP_VERSION=0.63.0
FRP_PLATFORM=linux_amd64
FRP_DIR="frp_${FRP_VERSION}_${FRP_PLATFORM}"

WORKDIR="$WORKSPACE/frpc_clients"
mkdir -p "$WORKDIR"

FRPC_BINARY="$WORKDIR/frpc"
FRPC_CONFIG="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.ini"
FRPC_LOG="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.log"
FRPC_PID="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.pid"

# Download frpc if missing
if [[ ! -f "$FRPC_BINARY" ]]; then
  echo "🔽 Downloading frpc v${FRP_VERSION}..."
  wget "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_DIR}.tar.gz" -O /tmp/frp.tar.gz || {
    echo "❌ Download failed. Please check FRP version or network."
    exit 1
  }

  tar -xzf /tmp/frp.tar.gz -C /tmp
  mv /tmp/${FRP_DIR}/frpc "$FRPC_BINARY"
  chmod +x "$FRPC_BINARY"
  rm -rf /tmp/${FRP_DIR} /tmp/frp.tar.gz
fi

echo "⚙️ Writing frpc config for local:$LOCAL_PORT → remote:$REMOTE_PORT"
cat &amp;gt; "$FRPC_CONFIG" &amp;lt;&amp;lt;EOL
[common]
server_addr = $FRP_SERVER
server_port = $FRP_SERVER_PORT

[tunnel_${LOCAL_PORT}]
type = tcp
local_port = $LOCAL_PORT
remote_port = $REMOTE_PORT
EOL

# Check if already running
if [[ -f "$FRPC_PID" ]]; then
  PID=$(cat "$FRPC_PID")
  if kill -0 "$PID" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    echo "⚠️ Tunnel already running with PID $PID"
    exit 0
  else
    echo "⚠️ Stale PID file found, removing"
    rm -f "$FRPC_PID"
  fi
fi

echo "🚀 Starting FRP tunnel..."
export BUILD_ID=dontKillMe
nohup "$FRPC_BINARY" -c "$FRPC_CONFIG" &amp;gt; "$FRPC_LOG" 2&amp;gt;&amp;amp;1 &amp;amp;
echo $! &amp;gt; "$FRPC_PID"

echo "✅ Tunnel running at $FRP_SERVER:$REMOTE_PORT (PID $(cat $FRPC_PID))"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Your janky React app is now live at &lt;a href="http://vps-ip:30001" rel="noopener noreferrer"&gt;http://vps-ip:30001&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ How To Kill A Tunnel To FRP Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create another freestyle job using the Bash script below.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As mentioned earlier, make sure to use the "Build with Parameters" option, setting $LOCAL_PORT and $REMOTE_PORT appropriately.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

LOCAL_PORT="$LOCAL_PORT"
REMOTE_PORT="$REMOTE_PORT"
FRPC_DIR="$FRP_DIR/frpc_clients"
PID_FILE="$FRPC_DIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.pid"

if [ -f "$PID_FILE" ]; then
  PID=$(cat "$PID_FILE")
  echo "Stopping tunnel: local $LOCAL_PORT → remote $REMOTE_PORT (PID: $PID)"
  kill "$PID"
  rm -f "$PID_FILE"
  echo "Tunnel stopped successfully."
else
  echo "No PID file found at $PID_FILE"
  exit 1
fi

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧠 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;They said, "Don't expose your local ports to the internet". They didn't say, "Don't automate it with Jenkins like a Bond villain".&lt;/p&gt;

&lt;p&gt;Because even in a zero-trust, cloud-native world…sometimes you just want to YOLO a port and livestream your cat feeder from Jenkins.&lt;/p&gt;

&lt;p&gt;You can find the full code for this madness &lt;a href="https://github.com/ppaczk0wsk1/frp-jenkins-tunnelkit" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>frp</category>
      <category>bash</category>
      <category>programming</category>
    </item>
    <item>
      <title>💾 Go Sheet DB: I Built a REST API in Go Using Google Sheets as a Database, Then Dockerized It Like I Hate Myself</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Mon, 23 Jun 2025 11:06:48 +0000</pubDate>
      <link>https://dev.to/cyberdev_/go-sheet-db-i-built-a-rest-api-in-go-using-google-sheets-as-a-database-then-dockerized-it-like-1nfe</link>
      <guid>https://dev.to/cyberdev_/go-sheet-db-i-built-a-rest-api-in-go-using-google-sheets-as-a-database-then-dockerized-it-like-1nfe</guid>
      <description>&lt;p&gt;Welcome to Go Sheet DB (API) ™ - the only REST API held together by sheer willpower, a Google Sheet, and my irrational desire to do something in Go.&lt;/p&gt;

&lt;p&gt;What started as "I should do something in Go" became a full-blown REST API running on Google Sheets as the backend database. No Postgres. No SQLite. Just... Sheets. The kind with rows and columns and sadness.&lt;/p&gt;

&lt;h2&gt;
  
  
  👶 Why Google Sheets?
&lt;/h2&gt;

&lt;p&gt;Because I didn't want to install a database. And I love bad ideas that technically work.&lt;/p&gt;

&lt;p&gt;✅ Free&lt;br&gt;
✅ Serverless (in the worst way possible)&lt;br&gt;
✅ Editable by non-devs (like your boss)&lt;br&gt;
✅ Feels illegal - but isn't&lt;/p&gt;
&lt;h2&gt;
  
  
  🧙 Step 1: Set Up Google Sheets API Like It's 2007
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;strong&gt;Google Cloud Console&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Library&lt;/strong&gt;, search for "Google Sheets API", and enable it&lt;/li&gt;
&lt;li&gt;Then go to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Credentials &amp;gt; Create Credentials &amp;gt; Service Account&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Once your Service account is created, click it email, later go to &lt;strong&gt;"Keys" &amp;gt; Add key &amp;gt; Create new key&lt;/strong&gt; and download config.json&lt;/li&gt;
&lt;li&gt;Download the JSON key (put this in config.json)&lt;/li&gt;
&lt;li&gt;Share your Google Sheet with the service account email
(something like &lt;a href="mailto:my-api-thingy@your-project.iam.gserviceaccount.com"&gt;my-api-thingy@your-project.iam.gserviceaccount.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Breathe deeply. You're now legally a spreadsheet engineer.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  📄 Step 2: Create Your "Database"
&lt;/h2&gt;

&lt;p&gt;Create a new Google Sheet. Call it something like &lt;em&gt;users_db&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the first sheet/tab, name the columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID | Name | Email | CreatedAt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's your schema. You're welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 Step 3: Go Code That Shouldn't Exist But Does
&lt;/h2&gt;

&lt;p&gt;Let's write some Go that connects to this spreadsheet and makes it behave like a proper database.&lt;/p&gt;

&lt;p&gt;Install Dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go mod init gosheetdb 
go get google.golang.org/api/sheets/v4 
go get golang.org/x/oauth2/google 
go get github.com/gin-gonic/gin
go install github.com/swaggo/swag/cmd/swag@latest
go get github.com/swaggo/gin-swagger
go get github.com/swaggo/files

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;main.go&lt;/em&gt; &lt;strong&gt;(All in One Like a Champion)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// @title           Go Sheet DB API (aka Go Sh**t DB API)
// @version         1.0
// @description     Local API that stores users in Google Sheets
// @host            localhost:8081
// @BasePath        /

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/gin-gonic/gin"
    _ "gosheetdb/docs" // Swagger docs generated by swag init
    "github.com/swaggo/files"
    "github.com/swaggo/gin-swagger"

    "golang.org/x/oauth2/google"
    "google.golang.org/api/option"
    "google.golang.org/api/sheets/v4"
)

var (
    srv     *sheets.Service
    sheetID = "1n3Vt0_zLdMspL5VEzbD7SVWgK9186k-GRLv5xcjrFDc"
)

// User represents a user stored in Google Sheets
type User struct {
    ID        string `json:"id" example:"1234567890"`
    Name      string `json:"name" example:"Cyber"`
    Email     string `json:"email" example:"cyber@spreadsheet.go"`
    CreatedAt string `json:"createdAt" example:"2025-06-22T15:04:05Z"`
}

type ErrorResponse struct {
    Error string `json:"error" example:"some error message"`
}

func initSheets() {
    ctx := context.Background()
    b, err := os.ReadFile("config.json")
    if err != nil {
        log.Fatalf("Failed to read config.json: %v", err)
    }

    config, err := google.JWTConfigFromJSON(b, sheets.SpreadsheetsScope)
    if err != nil {
        log.Fatalf("Failed to parse config: %v", err)
    }

    client := config.Client(ctx)
    srv, err = sheets.NewService(ctx, option.WithHTTPClient(client))
    if err != nil {
        log.Fatalf("Failed to create Sheets client: %v", err)
    }
}

// getUsers godoc
// @Summary      Get all users
// @Description  Retrieve all users from Google Sheets
// @Tags         users
// @Produce      json
// @Success      200  {array}   User
// @Failure      500  {object}  ErrorResponse
// @Router       /users [get]
func getUsers(c *gin.Context) {
    resp, err := srv.Spreadsheets.Values.Get(sheetID, "Users!A2:D").Do()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    var users []User
    for _, row := range resp.Values {
    if len(row) &amp;lt; 4 {
            continue
        }
        user := User{
            ID:        fmt.Sprintf("%v", row[0]),
            Name:      fmt.Sprintf("%v", row[1]),
            Email:     fmt.Sprintf("%v", row[2]),
            CreatedAt: fmt.Sprintf("%v", row[3]),
        }
        users = append(users, user)
    }

    c.JSON(http.StatusOK, users)
}

// addUser godoc
// @Summary      Add a new user
// @Description  Append a new user to Google Sheets
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        user  body      User  true  "User to add"
// @Success      201   {object}  User
// @Failure      400   {object}  ErrorResponse
// @Failure      500   {object}  ErrorResponse
// @Router       /users [post]
func addUser(c *gin.Context) {
    var u User
    if err := c.BindJSON(&amp;amp;u); 
    err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Bad JSON"})
        return
    }

    u.ID = fmt.Sprintf("%d", time.Now().UnixNano())
    u.CreatedAt = time.Now().Format(time.RFC3339)

    v := &amp;amp;sheets.ValueRange{
        Values: [][]interface{}{
            {u.ID, u.Name, u.Email, u.CreatedAt},
        },
    }

    _, err := srv.Spreadsheets.Values.Append(sheetID, "Users!A:D", v).ValueInputOption("RAW").Do()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, u)
}

func main() {
    initSheets()

    r := gin.Default()

    // Swagger UI at /swagger/index.html
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

    // API routes
    r.GET("/users", getUsers)
    r.POST("/users", addUser)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8081"
    }

    r.Run(":" + port)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'export PATH="$HOME/go/bin:$PATH"' &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc
swag init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 How to Run It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add your real Google Sheet ID where it says &lt;em&gt;sheetID = "...&lt;/em&gt;"&lt;/li&gt;
&lt;li&gt;Put your &lt;em&gt;config.json&lt;/em&gt; in the root directory&lt;/li&gt;
&lt;li&gt;Run it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go run main.go

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:8081/users -H "Content-Type: application/json" -d '{"name": "Cyber", "email": "cyber@spreadsheet.go"}'

curl http://localhost:8081/users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 You just built an API powered by a spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Step 4. Dockerfile From the Abyss
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM golang:1.23 
WORKDIR /app 
COPY go.mod go.sum ./ 
RUN go mod download 
COPY . . 
RUN go build -o gosheetdb . 
CMD ["./gosheetdb"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fast. Irresponsible. Beautiful.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧼 .dockerignore - Hide the Evidence
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config.json .git *.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't ship your secrets or your entire git history.&lt;br&gt;
You're building a meme, not a monolith.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 Run It Locally (For Science)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t gosheetdb .

docker run -p 8081:8081 -v $(pwd)/config.json:/app/config.json gosheetdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And just like that, your spreadsheet is now a cloud-native database.&lt;br&gt;
Somewhere, a DBA just felt a cold chill.&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  🛠️ Real-Life Uses for Go Sheet DB (Weirdly Viable)
&lt;/h2&gt;

&lt;p&gt;Despite being built on pure tech blasphemy, this API can work for small, chaotic, and oddly practical use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;💬 Event RSVP Tracking (Without a Real Backend)&lt;/li&gt;
&lt;li&gt;📋 Internal Tools for Non-Devs&lt;/li&gt;
&lt;li&gt;🧪 Prototyping APIs Before You Commit to a Real DB&lt;/li&gt;
&lt;li&gt;🎓 Teaching &amp;amp; Workshops&lt;/li&gt;
&lt;li&gt;📈 Collecting Feedback, Bug Reports, or Feature Votes&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  🤔 Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Sure, it was a wacky little experiment, but hey, now you know what a REST API in Go might look like, how to sweet-talk the Google Sheets API, and how to shove the whole thing into a Docker container like a tech-savvy magician.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I regret everything&lt;/li&gt;
&lt;li&gt;I also regret nothing&lt;/li&gt;
&lt;li&gt;It works&lt;/li&gt;
&lt;li&gt;And that’s the real problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to witness the beautiful chaos for yourself? The full code lives here (handle with care): &lt;a href="https://github.com/ppaczk0wsk1/go-sheet-api" rel="noopener noreferrer"&gt;https://github.com/ppaczk0wsk1/go-sheet-api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>restapi</category>
      <category>googlesheets</category>
    </item>
    <item>
      <title>Automating My Minecraft Bedrock Server with Bash, Jenkins, and Chicken Noises 🐔</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Sat, 14 Jun 2025 08:39:12 +0000</pubDate>
      <link>https://dev.to/cyberdev_/automating-my-minecraft-server-with-bash-jenkins-and-chicken-noises-5gi0</link>
      <guid>https://dev.to/cyberdev_/automating-my-minecraft-server-with-bash-jenkins-and-chicken-noises-5gi0</guid>
      <description>&lt;p&gt;&lt;em&gt;And yes, the Lava Chicken makes an appearance at the end.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Keeping your Minecraft Bedrock server up to date is important, unless you enjoy surprise crashes, player complaints, and corrupt worlds. Updating it manually? That’s a whole saga: stop the server, back everything up, grab the latest release, restore configs, restart it all. Miss a step? Say goodbye to your Nether castle.&lt;/p&gt;

&lt;p&gt;Naturally, I decided to &lt;strong&gt;automate the whole thing with a Bash script and Jenkins&lt;/strong&gt;, so now my server updates itself while I’m off building pixel art and eating a pickle pizza.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Why Jenkins?
&lt;/h2&gt;

&lt;p&gt;Jenkins is like that overachieving robot butler you wish you had in real life. It’s open-source, endlessly configurable, and perfect for automating server-side rituals like this one.&lt;/p&gt;

&lt;p&gt;With Jenkins, I get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic updates&lt;/strong&gt; on a schedule (e.g., 3 AM, when no one's online except Endermen).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs and job status&lt;/strong&gt; in a web UI (goodbye, mystery bugs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure notifications&lt;/strong&gt; if something breaks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-click manual runs&lt;/strong&gt;, for when I’m feeling fancy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔄 How Jenkins Runs the Update Script
&lt;/h2&gt;

&lt;p&gt;I set up my Bash script as a Jenkins freestyle job (pipeline works too):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stops the server (gently)&lt;/li&gt;
&lt;li&gt;Backs up all important files (just in case Creepers learn how to hack)&lt;/li&gt;
&lt;li&gt;Downloads the latest Bedrock release&lt;/li&gt;
&lt;li&gt;Restores worlds and configs&lt;/li&gt;
&lt;li&gt;Restarts the server&lt;/li&gt;
&lt;li&gt;Logs everything like it’s testifying before a redstone tribunal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something fails? Jenkins sends me a passive-aggressive email like, “Hey, your server faceplanted, bozo!”&lt;/p&gt;

&lt;h2&gt;
  
  
  🧰 Jenkins Setup TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install Jenkins somewhere with access to your server files. I have it on my VPS which is connected to my home server.&lt;/li&gt;
&lt;li&gt;Make sure the Jenkins user can stop/start the Bedrock server and write to the backup directory&lt;/li&gt;
&lt;li&gt;Create a Jenkins job with a build step that runs the Bash script&lt;/li&gt;
&lt;li&gt;Use cron syntax in Build Triggers to schedule it or trigger it manually.&lt;/li&gt;
&lt;li&gt;Peek at “Console Output” in Jenkins if things get weird&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📜 &lt;em&gt;The Bash Script That Runs the Show&lt;br&gt;
Pro tip: Put on headphones before running it. There’s a 1% chance the Lava Chicken song plays.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# Variables
BEDROCK_DIR=&amp;lt;BEDROCK_DIR&amp;gt;
BEDROCK_LOG_FILE="$BEDROCK_DIR/bedrock_server.log"
BACKUP_DIR=&amp;lt;BACKUP_DIR&amp;gt;
TEMP_DIR=&amp;lt;TEMP_DIR&amp;gt;
WORLD_DIR="$BEDROCK_DIR/worlds"
CONFIG_FILES=("$BEDROCK_DIR/server.properties" "$BEDROCK_DIR/permissions.json" "$BEDROCK_DIR/whitelist.json")
LOG_FILE=&amp;lt;LOG_FILE&amp;gt;
BACKUP_FILE="$BACKUP_DIR/bedrock_backup_latest.tar.gz"

# Logging
exec &amp;gt; &amp;gt;(tee -i $LOG_FILE)
exec 2&amp;gt;&amp;amp;1

echo "Starting Bedrock Server update process..."

# Stop the server
echo "Stopping Bedrock server..."
pkill -f "bedrock_server" || echo "Bedrock server not running."

# Backup files
echo "Backing up world and configuration files..."
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
tar -czf "$BACKUP_FILE" -C "$BEDROCK_DIR" .

# Save world/configs
echo "Saving world and configuration files..."
mkdir -p "$TEMP_DIR/config_backup"
cp -r "$WORLD_DIR" "$TEMP_DIR/config_backup"
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  [ -f "$CONFIG_FILE" ] &amp;amp;&amp;amp; cp "$CONFIG_FILE" "$TEMP_DIR/config_backup/"
done

# Download latest version
echo "Downloading the latest Bedrock server version..."
mkdir -p "$TEMP_DIR"
LATEST_URL=$(curl -Ls -A "Mozilla/5.0" "https://www.minecraft.net/en-us/download/server/bedrock/" | grep -o 'https://www.minecraft.net/bedrockdedicatedserver/bin-linux/[^"]*')
[ -z "$LATEST_URL" ] &amp;amp;&amp;amp; echo "Error: Could not find latest Bedrock URL." &amp;amp;&amp;amp; exit 1
wget --user-agent="Mozilla/5.0" "$LATEST_URL" -O "$TEMP_DIR/bedrock-server.zip" || { echo "Error: Download failed."; exit 1; }

# Update files
echo "Updating server files..."
unzip -o "$TEMP_DIR/bedrock-server.zip" -d "$BEDROCK_DIR"

# Restore world/configs
echo "Restoring world and configuration files..."
cp -r "$TEMP_DIR/config_backup/worlds" "$BEDROCK_DIR"
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  RESTORED="$TEMP_DIR/config_backup/$(basename "$CONFIG_FILE")"
  [ -f "$RESTORED" ] &amp;amp;&amp;amp; cp "$RESTORED" "$BEDROCK_DIR"
done

# Cleanup
rm -rf "$TEMP_DIR"
echo "Update complete. Cleaning up temporary files."

# Restart server
echo "Starting the Bedrock server..."
export BUILD_ID=dontKillMe
cd "$BEDROCK_DIR"
nohup bash -c "LD_LIBRARY_PATH=. ./bedrock_server" &amp;gt; "$BEDROCK_LOG_FILE" 2&amp;gt;&amp;amp;1 &amp;amp;
echo "Server restarted successfully. Log: $BEDROCK_LOG_FILE"
echo "https://youtu.be/41O_MydqxTU"

exit 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  🧠 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Thanks to this setup, my Minecraft Bedrock server updates itself while I’m AFK. Safely, reliably, and without any “oops I overwrote my Nether base” moments.&lt;/p&gt;

&lt;p&gt;If you're running a personal or community server, automating your updates with Jenkins + Bash is a power move. Add backups, clean logging, and a sprinkle of Lava Chicken magic, and you're golden.&lt;/p&gt;

&lt;p&gt;Bonus: Yes, I actually listen to the Lava Chicken song every time this script runs. It’s tradition now.&lt;br&gt;
La-la-la-Lava! Ch-ch-ch-chicken! 🐔🔥&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://tenor.com/pl/view/a-minecraft-movie-jack-black-gif-10393245520183570317" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.tenor.com%2Fm%2FkDw5vB32L40AAAAC%2Fa-minecraft.gif" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://tenor.com/pl/view/a-minecraft-movie-jack-black-gif-10393245520183570317" rel="noopener noreferrer" class="c-link"&gt;
            A Minecraft GIF – A Minecraft Movie – odkrywaj i udostępniaj GIF-y
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Click to view the GIF
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftenor.com%2Fassets%2Fimg%2Ffavicon%2Ffavicon-16x16.png"&gt;
          tenor.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>jenkins</category>
      <category>bash</category>
      <category>minecraft</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
