<?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>Forem: selfhosting.sh</title>
    <description>The latest articles on Forem by selfhosting.sh (@selfhostingsh).</description>
    <link>https://forem.com/selfhostingsh</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%2F3774438%2F482de56f-785e-440f-8085-139a14b60470.png</url>
      <title>Forem: selfhosting.sh</title>
      <link>https://forem.com/selfhostingsh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/selfhostingsh"/>
    <language>en</language>
    <item>
      <title>Pushover</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 16:09:25 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/pushover-nn6</link>
      <guid>https://forem.com/selfhostingsh/pushover-nn6</guid>
      <description>&lt;h2&gt;
  
  
  Why Replace Pushover?
&lt;/h2&gt;

&lt;p&gt;Pushover charges $4.99 per platform — buy it once for Android, pay again for iOS, and again for Desktop. A family using both platforms across multiple people quickly racks up $20-30 in license fees for what amounts to an HTTP POST endpoint.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; Verified with latest Docker images and configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Beyond cost, Pushover routes every notification through its cloud servers. Your home automation alerts, server monitoring pings, and security notifications all pass through a third party. If Pushover's servers go down, your notifications stop. If they change their API or shut down, you lose everything.&lt;/p&gt;

&lt;p&gt;Self-hosted alternatives give you unlimited notifications, zero recurring cost, and complete control over your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Alternatives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ntfy — Best Overall Replacement
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ntfy.sh/" rel="noopener noreferrer"&gt;ntfy&lt;/a&gt; is a simple HTTP-based pub/sub notification service. Send notifications with a single &lt;code&gt;curl&lt;/code&gt; command — no client libraries, no SDKs, no API keys required for basic use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"Server backup completed"&lt;/span&gt; ntfy.sh/your-private-topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Any device subscribed to that topic gets the notification instantly. ntfy supports Android (via Google Play or F-Droid with UnifiedPush), iOS, and web browsers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it wins:&lt;/strong&gt; Zero-config notifications. The API is just HTTP POST to a URL. Every monitoring tool, script, and automation platform can send an HTTP request. ntfy also supports attachments, priorities, action buttons, scheduled delivery, and access control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker setup:&lt;/strong&gt;&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;ntfy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;binwiederhier/ntfy:v2.19.2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ntfy&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serve&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UTC&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;./ntfy-cache:/var/cache/ntfy&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ntfy-config:/etc/ntfy&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;8080:80"&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;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wget"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-q"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--tries=1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:80/v1/health"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-O"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/null"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;server.yml&lt;/code&gt; config file in &lt;code&gt;./ntfy-config/&lt;/code&gt;:&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;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://ntfy.yourdomain.com"&lt;/span&gt;
&lt;span class="na"&gt;cache-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/cache/ntfy/cache.db"&lt;/span&gt;
&lt;span class="na"&gt;cache-duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12h"&lt;/span&gt;
&lt;span class="na"&gt;auth-default-access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deny-all"&lt;/span&gt;
&lt;span class="na"&gt;behind-proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read our full guide: &lt;a href="https://dev.to/apps/ntfy"&gt;How to Self-Host ntfy&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotify — Best for Simple Setups
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gotify.net/" rel="noopener noreferrer"&gt;Gotify&lt;/a&gt; is a self-hosted notification server with a clean web UI for managing applications and messages. Each application gets its own token, and messages arrive via WebSocket — no polling, no push service dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why choose Gotify:&lt;/strong&gt; If you want a web dashboard to manage notifications with application-level organization, Gotify's UI is more polished than ntfy's. It stores message history in a database you control.&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;gotify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gotify/server:2.9.1&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gotify&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;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GOTIFY_DEFAULTUSER_PASS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-strong-password-here"&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UTC&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;./gotify-data:/app/data&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;Read our full guide: &lt;a href="https://dev.to/apps/gotify"&gt;How to Self-Host Gotify&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Apprise — Best for Multi-Platform Routing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/caronc/apprise" rel="noopener noreferrer"&gt;Apprise&lt;/a&gt; isn't a notification server itself — it's a notification gateway that can route alerts to 100+ services simultaneously. Send one notification and have it delivered to Slack, Discord, email, SMS, Matrix, Telegram, and your self-hosted ntfy or Gotify instance.&lt;/p&gt;

&lt;p&gt;Use Apprise when you need to bridge notifications across multiple platforms from a single API call.&lt;/p&gt;

&lt;p&gt;Read our full guide: &lt;a href="https://dev.to/apps/apprise"&gt;How to Self-Host Apprise&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration Guide
&lt;/h2&gt;

&lt;p&gt;Pushover uses a simple HTTP POST API. Migrating to ntfy or Gotify is straightforward because they use the same pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pushover API Call
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--form-string&lt;/span&gt; &lt;span class="s2"&gt;"token=APP_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form-string&lt;/span&gt; &lt;span class="s2"&gt;"user=USER_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form-string&lt;/span&gt; &lt;span class="s2"&gt;"message=Hello"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.pushover.net/1/messages.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ntfy Equivalent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"Hello"&lt;/span&gt; https://ntfy.yourdomain.com/your-topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gotify Equivalent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://gotify.yourdomain.com/message?token=APP_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"title=Alert"&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"message=Hello"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For scripts and automations that use Pushover's API, replace the endpoint URL and adjust the payload format. Most home automation tools (Home Assistant, Uptime Kuma, Grafana) have built-in ntfy and Gotify integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pushover&lt;/th&gt;
&lt;th&gt;Self-Hosted (ntfy/Gotify)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;License cost&lt;/td&gt;
&lt;td&gt;$4.99/platform (one-time)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Family (2 people, 2 platforms each)&lt;/td&gt;
&lt;td&gt;$19.96&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly API cost&lt;/td&gt;
&lt;td&gt;Free (10K messages/month)&lt;/td&gt;
&lt;td&gt;$0 (unlimited)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;~$3-5/month electricity (existing server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy&lt;/td&gt;
&lt;td&gt;Notifications routed through Pushover servers&lt;/td&gt;
&lt;td&gt;Stays on your network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;Depends on Pushover uptime&lt;/td&gt;
&lt;td&gt;Depends on your server uptime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What You Give Up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mobile app polish.&lt;/strong&gt; Pushover's Android and iOS apps are mature and well-designed. ntfy's Android app is solid (and on F-Droid), but its iOS app is newer. Gotify only has a third-party iOS client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero maintenance.&lt;/strong&gt; Pushover just works. Self-hosted servers need Docker updates and occasional troubleshooting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email gateway.&lt;/strong&gt; Pushover can receive notifications via a special email address. ntfy supports this too, but it requires additional configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Established integrations.&lt;/strong&gt; Some commercial tools support Pushover natively but not ntfy or Gotify. Most support generic webhooks as a workaround.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does ntfy work with Home Assistant, Uptime Kuma, and other self-hosted tools?
&lt;/h3&gt;

&lt;p&gt;Yes. ntfy has native integrations in Home Assistant, &lt;a href="https://dev.to/apps/uptime-kuma/"&gt;Uptime Kuma&lt;/a&gt;, &lt;a href="https://dev.to/apps/grafana/"&gt;Grafana&lt;/a&gt;, Prometheus Alertmanager, and many other self-hosted tools. For tools without built-in ntfy support, any tool that supports webhooks can send to ntfy — it's just an HTTP POST to a URL. Gotify also has growing integration support, though ntfy's is broader due to its simpler API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can ntfy send notifications to iOS devices?
&lt;/h3&gt;

&lt;p&gt;Yes. ntfy has an iOS app on the App Store that uses Apple's Push Notification Service for reliable delivery even when the app is in the background. When self-hosting, you need to configure your ntfy server with APNs credentials, or use the ntfy.sh public relay for iOS push delivery while your server handles the backend. Gotify's iOS support is weaker — only third-party clients exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I secure ntfy so random people can't send me notifications?
&lt;/h3&gt;

&lt;p&gt;Set &lt;code&gt;auth-default-access: "deny-all"&lt;/code&gt; in ntfy's server config, then create user accounts with read/write permissions on specific topics. Use strong, random topic names as an additional layer — topics act as passwords when access control is enabled. For sensitive notifications, enable HTTPS via a reverse proxy and use authentication tokens in API headers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use ntfy or Gotify as a replacement for SMS notifications?
&lt;/h3&gt;

&lt;p&gt;Yes, and it's more reliable. SMS depends on carrier delivery and has length limits. ntfy/Gotify deliver instantly over the internet with no character limits, support images/attachments, and include priority levels for alert severities. Configure critical alerts with &lt;code&gt;Priority: urgent&lt;/code&gt; in ntfy to override Do Not Disturb on Android. For redundancy, pair ntfy with &lt;a href="https://dev.to/apps/apprise/"&gt;Apprise&lt;/a&gt; to route critical alerts to both ntfy and an SMS gateway simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much server resources does a self-hosted notification server need?
&lt;/h3&gt;

&lt;p&gt;Minimal. ntfy uses about 30-50 MB RAM and negligible CPU. Gotify uses about 50-80 MB RAM. Both run comfortably on the smallest VPS or a Raspberry Pi alongside other services. Neither requires an external database — ntfy uses SQLite for caching, Gotify uses SQLite by default. A typical homelab generating 50-100 notifications per day uses less than 100 MB of cache storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I migrate my Pushover integrations to ntfy without breaking everything?
&lt;/h3&gt;

&lt;p&gt;Migrate one integration at a time. Pushover and ntfy both use HTTP POST — the only changes are the endpoint URL and payload format. For scripts: replace &lt;code&gt;curl https://api.pushover.net/1/messages.json&lt;/code&gt; with &lt;code&gt;curl -d "message" https://ntfy.yourdomain.com/topic&lt;/code&gt;. For Home Assistant: change the notification platform from &lt;code&gt;pushover&lt;/code&gt; to &lt;code&gt;ntfy&lt;/code&gt;. Run both in parallel during transition until you've verified all integrations work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a way to route one notification to multiple platforms simultaneously?
&lt;/h3&gt;

&lt;p&gt;Yes — that's exactly what &lt;a href="https://dev.to/apps/apprise/"&gt;Apprise&lt;/a&gt; does. Send one API call to Apprise, and it forwards the notification to ntfy, Gotify, Slack, Discord, email, Telegram, Matrix, and 100+ other services simultaneously. This is useful for critical alerts that need redundancy — if your ntfy server is down, the notification still reaches you via Telegram or email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/ntfy"&gt;How to Self-Host ntfy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/gotify"&gt;How to Self-Host Gotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/apprise"&gt;How to Self-Host Apprise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/ntfy-vs-gotify"&gt;ntfy vs Gotify: Which Notification Server?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/communication"&gt;Best Self-Hosted Communication Tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>monitoring</category>
      <category>opensource</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Paperless Ngx Vs Teedy</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 14:19:23 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/paperless-ngx-vs-teedy-4kmj</link>
      <guid>https://forem.com/selfhostingsh/paperless-ngx-vs-teedy-4kmj</guid>
      <description>&lt;h2&gt;
  
  
  Quick Verdict
&lt;/h2&gt;

&lt;p&gt;Paperless-ngx is the better choice for most self-hosters. Its automated document classification, machine learning tagging, and consumption folder workflow make it the gold standard for going paperless. Choose Teedy only if you specifically need document approval workflows, LDAP authentication, or a traditional enterprise DMS structure with formal routing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; Verified with latest Docker images and configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Paperless-ngx&lt;/strong&gt; is a community-maintained document management system focused on the paperless office workflow. Scan a document, drop it in a folder, and Paperless-ngx automatically OCRs it, classifies it by correspondent and type, applies tags based on content, and makes everything searchable. Active development with frequent releases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teedy&lt;/strong&gt; (formerly Sismics Docs) is a lightweight document management system with built-in workflow routing, 256-bit AES encryption, and LDAP support. It handles a wider range of file formats (Office docs, presentations) but lacks the automated classification that defines Paperless-ngx. Development has slowed — v1.11 released March 2023.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Paperless-ngx&lt;/th&gt;
&lt;th&gt;Teedy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OCR engine&lt;/td&gt;
&lt;td&gt;Tesseract + text layer injection&lt;/td&gt;
&lt;td&gt;Tesseract 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic classification&lt;/td&gt;
&lt;td&gt;Yes — ML-based tagging&lt;/td&gt;
&lt;td&gt;No — manual tagging only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumption folder&lt;/td&gt;
&lt;td&gt;Yes — auto-ingest from watched directory&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email ingestion&lt;/td&gt;
&lt;td&gt;Yes — IMAP polling&lt;/td&gt;
&lt;td&gt;Yes — email import&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document types&lt;/td&gt;
&lt;td&gt;PDF, images (primary focus)&lt;/td&gt;
&lt;td&gt;PDF, images, ODT, DOCX, PPTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow/approval routing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — multi-step approval chains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text search&lt;/td&gt;
&lt;td&gt;Yes (excellent)&lt;/td&gt;
&lt;td&gt;Yes (advanced search mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correspondents model&lt;/td&gt;
&lt;td&gt;Yes — auto-detected&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tags&lt;/td&gt;
&lt;td&gt;Yes — auto-assigned + manual&lt;/td&gt;
&lt;td&gt;Yes — manual only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LDAP/SSO&lt;/td&gt;
&lt;td&gt;No native LDAP&lt;/td&gt;
&lt;td&gt;Yes — built-in LDAP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-factor authentication&lt;/td&gt;
&lt;td&gt;No native 2FA&lt;/td&gt;
&lt;td&gt;Yes — TOTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AES encryption at rest&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — 256-bit AES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile app&lt;/td&gt;
&lt;td&gt;PWA (responsive web)&lt;/td&gt;
&lt;td&gt;Android app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-user&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (with permissions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Compose complexity&lt;/td&gt;
&lt;td&gt;4 services (app + Redis + PostgreSQL + Gotenberg)&lt;/td&gt;
&lt;td&gt;2 services (app + PostgreSQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM usage&lt;/td&gt;
&lt;td&gt;1-2 GB&lt;/td&gt;
&lt;td&gt;512 MB - 1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Development activity&lt;/td&gt;
&lt;td&gt;Very active (monthly releases)&lt;/td&gt;
&lt;td&gt;Slow (last release March 2023)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;GPL-3.0&lt;/td&gt;
&lt;td&gt;GPL-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Document Ingestion
&lt;/h2&gt;

&lt;p&gt;The core difference: Paperless-ngx is built around &lt;strong&gt;automated ingestion&lt;/strong&gt;. Drop a scanned receipt into a consumption folder (or email it), and Paperless-ngx:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OCRs the document and creates a searchable PDF&lt;/li&gt;
&lt;li&gt;Detects the correspondent (who sent it)&lt;/li&gt;
&lt;li&gt;Assigns a document type (invoice, receipt, contract)&lt;/li&gt;
&lt;li&gt;Applies tags based on content matching rules&lt;/li&gt;
&lt;li&gt;Extracts and stores the creation date&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Teedy requires you to upload documents through the web UI or API, then manually tag and categorize them. There's no watched folder, no automatic classification, no machine learning. You do all the organizing yourself.&lt;/p&gt;

&lt;p&gt;For a household scanning invoices and receipts, Paperless-ngx's automation saves hours. For an office managing formal documents with approval chains, Teedy's manual workflow may be preferred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Setup Comparison
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Paperless-ngx&lt;/strong&gt; (4 containers):&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;paperless&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/paperless-ngx/paperless-ngx:2.20.11&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
  &lt;span class="na"&gt;gotenberg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gotenberg/gotenberg:8.17&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Teedy&lt;/strong&gt; (2 containers):&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;teedy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sismics/docs:v1.11&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teedy is simpler to deploy — half the containers, fewer moving parts. Paperless-ngx needs Redis for task queuing and Gotenberg for document conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Usage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Paperless-ngx&lt;/th&gt;
&lt;th&gt;Teedy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAM (idle)&lt;/td&gt;
&lt;td&gt;800 MB - 1.5 GB&lt;/td&gt;
&lt;td&gt;400-700 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM (OCR processing)&lt;/td&gt;
&lt;td&gt;1.5-3 GB&lt;/td&gt;
&lt;td&gt;700 MB - 1.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU during OCR&lt;/td&gt;
&lt;td&gt;High (multi-threaded)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk (app + dependencies)&lt;/td&gt;
&lt;td&gt;~2 GB&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database growth&lt;/td&gt;
&lt;td&gt;Fast (full-text index + thumbnails)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Teedy is lighter because it's a simpler Java application without the task queue, thumbnail generation, and ML classification pipeline that Paperless-ngx runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;Both support full-text search, but Paperless-ngx is significantly better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paperless-ngx:&lt;/strong&gt; Searches document content, correspondents, types, tags, dates, and custom fields. Filtered views. Saved searches. The search is central to the UX — it's how you find everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teedy:&lt;/strong&gt; Full-text search is available but requires using "advanced search." Basic search may not surface OCR'd text. The experience is more like a traditional file browser with search bolted on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;Teedy has the edge on built-in security features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AES-256 encryption at rest&lt;/strong&gt; — all stored documents are encrypted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LDAP integration&lt;/strong&gt; — connect to Active Directory or OpenLDAP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TOTP two-factor authentication&lt;/strong&gt; — native 2FA support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt; — track all document access and modifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paperless-ngx has none of these natively. For LDAP or 2FA, you'd need a reverse proxy with Authelia or Authentik in front. Documents on disk are not encrypted (you'd use filesystem-level encryption).&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Paperless-ngx If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You scan receipts, invoices, and letters regularly&lt;/li&gt;
&lt;li&gt;You want documents automatically classified and tagged&lt;/li&gt;
&lt;li&gt;Search and retrieval speed matter most&lt;/li&gt;
&lt;li&gt;You prefer active, well-maintained software&lt;/li&gt;
&lt;li&gt;You're a household or small business going paperless&lt;/li&gt;
&lt;li&gt;You want the best OCR pipeline (text layer injection into PDFs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Teedy If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need document approval workflows (review → approve → archive)&lt;/li&gt;
&lt;li&gt;LDAP authentication is a requirement&lt;/li&gt;
&lt;li&gt;You want built-in 2FA without extra infrastructure&lt;/li&gt;
&lt;li&gt;Encrypted storage at rest is mandatory&lt;/li&gt;
&lt;li&gt;You manage Office documents (DOCX, PPTX) alongside PDFs&lt;/li&gt;
&lt;li&gt;You need webhook integrations for automation&lt;/li&gt;
&lt;li&gt;Simpler Docker deployment matters (2 containers vs 4)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;Paperless-ngx dominates for the use case most self-hosters care about: turning paper into searchable, organized digital documents automatically. Its machine learning classification, consumption folder, and active development make it the category leader. Teedy serves a different niche — formal document management with approval workflows and enterprise authentication. If you're going paperless at home, Paperless-ngx is the obvious choice. If you're managing contract approvals or need LDAP and encryption, Teedy fills gaps Paperless-ngx doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/paperless-ngx"&gt;How to Self-Host Paperless-ngx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/teedy"&gt;How to Self-Host Teedy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/docspell"&gt;How to Self-Host Docspell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/paperless-ngx-vs-stirling-pdf"&gt;Paperless-ngx vs Stirling-PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/paperless-ngx-vs-docspell"&gt;Paperless-ngx vs Docspell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/document-management"&gt;Best Self-Hosted Document Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/adobe-acrobat"&gt;Self-Hosted Alternatives to Adobe Acrobat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/troubleshooting/paperless-ngx-ocr-not-working"&gt;Paperless-ngx: OCR Not Working — Fix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Matrix Vs Discord</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 12:24:22 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/matrix-vs-discord-4akj</link>
      <guid>https://forem.com/selfhostingsh/matrix-vs-discord-4akj</guid>
      <description>&lt;h2&gt;
  
  
  Quick Verdict
&lt;/h2&gt;

&lt;p&gt;Matrix is the only viable self-hosted alternative to Discord that supports federation, end-to-end encryption, and community features like spaces. You trade Discord's polish and massive user base for full data ownership and privacy. For private communities, gaming groups wanting independence from Discord's ToS, or organizations needing compliance, Matrix wins. For casual social hangouts where everyone already has Discord, switching is a hard sell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Discord&lt;/strong&gt; is a proprietary chat platform built for gaming communities that expanded into general-purpose communication. 200+ million monthly active users. Free tier is generous but monetized through Nitro subscriptions. All data lives on Discord's servers — you have no control and no export.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matrix&lt;/strong&gt; is an open, federated communication protocol with multiple server implementations (Synapse, Conduit, Dendrite). Anyone can run a server and federate with the global Matrix network. The reference client is Element. Self-hosting gives you complete control over your data, encryption keys, and server policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Matrix (Element)&lt;/th&gt;
&lt;th&gt;Discord&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Self-hostable&lt;/td&gt;
&lt;td&gt;Yes (Synapse, Conduit)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end encryption&lt;/td&gt;
&lt;td&gt;Yes (Megolm/Olm, on by default in DMs)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Federation&lt;/td&gt;
&lt;td&gt;Yes — servers communicate across domains&lt;/td&gt;
&lt;td&gt;No — single platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice/video calls&lt;/td&gt;
&lt;td&gt;Yes (via Jitsi or native Element Call)&lt;/td&gt;
&lt;td&gt;Yes (excellent quality)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen sharing&lt;/td&gt;
&lt;td&gt;Yes (via Element Call or Jitsi)&lt;/td&gt;
&lt;td&gt;Yes (built-in, smooth)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communities/Spaces&lt;/td&gt;
&lt;td&gt;Yes (Spaces = Discord server equivalent)&lt;/td&gt;
&lt;td&gt;Yes (Servers with channels)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bots&lt;/td&gt;
&lt;td&gt;Yes (bridges, appservices)&lt;/td&gt;
&lt;td&gt;Yes (massive ecosystem)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich embeds&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile apps&lt;/td&gt;
&lt;td&gt;Element (iOS/Android)&lt;/td&gt;
&lt;td&gt;Discord (iOS/Android)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop apps&lt;/td&gt;
&lt;td&gt;Element Desktop&lt;/td&gt;
&lt;td&gt;Discord Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message search&lt;/td&gt;
&lt;td&gt;Yes (full-text)&lt;/td&gt;
&lt;td&gt;Yes (limited on free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File sharing&lt;/td&gt;
&lt;td&gt;Yes (size depends on server config)&lt;/td&gt;
&lt;td&gt;25 MB free, 500 MB with Nitro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom emoji&lt;/td&gt;
&lt;td&gt;Yes (per-room)&lt;/td&gt;
&lt;td&gt;Yes (per-server, Nitro for global)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roles/permissions&lt;/td&gt;
&lt;td&gt;Yes (power levels)&lt;/td&gt;
&lt;td&gt;Yes (granular role system)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User verification&lt;/td&gt;
&lt;td&gt;Yes (cross-signing, emoji verification)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data export&lt;/td&gt;
&lt;td&gt;Yes (full server data, you own it)&lt;/td&gt;
&lt;td&gt;Limited (GDPR request only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;Yes (Apache 2.0)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free (self-hosted)&lt;/td&gt;
&lt;td&gt;Free tier + Nitro ($9.99/mo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol bridges&lt;/td&gt;
&lt;td&gt;IRC, Slack, Telegram, XMPP, Discord&lt;/td&gt;
&lt;td&gt;None (walled garden)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Voice and Video Quality
&lt;/h2&gt;

&lt;p&gt;Discord's voice chat is its killer feature — low latency, excellent quality, seamless channel switching. Matrix voice/video exists through Element Call (native WebRTC) or Jitsi integration, but it's noticeably rougher. Group calls work but lack Discord's polish: no "move between voice channels" fluidity, occasional connectivity issues, and higher latency.&lt;/p&gt;

&lt;p&gt;For text-heavy communities, this doesn't matter. For gaming groups that live in voice channels, it's the biggest gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bot and Integration Ecosystem
&lt;/h2&gt;

&lt;p&gt;Discord's bot ecosystem is enormous — thousands of bots for moderation, music, games, automation, and community management. Matrix has a growing but much smaller ecosystem. Where Matrix shines is &lt;strong&gt;bridges&lt;/strong&gt; — you can bridge Matrix rooms to Discord, Telegram, IRC, Slack, and more using &lt;a href="https://github.com/mautrix" rel="noopener noreferrer"&gt;mautrix&lt;/a&gt; bridges. This means your Matrix server can connect to Discord without anyone leaving their preferred platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Management
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Matrix&lt;/th&gt;
&lt;th&gt;Discord&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server structure&lt;/td&gt;
&lt;td&gt;Spaces → Rooms (nested hierarchy)&lt;/td&gt;
&lt;td&gt;Servers → Categories → Channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Moderation tools&lt;/td&gt;
&lt;td&gt;Power levels, bans, ACLs, Mjolnir bot&lt;/td&gt;
&lt;td&gt;Built-in moderation, AutoMod, bots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User onboarding&lt;/td&gt;
&lt;td&gt;Invite links or open registration&lt;/td&gt;
&lt;td&gt;Invite links with customizable landing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Member limits&lt;/td&gt;
&lt;td&gt;Unlimited (server capacity)&lt;/td&gt;
&lt;td&gt;500K per server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovery&lt;/td&gt;
&lt;td&gt;Room directory, Spaces&lt;/td&gt;
&lt;td&gt;Server discovery, templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;Cross-signing with emoji or QR&lt;/td&gt;
&lt;td&gt;Phone verification, ID verification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Discord's moderation tools are more mature and user-friendly. Matrix's power level system is more flexible but less intuitive. For large public communities, Discord's built-in AutoMod and raid protection are significant advantages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy and Data Ownership
&lt;/h2&gt;

&lt;p&gt;This is where Matrix dominates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E2E encryption&lt;/strong&gt; is built into the protocol. DMs are encrypted by default. Room encryption is optional and widely used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Federation&lt;/strong&gt; means no single entity controls the network. Your messages can't be deleted by a company policy change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosting&lt;/strong&gt; means your data lives on your hardware. No training AI models on your conversations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No tracking&lt;/strong&gt; — Discord tracks extensive user behavior for advertising and recommendation systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Discord has no end-to-end encryption, scans all messages for "safety," and has been known to share data with law enforcement without user notification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosting Requirements
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Matrix (Synapse)&lt;/th&gt;
&lt;th&gt;Matrix (Conduit)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;1-2 GB minimum&lt;/td&gt;
&lt;td&gt;70-200 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;2+ cores recommended&lt;/td&gt;
&lt;td&gt;1 core sufficient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk&lt;/td&gt;
&lt;td&gt;50+ GB (grows fast)&lt;/td&gt;
&lt;td&gt;1-5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL (required)&lt;/td&gt;
&lt;td&gt;RocksDB (embedded)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;30-60 minutes&lt;/td&gt;
&lt;td&gt;10-15 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a small community (under 50 users), &lt;a href="https://dev.to/apps/conduit"&gt;Conduit&lt;/a&gt; is ideal — it runs on a Raspberry Pi. For larger deployments, &lt;a href="https://dev.to/apps/matrix-synapse"&gt;Synapse&lt;/a&gt; provides full spec compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Matrix If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Privacy and data ownership matter to your community&lt;/li&gt;
&lt;li&gt;You need end-to-end encryption for sensitive conversations&lt;/li&gt;
&lt;li&gt;You want to federate with other Matrix servers or bridge to other platforms&lt;/li&gt;
&lt;li&gt;You're building a community for an organization with compliance requirements&lt;/li&gt;
&lt;li&gt;You want to escape vendor lock-in and platform risk&lt;/li&gt;
&lt;li&gt;You're comfortable with self-hosting or using a Matrix hosting provider&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Discord If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Voice chat quality is critical (gaming groups)&lt;/li&gt;
&lt;li&gt;You need the massive bot ecosystem&lt;/li&gt;
&lt;li&gt;Your community members already use Discord and won't switch&lt;/li&gt;
&lt;li&gt;You want the easiest setup with zero maintenance&lt;/li&gt;
&lt;li&gt;You're building a large public community (10K+ members)&lt;/li&gt;
&lt;li&gt;Moderation tools and raid protection are top priorities&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bridging: The Best of Both Worlds
&lt;/h2&gt;

&lt;p&gt;You don't have to choose entirely. Matrix bridges let you connect Matrix rooms to Discord channels bidirectionally. Messages flow between both platforms transparently. Run your own Matrix server for members who care about privacy, while keeping a Discord presence for discoverability. The &lt;a href="https://github.com/mautrix/discord" rel="noopener noreferrer"&gt;mautrix-discord&lt;/a&gt; bridge handles this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;Matrix is a credible self-hosted alternative to Discord for communities that value privacy, data ownership, and independence. The voice/video gap is real but narrowing. The bot ecosystem gap is wide but mitigated by bridges. If your community's primary mode is text chat and you care about where your data lives, Matrix is the clear choice. If you're a gaming community that lives in voice channels and uses 15 different bots, Discord remains hard to replace entirely — but bridging lets you have a foot in both worlds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/matrix-synapse"&gt;How to Self-Host Matrix Synapse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/conduit"&gt;How to Self-Host Conduit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/communication"&gt;Best Self-Hosted Chat Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/matrix-vs-mattermost"&gt;Matrix vs Mattermost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/matrix-vs-rocket-chat"&gt;Matrix vs Rocket.Chat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/matrix-vs-xmpp"&gt;Matrix vs XMPP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/discord"&gt;Self-Hosted Alternatives to Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/slack"&gt;Self-Hosted Alternatives to Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/troubleshooting/matrix-federation-issues"&gt;Matrix Synapse: Federation Not Working — Fix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>community</category>
      <category>opensource</category>
      <category>privacy</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>Nextcloud Upload Limit</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 10:40:02 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/nextcloud-upload-limit-3oce</link>
      <guid>https://forem.com/selfhostingsh/nextcloud-upload-limit-3oce</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Nextcloud rejects file uploads above a certain size. Users see errors like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Upload failed. Could not upload file because the file is too large"&lt;/li&gt;
&lt;li&gt;"Request Entity Too Large" (413 error)&lt;/li&gt;
&lt;li&gt;Upload progress bar reaches 100% but the file disappears&lt;/li&gt;
&lt;li&gt;Desktop client shows sync errors for large files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default upload limit depends on your PHP configuration — typically 2 MB or 512 MB. When using a reverse proxy, the proxy may impose its own limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cause
&lt;/h2&gt;

&lt;p&gt;Four layers can each impose upload size limits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Default Limit&lt;/th&gt;
&lt;th&gt;Config Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PHP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 MB (&lt;code&gt;upload_max_filesize&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;php.ini&lt;/code&gt; or &lt;code&gt;.htaccess&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PHP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8 MB (&lt;code&gt;post_max_size&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;php.ini&lt;/code&gt; or &lt;code&gt;.htaccess&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nextcloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;512 MB (chunked upload)&lt;/td&gt;
&lt;td&gt;Admin settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Web server (nginx)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 MB (&lt;code&gt;client_max_body_size&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx.conf&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reverse proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Proxy config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most restrictive layer wins. You need to increase limits at every layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Method 1: PHP Configuration (Docker)
&lt;/h3&gt;

&lt;p&gt;For the official Nextcloud Docker image, create a custom PHP config:&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;# Create custom PHP config&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /path/to/nextcloud/config/upload.ini &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
upload_max_filesize = 16G
post_max_size = 16G
max_input_time = 3600
max_execution_time = 3600
memory_limit = 512M
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount it in Docker Compose:&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;nextcloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud:33.0.0&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;nextcloud_data:/var/www/html&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./upload.ini:/usr/local/etc/php/conf.d/upload.ini:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the container:&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 restart nextcloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 2: PHP Configuration (Non-Docker)
&lt;/h3&gt;

&lt;p&gt;Edit the PHP config file (location depends on your OS):&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;# Find the active php.ini&lt;/span&gt;
php &lt;span class="nt"&gt;-i&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"php.ini"&lt;/span&gt;

&lt;span class="c"&gt;# Edit it&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/php/8.3/fpm/php.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change these values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;upload_max_filesize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;16G&lt;/span&gt;
&lt;span class="py"&gt;post_max_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;16G&lt;/span&gt;
&lt;span class="py"&gt;max_input_time&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3600&lt;/span&gt;
&lt;span class="py"&gt;max_execution_time&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3600&lt;/span&gt;
&lt;span class="py"&gt;memory_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, add to Nextcloud's &lt;code&gt;.htaccess&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;php_value upload_max_filesize 16G
php_value post_max_size 16G
php_value max_input_time 3600
php_value max_execution_time 3600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart PHP-FPM:&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;sudo &lt;/span&gt;systemctl restart php8.3-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 3: Nginx Reverse Proxy
&lt;/h3&gt;

&lt;p&gt;If you're using nginx as a reverse proxy in front of Nextcloud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;cloud.yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;16G&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;# Maximum upload size&lt;/span&gt;
    &lt;span class="kn"&gt;client_body_timeout&lt;/span&gt; &lt;span class="s"&gt;3600s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;# Time to wait for request body&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt; &lt;span class="s"&gt;3600s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;# Time to wait for proxy response&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_connect_timeout&lt;/span&gt; &lt;span class="s"&gt;3600s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://nextcloud:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_request_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# Stream uploads directly, don't buffer&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload nginx:&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;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 4: Nginx Proxy Manager
&lt;/h3&gt;

&lt;p&gt;In Nginx Proxy Manager:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Nextcloud proxy host&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Advanced&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Add this custom configuration:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;16G&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_request_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;client_body_timeout&lt;/span&gt; &lt;span class="s"&gt;3600s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 5: Apache Reverse Proxy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; *:443&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;ServerName&lt;/span&gt; cloud.yourdomain.com

    &lt;span class="nc"&gt;ProxyPass&lt;/span&gt; / http://nextcloud:80/
    &lt;span class="nc"&gt;ProxyPassReverse&lt;/span&gt; / http://nextcloud:80/

    &lt;span class="c"&gt;# Increase upload limit&lt;/span&gt;
    &lt;span class="nc"&gt;LimitRequestBody&lt;/span&gt; 17179869184

    &lt;span class="nc"&gt;ProxyTimeout&lt;/span&gt; 3600
    &lt;span class="nc"&gt;TimeOut&lt;/span&gt; 3600
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 6: Nextcloud Admin Settings
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into Nextcloud as admin&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Administration Settings &amp;gt; Basic Settings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Upload&lt;/strong&gt;, check the current chunked upload limit&lt;/li&gt;
&lt;li&gt;Nextcloud uses chunked uploads (10 MB chunks by default), so the PHP limit mainly affects the web UI uploader rather than desktop/mobile clients&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verify the Fix
&lt;/h3&gt;

&lt;p&gt;After applying changes, verify the effective limits:&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;# Check PHP settings inside the Docker container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;nextcloud php &lt;span class="nt"&gt;-i&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"upload_max_filesize|post_max_size|memory_limit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;upload_max_filesize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; 16G =&amp;gt; 16G&lt;/span&gt;
&lt;span class="py"&gt;post_max_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; 16G =&amp;gt; 16G&lt;/span&gt;
&lt;span class="py"&gt;memory_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; 512M =&amp;gt; 512M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test by uploading a file larger than the previous limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set upload limits during initial deployment&lt;/strong&gt; — add the custom PHP config to your Docker Compose from the start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the desktop client for large files&lt;/strong&gt; — Nextcloud desktop clients use chunked uploads that bypass PHP limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;proxy_request_buffering off&lt;/code&gt;&lt;/strong&gt; in nginx to prevent the proxy from buffering the entire upload in memory before forwarding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor disk space&lt;/strong&gt; — large uploads can fill your volume unexpectedly; set up disk usage alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/nextcloud"&gt;How to Self-Host Nextcloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/troubleshooting/nextcloud-sync-not-working"&gt;Nextcloud: Sync Not Working — Fix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/troubleshooting/nextcloud-slow-performance"&gt;Nextcloud: Slow Performance — Fix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/file-sync"&gt;Best Self-Hosted File Sync &amp;amp; Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/reverse-proxy-explained"&gt;Reverse Proxy Setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>opensource</category>
      <category>php</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Syncthing Not Connecting</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 08:38:08 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/syncthing-not-connecting-50c4</link>
      <guid>https://forem.com/selfhostingsh/syncthing-not-connecting-50c4</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Syncthing devices show as "Disconnected" in the web UI. Folders display "Out of Sync" or "Waiting to Connect." Devices that previously synced stop communicating, or newly added devices never establish a connection.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; Verified with latest Docker images and configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Common error messages in the Syncthing log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connection to DEVICE-ID at tcp://192.168.1.100:22000 refused
Relay connection failed: dial tcp relay.syncthing.net:443: i/o timeout
Discovery: no addresses returned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Cause
&lt;/h2&gt;

&lt;p&gt;Syncthing uses three connection methods in this priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Direct connection&lt;/strong&gt; — TCP on port 22000 (default) between devices on the same LAN or port-forwarded through NAT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relay&lt;/strong&gt; — Through Syncthing relay servers when direct connections fail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt; — Global discovery servers to find device addresses, plus local discovery via broadcast&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Connection failures happen when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firewalls block port 22000 (TCP) and/or 21027 (UDP for local discovery)&lt;/li&gt;
&lt;li&gt;NAT/router configuration prevents incoming connections&lt;/li&gt;
&lt;li&gt;Relay servers are unreachable (corporate networks, restrictive ISPs)&lt;/li&gt;
&lt;li&gt;Device IDs are mismatched between peers&lt;/li&gt;
&lt;li&gt;Syncthing is bound to the wrong network interface&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Method 1: Fix Firewall Rules (Most Common)
&lt;/h3&gt;

&lt;p&gt;On the server running Syncthing, open the required ports:&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;# UFW (Ubuntu/Debian)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 22000/tcp comment &lt;span class="s2"&gt;"Syncthing file sync"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 21027/udp comment &lt;span class="s2"&gt;"Syncthing local discovery"&lt;/span&gt;

&lt;span class="c"&gt;# firewalld (Fedora/RHEL)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&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;22000/tcp
&lt;span class="nb"&gt;sudo &lt;/span&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;21027/udp
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Docker deployments, ensure the ports are mapped in your Docker Compose:&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;syncthing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;syncthing/syncthing:2.0.15&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;8384:8384"&lt;/span&gt;     &lt;span class="c1"&gt;# Web UI&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;22000:22000/tcp"&lt;/span&gt;  &lt;span class="c1"&gt;# File sync (TCP)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;22000:22000/udp"&lt;/span&gt;  &lt;span class="c1"&gt;# File sync (QUIC)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;21027:21027/udp"&lt;/span&gt;  &lt;span class="c1"&gt;# Local discovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 2: Fix NAT/Router Configuration
&lt;/h3&gt;

&lt;p&gt;If devices are on different networks, port forward on your router:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into your router admin panel&lt;/li&gt;
&lt;li&gt;Forward &lt;strong&gt;port 22000 TCP&lt;/strong&gt; to your Syncthing server's LAN IP&lt;/li&gt;
&lt;li&gt;Forward &lt;strong&gt;port 21027 UDP&lt;/strong&gt; for discovery (optional but helps)&lt;/li&gt;
&lt;li&gt;Forward &lt;strong&gt;port 22000 UDP&lt;/strong&gt; for QUIC connections (optional, improves performance)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify the port is reachable from outside:&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;# From another network&lt;/span&gt;
nc &lt;span class="nt"&gt;-zv&lt;/span&gt; your-public-ip 22000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 3: Force Relay Connections
&lt;/h3&gt;

&lt;p&gt;If direct connections aren't possible (both devices behind strict NAT), verify relay access:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Syncthing web UI → &lt;strong&gt;Actions &amp;gt; Settings &amp;gt; Connections&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Ensure &lt;strong&gt;"Enable Relaying"&lt;/strong&gt; is checked&lt;/li&gt;
&lt;li&gt;Ensure &lt;strong&gt;"Global Discovery"&lt;/strong&gt; is enabled&lt;/li&gt;
&lt;li&gt;Default relay servers: &lt;code&gt;default&lt;/code&gt; (uses Syncthing's public relays)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test relay connectivity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://relays.syncthing.net/endpoint | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your network blocks outbound connections to relay servers (port 443), you may need to configure a corporate proxy or use a VPN.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 4: Verify Device IDs
&lt;/h3&gt;

&lt;p&gt;Mismatched device IDs prevent connections entirely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On Device A: &lt;strong&gt;Actions &amp;gt; Show ID&lt;/strong&gt; — copy the full device ID&lt;/li&gt;
&lt;li&gt;On Device B: &lt;strong&gt;Add Remote Device&lt;/strong&gt; — paste Device A's ID exactly&lt;/li&gt;
&lt;li&gt;Repeat in reverse (Device B's ID on Device A)&lt;/li&gt;
&lt;li&gt;Both devices must accept each other&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Method 5: Check Listen Address
&lt;/h3&gt;

&lt;p&gt;If Syncthing is bound to a specific interface:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Actions &amp;gt; Settings &amp;gt; Connections&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;"Sync Protocol Listen Addresses"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Default is &lt;code&gt;default&lt;/code&gt; which listens on all interfaces&lt;/li&gt;
&lt;li&gt;If set to a specific IP (e.g., &lt;code&gt;tcp://192.168.1.100:22000&lt;/code&gt;), ensure that IP is correct and reachable&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prevention
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the &lt;code&gt;default&lt;/code&gt; listen address&lt;/strong&gt; unless you have a specific reason to bind to one interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep relay enabled&lt;/strong&gt; as a fallback even if you primarily use direct connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up port forwarding&lt;/strong&gt; on your router for at least one device per network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor connection status&lt;/strong&gt; in the Syncthing web UI — the "Connections" tab shows which method each device uses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Syncthing's built-in NAT traversal&lt;/strong&gt; (enabled by default) — it handles UPnP and NAT-PMP automatically if your router supports them&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/syncthing"&gt;How to Self-Host Syncthing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/file-sync"&gt;Best Self-Hosted File Sync &amp;amp; Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/nextcloud-vs-syncthing"&gt;Nextcloud vs Syncthing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/docker-networking"&gt;Docker Networking Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Splitwise</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 06:48:07 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/splitwise-1hla</link>
      <guid>https://forem.com/selfhostingsh/splitwise-1hla</guid>
      <description>&lt;h2&gt;
  
  
  Why Replace Splitwise?
&lt;/h2&gt;

&lt;p&gt;Splitwise knows exactly how you split dinner, who owes what on the electricity bill, and how much your housemate spent on groceries last Tuesday. It's a detailed financial profile of your relationships — stored on someone else's servers.&lt;/p&gt;

&lt;p&gt;Beyond privacy, Splitwise has been progressively paywalling features. Splitwise Pro ($4.99/month or $49.99/year) gates receipt scanning, currency conversion, chart visualizations, search, and more. Features that were free in 2020 now cost money.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Splitwise&lt;/th&gt;
&lt;th&gt;Self-Hosted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free (limited) / $4.99/mo (Pro)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Annual cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0-50/year&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data location&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Splitwise's cloud&lt;/td&gt;
&lt;td&gt;Your server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (free tier)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Receipt scanning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pro only&lt;/td&gt;
&lt;td&gt;Varies by app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Currency conversion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pro only&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search expenses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pro only&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Third-party access&lt;/td&gt;
&lt;td&gt;Full control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Best Alternatives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IHateMoney — Best Overall Replacement
&lt;/h3&gt;

&lt;p&gt;IHateMoney does exactly what Splitwise does — track shared expenses in a group and calculate who owes whom. No accounts needed. Create a project with a shared link, add expenses, and the app calculates the simplest settlement plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What matches Splitwise:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add expenses with payer and participants&lt;/li&gt;
&lt;li&gt;Automatic debt simplification (minimizes number of transfers)&lt;/li&gt;
&lt;li&gt;Multi-currency support&lt;/li&gt;
&lt;li&gt;Project-based groups (one per trip, household, etc.)&lt;/li&gt;
&lt;li&gt;No registration required — just a project name and password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's different:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No mobile app (responsive web UI only)&lt;/li&gt;
&lt;li&gt;No receipt scanning&lt;/li&gt;
&lt;li&gt;Simpler UI — functional, not pretty&lt;/li&gt;
&lt;li&gt;No push notifications for new expenses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&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;ihatemoney&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ihatemoney/ihatemoney:7.0.1&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ihatemoney&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IHATEMONEY_SETTINGS_FILE_PATH=/etc/ihatemoney/ihatemoney.cfg&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:/database&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ihatemoney.cfg:/etc/ihatemoney/ihatemoney.cfg:ro&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;8000:8000"&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;Create &lt;code&gt;ihatemoney.cfg&lt;/code&gt;:&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="c1"&gt;# IHateMoney configuration
&lt;/span&gt;&lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:////database/ihatemoney.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change-this-to-a-random-string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ALLOW_PUBLIC_PROJECT_CREATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One container, ~30 MB RAM. Create a project, share the URL, start splitting. &lt;a href="https://dev.to/apps/ihatemoney"&gt;Read our full guide: How to Self-Host IHateMoney&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Firefly III — Best for All-in-One Finance
&lt;/h3&gt;

&lt;p&gt;If you want expense splitting as part of a complete personal finance system, Firefly III handles shared expenses through its split transaction feature and tagging system. It's not purpose-built for Splitwise-style group splitting, but if you already run Firefly III for budgeting, you can track shared expenses within the same system.&lt;/p&gt;

&lt;p&gt;Best for people who want one financial tool instead of multiple specialized apps. &lt;a href="https://dev.to/apps/firefly-iii"&gt;Read our full guide: How to Self-Host Firefly III&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Actual Budget — Best Budget-First Approach
&lt;/h3&gt;

&lt;p&gt;Actual Budget can track shared expenses through categories and payees, though it doesn't calculate settlement balances automatically. Pair it with IHateMoney — use Actual Budget for personal budgeting and IHateMoney for group expense splitting. &lt;a href="https://dev.to/apps/actual-budget"&gt;Read our full guide: How to Self-Host Actual Budget&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Splitwise Free&lt;/th&gt;
&lt;th&gt;Splitwise Pro&lt;/th&gt;
&lt;th&gt;IHateMoney&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$4.99/month&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Annual cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$49.99/year&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3-year cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$150&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Receipt scanning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Charts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (basic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Currency conversion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data ownership&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Migration Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Exporting from Splitwise
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to Splitwise on the web&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Account Settings&lt;/strong&gt; → &lt;strong&gt;Export Data&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Download your expense history as CSV&lt;/li&gt;
&lt;li&gt;Each group exports separately&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Importing into IHateMoney
&lt;/h3&gt;

&lt;p&gt;IHateMoney supports CSV import:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new project in IHateMoney matching your Splitwise group&lt;/li&gt;
&lt;li&gt;Reformat the Splitwise CSV to match IHateMoney's expected columns (date, description, payer, amount, participants)&lt;/li&gt;
&lt;li&gt;Import via the project settings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Manual entry is also fast for active groups — most people have 5-20 expenses per month per group.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Give Up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Native mobile apps.&lt;/strong&gt; Splitwise's iOS and Android apps are polished and purpose-built. IHateMoney is web-only — functional on mobile browsers but not as convenient for quick expense entry at the register.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push notifications.&lt;/strong&gt; Splitwise notifies group members when expenses are added. Self-hosted alternatives rely on manual checking or email notifications you'd need to configure yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receipt scanning.&lt;/strong&gt; Splitwise Pro scans receipts and extracts amounts automatically. No self-hosted expense splitter has this feature — you'll type amounts manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Friend network.&lt;/strong&gt; Splitwise maintains persistent debts across groups and over time. IHateMoney treats each project independently — settling up in one project doesn't affect balances in another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplicity.&lt;/strong&gt; Splitwise is "download app, create group, add expense." Self-hosted requires deploying a container and sharing a URL. For a group of non-technical housemates, this is a real friction point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I import my Splitwise expense history into IHateMoney?
&lt;/h3&gt;

&lt;p&gt;Yes, with reformatting. Export your Splitwise data as CSV (Account Settings → Export Data), then reformat the columns to match IHateMoney's expected format: date, description, payer, amount, and participants. IHateMoney supports CSV import through project settings. You'll need to create a project for each Splitwise group and import them separately. For small groups with recent activity, manual entry may be faster than reformatting the CSV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does IHateMoney have a mobile app?
&lt;/h3&gt;

&lt;p&gt;No native app — IHateMoney is web-only with a responsive interface that works in mobile browsers. You can add it to your home screen as a PWA for quick access. The mobile experience is functional for viewing balances and adding expenses, but it lacks the polished tap-to-add workflow of Splitwise's native apps. For groups where quick expense entry at the register matters, this is the biggest trade-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can multiple groups share the same IHateMoney instance?
&lt;/h3&gt;

&lt;p&gt;Yes. Each IHateMoney "project" is independent with its own URL, password, and participants. You can run as many projects as you want on a single instance — one for your household, another for a trip, another for office lunches. Projects don't interact with each other. There's no cross-project debt tracking like Splitwise's friend-level balances, but each group works independently without issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does IHateMoney handle different currencies?
&lt;/h3&gt;

&lt;p&gt;IHateMoney supports multi-currency expenses within a single project. When adding an expense, you can specify the currency. The settlement calculation handles conversion based on rates you set. It's not as automatic as Splitwise Pro's real-time currency conversion, but it works for trips where expenses happen in mixed currencies. Set the exchange rate manually when adding the expense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is IHateMoney secure enough for financial data?
&lt;/h3&gt;

&lt;p&gt;IHateMoney protects projects with a shared password — anyone with the project ID and password can view and add expenses. There's no per-user authentication or audit trail. For household expense splitting, this is fine. For anything involving sensitive financial data, pair it with a reverse proxy that adds proper authentication (OAuth2, basic auth). The data itself is stored in a SQLite database on your server, so you control access at the infrastructure level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I self-host Splitwise itself instead of using an alternative?
&lt;/h3&gt;

&lt;p&gt;No. Splitwise is a proprietary, closed-source application with no self-hosted option. The only way to use Splitwise is through their cloud service. IHateMoney is the closest open-source equivalent in terms of functionality — shared expense tracking with automatic debt simplification. If you need features IHateMoney lacks (receipt scanning, push notifications), you'll need to combine multiple tools or accept those limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/ihatemoney"&gt;How to Self-Host IHateMoney&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/firefly-iii"&gt;How to Self-Host Firefly III&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/actual-budget"&gt;How to Self-Host Actual Budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/personal-finance"&gt;Best Self-Hosted Personal Finance Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/ynab"&gt;Self-Hosted Alternatives to YNAB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/mint"&gt;Self-Hosted Alternatives to Mint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/docker-compose-basics"&gt;Docker Compose Basics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Privacy Friendly Analytics Setup</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 04:58:05 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/privacy-friendly-analytics-setup-2m7h</link>
      <guid>https://forem.com/selfhostingsh/privacy-friendly-analytics-setup-2m7h</guid>
      <description>&lt;h2&gt;
  
  
  Why Privacy-Friendly Analytics Matter
&lt;/h2&gt;

&lt;p&gt;Google Analytics collects 72 data points per visitor. It tracks users across sites, builds advertising profiles, and requires cookie consent banners that annoy everyone and tank your conversion rates. Since GDPR enforcement began, running GA4 without a consent banner is a legal risk in the EU. Running it with a banner means 30-50% of visitors decline cookies and disappear from your data entirely.&lt;/p&gt;

&lt;p&gt;Privacy-friendly analytics solve all three problems at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No cookies.&lt;/strong&gt; No consent banner needed. No GDPR headaches. You see 100% of your traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You own the data.&lt;/strong&gt; Visitor data never leaves your server. No third party mines it for ad targeting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighter pages.&lt;/strong&gt; Google's &lt;code&gt;gtag.js&lt;/code&gt; is 28 KB gzipped and makes multiple network requests. Plausible's script is under 1 KB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tradeoff is real: you lose user-level tracking, cross-site attribution, and the deep integration Google Analytics has with Google Ads. If you depend on those for paid acquisition campaigns, privacy-friendly analytics are a complement, not a replacement. For content sites, blogs, documentation, and most self-hosted projects, they are strictly better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Data You Actually Need
&lt;/h2&gt;

&lt;p&gt;Most site owners use less than 5% of what GA4 collects. Here is what matters for a content site:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Privacy Analytics&lt;/th&gt;
&lt;th&gt;GA4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pageviews&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique visitors (anonymized)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traffic sources / referrers&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top pages&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Country-level location&lt;/td&gt;
&lt;td&gt;Yes (IP-based, not stored)&lt;/td&gt;
&lt;td&gt;Yes (cookie-based)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device / browser / OS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bounce rate&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session duration&lt;/td&gt;
&lt;td&gt;Varies by tool&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTM campaign tracking&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-level journey tracking&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-domain tracking&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversion funnels&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Ads integration&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the "No" column does not affect your business, self-hosted privacy analytics give you everything you need with none of the baggage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Lightest Options Compared
&lt;/h2&gt;

&lt;p&gt;Three tools dominate the self-hosted privacy analytics space. Each takes a different approach.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Plausible CE&lt;/th&gt;
&lt;th&gt;Umami&lt;/th&gt;
&lt;th&gt;GoAccess&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approach&lt;/td&gt;
&lt;td&gt;JavaScript snippet&lt;/td&gt;
&lt;td&gt;JavaScript snippet&lt;/td&gt;
&lt;td&gt;Server log parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cookie-free&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (no JS at all)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDPR compliant without consent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time dashboard&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script size&lt;/td&gt;
&lt;td&gt;&amp;lt;1 KB&lt;/td&gt;
&lt;td&gt;~2 KB&lt;/td&gt;
&lt;td&gt;N/A (no script)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL + ClickHouse&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;None (flat files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM usage (idle)&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;~50 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom events&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited (JSON export)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (separate configs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker support&lt;/td&gt;
&lt;td&gt;Official&lt;/td&gt;
&lt;td&gt;Official&lt;/td&gt;
&lt;td&gt;Official&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latest version&lt;/td&gt;
&lt;td&gt;v3.2.0&lt;/td&gt;
&lt;td&gt;v3.0.3&lt;/td&gt;
&lt;td&gt;1.10.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Simplicity, drop-in GA replacement&lt;/td&gt;
&lt;td&gt;Customization, multi-site dashboards&lt;/td&gt;
&lt;td&gt;Zero-JS, minimal infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Use &lt;a href="https://dev.to/apps/plausible/"&gt;Plausible&lt;/a&gt; if you want the simplest path from GA4 to privacy analytics. Use &lt;a href="https://dev.to/apps/umami/"&gt;Umami&lt;/a&gt; if you want more control over dashboards and event tracking. Use &lt;a href="https://dev.to/apps/goaccess/"&gt;GoAccess&lt;/a&gt; if you want zero JavaScript on your site and already have access logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Linux server with Docker and Docker Compose installed (&lt;a href="https://dev.to/foundations/docker-compose-basics/"&gt;Docker Compose Basics&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A domain or subdomain for your analytics instance (e.g., &lt;code&gt;analytics.example.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;1 GB RAM minimum for Umami, 2 GB for Plausible (ClickHouse is hungry)&lt;/li&gt;
&lt;li&gt;A reverse proxy for HTTPS termination (&lt;a href="https://dev.to/foundations/reverse-proxy-explained/"&gt;Reverse Proxy Setup&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 1: Plausible Community Edition
&lt;/h2&gt;

&lt;p&gt;Plausible is the closest thing to a drop-in GA4 replacement. The dashboard is clean, opinionated, and shows exactly what you need on a single page. No training required — anyone on your team can read it.&lt;/p&gt;

&lt;p&gt;Plausible CE requires three services: the Plausible application, PostgreSQL for metadata, and ClickHouse for analytics event data.&lt;/p&gt;

&lt;p&gt;Create a project directory and the required files:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/plausible &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /opt/plausible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ClickHouse Configuration
&lt;/h3&gt;

&lt;p&gt;ClickHouse needs a few config tweaks for low-resource operation. Create these files before starting the stack.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;clickhouse/clickhouse-config.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;clickhouse&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;logger&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;level&amp;gt;&lt;/span&gt;warning&lt;span class="nt"&gt;&amp;lt;/level&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;console&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/console&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/logger&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;listen_host&amp;gt;&lt;/span&gt;0.0.0.0&lt;span class="nt"&gt;&amp;lt;/listen_host&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;http_port&amp;gt;&lt;/span&gt;8123&lt;span class="nt"&gt;&amp;lt;/http_port&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tcp_port&amp;gt;&lt;/span&gt;9000&lt;span class="nt"&gt;&amp;lt;/tcp_port&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;profiles&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;log_queries&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/log_queries&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;log_query_threads&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/log_query_threads&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/profiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/clickhouse&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;clickhouse/clickhouse-user-config.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;clickhouse&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;listen_host&amp;gt;&lt;/span&gt;0.0.0.0&lt;span class="nt"&gt;&amp;lt;/listen_host&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/clickhouse&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Compose
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;docker-compose.yml&lt;/code&gt;:&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;plausible_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&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;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;db-data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=plausible-db-password&lt;/span&gt;  &lt;span class="c1"&gt;# Change this&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;plausible_events_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse/clickhouse-server:24.12-alpine&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;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;event-data:/var/lib/clickhouse&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;event-logs:/var/log/clickhouse-server&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro&lt;/span&gt;
    &lt;span class="na"&gt;ulimits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nofile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;soft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;262144&lt;/span&gt;
        &lt;span class="na"&gt;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;262144&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wget&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--no-verbose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--tries=1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--spider&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://localhost:8123/ping&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;plausible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/plausible/community-edition:v3.2.0&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;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh -c "/entrypoint.sh db createdb &amp;amp;&amp;amp; /entrypoint.sh db migrate &amp;amp;&amp;amp; /entrypoint.sh run"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;plausible_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;plausible_events_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&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;8000:8000"&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;plausible-data:/var/lib/plausible&lt;/span&gt;
    &lt;span class="na"&gt;ulimits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nofile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;soft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65535&lt;/span&gt;
        &lt;span class="na"&gt;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65535&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BASE_URL=https://analytics.example.com&lt;/span&gt;  &lt;span class="c1"&gt;# Your analytics domain — MUST change&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY_BASE=REPLACE_WITH_64_BYTE_SECRET&lt;/span&gt;  &lt;span class="c1"&gt;# Generate with: openssl rand -base64 48&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:plausible-db-password@plausible_db:5432/plausible_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CLICKHOUSE_DATABASE_URL=http://plausible_events_db:8123/plausible_events_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DISABLE_REGISTRATION=invite_only&lt;/span&gt;  &lt;span class="c1"&gt;# Set to 'true' after creating your account&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MAILER_EMAIL=analytics@example.com&lt;/span&gt;  &lt;span class="c1"&gt;# From address for emails&lt;/span&gt;
      &lt;span class="c1"&gt;# Uncomment and configure for email (account creation, reports):&lt;/span&gt;
      &lt;span class="c1"&gt;# - SMTP_HOST_ADDR=smtp.example.com&lt;/span&gt;
      &lt;span class="c1"&gt;# - SMTP_HOST_PORT=587&lt;/span&gt;
      &lt;span class="c1"&gt;# - SMTP_USER_NAME=your-smtp-user&lt;/span&gt;
      &lt;span class="c1"&gt;# - SMTP_USER_PWD=your-smtp-password&lt;/span&gt;
      &lt;span class="c1"&gt;# - SMTP_HOST_SSL_ENABLED=true&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;event-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;event-logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;plausible-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate the Secret Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 48
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the output and replace &lt;code&gt;REPLACE_WITH_64_BYTE_SECRET&lt;/code&gt; in the compose file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Plausible
&lt;/h3&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;The first startup takes 30-60 seconds as ClickHouse initializes and migrations run. Check logs:&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 logs &lt;span class="nt"&gt;-f&lt;/span&gt; plausible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you see &lt;code&gt;[info] Running Plausible on port 8000&lt;/code&gt;, the instance is ready at &lt;code&gt;http://your-server-ip:8000&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your Plausible instance in a browser&lt;/li&gt;
&lt;li&gt;Create your admin account (the first account becomes the owner)&lt;/li&gt;
&lt;li&gt;Add your site domain&lt;/li&gt;
&lt;li&gt;Copy the tracking script (covered below in "Adding the Tracking Script")&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;DISABLE_REGISTRATION=true&lt;/code&gt; in the compose file and restart to lock down signups&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Option 2: Umami
&lt;/h2&gt;

&lt;p&gt;Umami is lighter than Plausible (no ClickHouse), offers more dashboard customization, and has a built-in API for pulling data into other tools. It supports custom events, multiple dashboards per site, and ad-blocker evasion through script/endpoint renaming.&lt;/p&gt;

&lt;p&gt;Umami needs two services: the Node.js application and PostgreSQL.&lt;/p&gt;

&lt;p&gt;Create a project directory:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/umami &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /opt/umami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Compose
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;docker-compose.yml&lt;/code&gt;:&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;umami&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/umami-software/umami:v3.0.3&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;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;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://umami:change-this-password@umami_db:5432/umami&lt;/span&gt;
      &lt;span class="na"&gt;APP_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_RANDOM_STRING&lt;/span&gt;  &lt;span class="c1"&gt;# Min 32 chars. Generate with: openssl rand -hex 32&lt;/span&gt;
      &lt;span class="c1"&gt;# Rename tracking paths to bypass ad blockers (optional):&lt;/span&gt;
      &lt;span class="c1"&gt;# TRACKER_SCRIPT_NAME: custom-script-name&lt;/span&gt;
      &lt;span class="c1"&gt;# COLLECT_API_ENDPOINT: /api/custom-endpoint&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;umami_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-f&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/api/heartbeat&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;init&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;umami_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&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;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;umami-db-data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;change-this-password&lt;/span&gt;  &lt;span class="c1"&gt;# Must match DATABASE_URL above&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;umami"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;umami-db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate the App Secret
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;REPLACE_WITH_RANDOM_STRING&lt;/code&gt; in the compose file with the output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Umami
&lt;/h3&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;Umami starts faster than Plausible — typically under 15 seconds. Check logs:&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 logs &lt;span class="nt"&gt;-f&lt;/span&gt; umami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once healthy, Umami is available at &lt;code&gt;http://your-server-ip:3000&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in with the default credentials: &lt;strong&gt;admin&lt;/strong&gt; / &lt;strong&gt;umami&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Change the admin password immediately (Settings &amp;gt; Profile)&lt;/li&gt;
&lt;li&gt;Add your website (Settings &amp;gt; Websites &amp;gt; Add Website)&lt;/li&gt;
&lt;li&gt;Copy the tracking script from the website settings page&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Bypassing Ad Blockers
&lt;/h3&gt;

&lt;p&gt;Many ad blockers target analytics scripts by URL pattern. Umami lets you rename the script and collection endpoint:&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;TRACKER_SCRIPT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom-data&lt;/span&gt;
  &lt;span class="na"&gt;COLLECT_API_ENDPOINT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/custom-collect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, the tracking script URL becomes &lt;code&gt;/custom-data.js&lt;/code&gt; instead of the default &lt;code&gt;/script.js&lt;/code&gt;, which ad blockers are less likely to block. Proxy the analytics subdomain through your main domain's reverse proxy for even better results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: GoAccess (No JavaScript)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/apps/goaccess/"&gt;GoAccess&lt;/a&gt; takes a fundamentally different approach: it parses your web server's access logs instead of injecting JavaScript. Zero scripts on your site. Zero impact on page load. No data sent to any external server — not even your own analytics instance.&lt;/p&gt;

&lt;p&gt;The tradeoff: GoAccess cannot track client-side events, single-page app navigation, or JavaScript-dependent metrics. It sees what your web server sees — HTTP requests. For static sites and server-rendered pages, this is often enough.&lt;/p&gt;

&lt;p&gt;GoAccess runs as a single binary with no database. Feed it a log file and it produces a real-time HTML dashboard or terminal UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Docker Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; /var/log/nginx:/var/log/nginx:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /opt/goaccess/data:/srv/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /opt/goaccess/html:/srv/report &lt;span class="se"&gt;\&lt;/span&gt;
  allinurl/goaccess:1.10.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--log-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/srv/report/index.html &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--real-time-html&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ws-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wss://stats.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7890
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For persistent operation, create a &lt;code&gt;docker-compose.yml&lt;/code&gt;:&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;goaccess&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allinurl/goaccess:1.10.1&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;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;7890:7890"&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;/var/log/nginx:/var/log/nginx:ro&lt;/span&gt;  &lt;span class="c1"&gt;# Mount your web server logs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;goaccess-data:/srv/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;goaccess-html:/srv/report&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;--log-file=/var/log/nginx/access.log&lt;/span&gt;
      &lt;span class="s"&gt;--log-format=COMBINED&lt;/span&gt;
      &lt;span class="s"&gt;--output=/srv/report/index.html&lt;/span&gt;
      &lt;span class="s"&gt;--real-time-html&lt;/span&gt;
      &lt;span class="s"&gt;--ws-url=wss://stats.example.com&lt;/span&gt;
      &lt;span class="s"&gt;--port=7890&lt;/span&gt;
      &lt;span class="s"&gt;--persist&lt;/span&gt;
      &lt;span class="s"&gt;--restore&lt;/span&gt;
      &lt;span class="s"&gt;--db-path=/srv/data&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;goaccess-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;goaccess-html&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Serve the generated &lt;code&gt;index.html&lt;/code&gt; from the &lt;code&gt;goaccess-html&lt;/code&gt; volume through your reverse proxy, and use WebSocket passthrough for real-time updates.&lt;/p&gt;

&lt;p&gt;GoAccess is best suited for operators who want analytics without touching their frontend code. For most self-hosters running content sites, Plausible or Umami will be more practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Tracking Script
&lt;/h2&gt;

&lt;p&gt;Both Plausible and Umami work by adding a single &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to your site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plausible
&lt;/h3&gt;

&lt;p&gt;Add this to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of every page:&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;defer&lt;/span&gt; &lt;span class="na"&gt;data-domain=&lt;/span&gt;&lt;span class="s"&gt;"yoursite.com"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://analytics.example.com/js/script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;yoursite.com&lt;/code&gt; with the domain you registered in Plausible and &lt;code&gt;analytics.example.com&lt;/code&gt; with your Plausible instance URL.&lt;/p&gt;

&lt;p&gt;Plausible offers script extensions for additional tracking:&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="c"&gt;&amp;lt;!-- Track outbound link clicks --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;data-domain=&lt;/span&gt;&lt;span class="s"&gt;"yoursite.com"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://analytics.example.com/js/script.outbound-links.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Track file downloads --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;data-domain=&lt;/span&gt;&lt;span class="s"&gt;"yoursite.com"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://analytics.example.com/js/script.file-downloads.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Combine multiple extensions --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;data-domain=&lt;/span&gt;&lt;span class="s"&gt;"yoursite.com"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://analytics.example.com/js/script.outbound-links.file-downloads.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Umami
&lt;/h3&gt;

&lt;p&gt;Add this to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of every page:&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;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://analytics.example.com/script.js"&lt;/span&gt; &lt;span class="na"&gt;data-website-id=&lt;/span&gt;&lt;span class="s"&gt;"YOUR-WEBSITE-ID"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;data-website-id&lt;/code&gt; is a UUID generated when you add the site in Umami's dashboard.&lt;/p&gt;

&lt;p&gt;For custom event tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Track a button click&lt;/span&gt;
&lt;span class="nx"&gt;umami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup-button-click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Track with properties&lt;/span&gt;
&lt;span class="nx"&gt;umami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download&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="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docker-compose.yml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yaml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reverse Proxy Configuration
&lt;/h2&gt;

&lt;p&gt;Run your analytics instance behind a reverse proxy on a subdomain like &lt;code&gt;analytics.example.com&lt;/code&gt; or &lt;code&gt;stats.example.com&lt;/code&gt;. This gives you HTTPS and keeps the analytics service isolated.&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://dev.to/foundations/nginx-proxy-manager-setup/"&gt;Nginx Proxy Manager&lt;/a&gt;, create a new proxy host pointing to your analytics container's port (8000 for Plausible, 3000 for Umami). Enable SSL with Let's Encrypt.&lt;/p&gt;

&lt;p&gt;For Caddy, add to your &lt;code&gt;Caddyfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;analytics.example.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;reverse_proxy&lt;/span&gt; &lt;span class="nf"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;  &lt;span class="c1"&gt;# or :3000 for Umami&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;analytics.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/analytics.example.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/analytics.example.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# or :3000 for Umami&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# WebSocket support (needed for Plausible real-time)&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/live/websocket&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://dev.to/foundations/reverse-proxy-explained/"&gt;Reverse Proxy Setup&lt;/a&gt; for detailed configuration guides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Impact
&lt;/h2&gt;

&lt;p&gt;Privacy-friendly analytics scripts are dramatically smaller than Google's.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Script&lt;/th&gt;
&lt;th&gt;Transfer Size&lt;/th&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;th&gt;Blocking Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Analytics (gtag.js)&lt;/td&gt;
&lt;td&gt;~28 KB gzipped&lt;/td&gt;
&lt;td&gt;2-3&lt;/td&gt;
&lt;td&gt;~50-80 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plausible&lt;/td&gt;
&lt;td&gt;~0.8 KB gzipped&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&amp;lt;5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Umami&lt;/td&gt;
&lt;td&gt;~2 KB gzipped&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&amp;lt;5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoAccess&lt;/td&gt;
&lt;td&gt;0 KB (no script)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both Plausible and Umami use the &lt;code&gt;defer&lt;/code&gt; attribute, meaning the script loads asynchronously and never blocks page rendering. The performance difference is negligible on modern connections but adds up across millions of pageviews — fewer bytes served, lower CDN costs, faster Time to Interactive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Running ClickHouse on a 1 GB RAM server.&lt;/strong&gt; Plausible requires ClickHouse, which allocates significant memory at startup. Budget at least 2 GB total RAM for a Plausible stack. If you only have 1 GB, use Umami instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to set DISABLE_REGISTRATION after creating your account.&lt;/strong&gt; Both tools allow open registration by default. Lock this down immediately after creating your admin account or anyone who finds your analytics URL can create an account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the old Plausible Docker Hub image.&lt;/strong&gt; The &lt;code&gt;plausible/analytics&lt;/code&gt; image on Docker Hub is frozen at v2.0.0 (July 2023). The current image is &lt;code&gt;ghcr.io/plausible/community-edition&lt;/code&gt; on GitHub Container Registry. Using the old image means missing two years of features and security fixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not configuring IP anonymization.&lt;/strong&gt; Both tools hash or discard IP addresses by default, but verify this in your configuration. Storing raw IP addresses, even on your own server, has GDPR implications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exposing the analytics port directly.&lt;/strong&gt; Always put your analytics instance behind a reverse proxy with HTTPS. Running on a bare HTTP port means your tracking data transits the network unencrypted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Deploy your chosen analytics tool using the Docker Compose configs above&lt;/li&gt;
&lt;li&gt;Add the tracking script to your site and verify data is flowing&lt;/li&gt;
&lt;li&gt;Set up a reverse proxy with HTTPS for your analytics subdomain&lt;/li&gt;
&lt;li&gt;Configure email (SMTP) if you want weekly reports from Plausible&lt;/li&gt;
&lt;li&gt;Explore custom events in &lt;a href="https://dev.to/apps/umami/"&gt;Umami&lt;/a&gt; for tracking specific user actions&lt;/li&gt;
&lt;li&gt;For a deeper comparison, see &lt;a href="https://dev.to/compare/plausible-vs-umami/"&gt;Plausible vs Umami&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/plausible/"&gt;How to Self-Host Plausible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/umami/"&gt;How to Self-Host Umami&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/goaccess/"&gt;How to Self-Host GoAccess&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/matomo/"&gt;How to Self-Host Matomo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/plausible-vs-umami/"&gt;Plausible vs Umami&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/google-analytics/"&gt;Self-Hosted Alternatives to Google Analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/analytics/"&gt;Best Self-Hosted Analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/reverse-proxy-explained/"&gt;Reverse Proxy Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/docker-compose-basics/"&gt;Docker Compose Basics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>analytics</category>
      <category>performance</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ghostfolio Vs Maybe</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 02:58:04 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/ghostfolio-vs-maybe-2ma1</link>
      <guid>https://forem.com/selfhostingsh/ghostfolio-vs-maybe-2ma1</guid>
      <description>&lt;p&gt;Ghostfolio is a purpose-built portfolio tracker that pulls live market data, calculates performance metrics, and actively receives updates. Maybe aimed to combine budgeting and investment tracking but was archived in July 2025 after the founding company shut down. For self-hosted investment tracking in 2026, the choice is straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Ghostfolio focuses specifically on investment portfolio management — tracking stocks, ETFs, bonds, crypto, and other assets with automatic price updates via Yahoo Finance and other data providers. It shows allocation breakdowns, performance charts, dividend tracking, and net worth over time. Built with NestJS and Angular.&lt;/p&gt;

&lt;p&gt;Maybe was a broader personal finance app covering budgeting, transaction tracking, and investment monitoring. It was built by a venture-backed startup that pivoted to open source before shutting down. The v0.6.0 release in July 2025 was the last, and the GitHub repository is archived.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Ghostfolio&lt;/th&gt;
&lt;th&gt;Maybe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Focus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Investment portfolio tracking&lt;/td&gt;
&lt;td&gt;All-in-one personal finance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ghostfolio/ghostfolio:2.250.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ghcr.io/maybe-finance/maybe:0.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript (NestJS + Angular)&lt;/td&gt;
&lt;td&gt;Ruby on Rails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Actively maintained&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (weekly releases)&lt;/td&gt;
&lt;td&gt;No (archived July 2025)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Market data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yahoo Finance, manual, data providers&lt;/td&gt;
&lt;td&gt;None (manual entry only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asset types&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stocks, ETFs, bonds, crypto, commodities, real estate&lt;/td&gt;
&lt;td&gt;Stocks, crypto, real estate (basic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TWR, MWR, dividends, fees&lt;/td&gt;
&lt;td&gt;Basic balance tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Allocation analysis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;By asset class, region, sector, account&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-currency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (automatic conversion)&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL + Redis&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~200-350 MB&lt;/td&gt;
&lt;td&gt;~300-500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Benchmark comparison&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (compare against S&amp;amp;P 500, etc.)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Installation Complexity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ghostfolio
&lt;/h3&gt;

&lt;p&gt;Three containers — app, PostgreSQL, Redis:&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;ghostfolio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostfolio/ghostfolio:2.250.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostfolio&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://ghostfolio:changeme-gf-db-pass@ghostfolio-db:5432/ghostfolio&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST=ghostfolio-redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_PORT=6379&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ACCESS_TOKEN_SALT=generate-random-string-here&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;JWT_SECRET_KEY=generate-another-random-string-here&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;3333:3333"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ghostfolio-db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ghostfolio-redis&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;span class="na"&gt;ghostfolio-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostfolio-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=ghostfolio&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=ghostfolio&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=changeme-gf-db-pass&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;./db:/var/lib/postgresql/data&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;span class="na"&gt;ghostfolio-redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostfolio-redis&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;Default login: &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;Admin1234&lt;/code&gt; — change immediately after first login.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maybe
&lt;/h3&gt;

&lt;p&gt;Two containers — app and PostgreSQL:&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;maybe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/maybe-finance/maybe:0.6.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;maybe&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_ENV=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY_BASE=generate-a-64-char-hex-string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_HOST=maybe-db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PORT=5432&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_NAME=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_USERNAME=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD=changeme-maybe-db-pass&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;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;maybe-db&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;span class="na"&gt;maybe-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;maybe-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=changeme-maybe-db-pass&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;./db:/var/lib/postgresql/data&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;h2&gt;
  
  
  Performance and Resource Usage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Ghostfolio&lt;/th&gt;
&lt;th&gt;Maybe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Containers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3 (app + PostgreSQL + Redis)&lt;/td&gt;
&lt;td&gt;2 (app + PostgreSQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;~300 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (under load)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~350 MB&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~300 MB&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~10 seconds&lt;/td&gt;
&lt;td&gt;~15-20 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Market data updates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatic (configurable interval)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ghostfolio's Redis dependency adds a container but barely impacts resources (~20 MB). Maybe's Rails stack is heavier despite doing less.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Maintenance Reality
&lt;/h2&gt;

&lt;p&gt;Ghostfolio ships weekly releases with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New data provider integrations&lt;/li&gt;
&lt;li&gt;Performance improvements&lt;/li&gt;
&lt;li&gt;Bug fixes and security patches&lt;/li&gt;
&lt;li&gt;New asset type support&lt;/li&gt;
&lt;li&gt;UI improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maybe has received zero updates since July 2025. Known issues include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No security patches for Ruby on Rails dependencies&lt;/li&gt;
&lt;li&gt;Gem vulnerabilities accumulating over time&lt;/li&gt;
&lt;li&gt;No community maintaining the fork&lt;/li&gt;
&lt;li&gt;API integrations (if any) breaking as external services change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running Maybe means accepting increasing security risk with each passing month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Ghostfolio If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want automatic market data updates for your portfolio&lt;/li&gt;
&lt;li&gt;Performance metrics (TWR, dividends, fees) matter to you&lt;/li&gt;
&lt;li&gt;You want allocation analysis by sector, region, and asset class&lt;/li&gt;
&lt;li&gt;Long-term support and regular updates are important&lt;/li&gt;
&lt;li&gt;You want to benchmark your portfolio against indices&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Maybe If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want a combined budgeting + investment view (and accept unmaintained software)&lt;/li&gt;
&lt;li&gt;You plan to fork and maintain the codebase yourself&lt;/li&gt;
&lt;li&gt;You only need basic balance tracking without live market data&lt;/li&gt;
&lt;li&gt;You're using it as a starting point for a custom Rails app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;If you need investment tracking, choose Ghostfolio — there's no practical alternative here. The question isn't which is better; it's whether you want maintained software with live market data or an abandoned project with manual entry only. Ghostfolio paired with &lt;a href="https://dev.to/apps/actual-budget"&gt;Actual Budget&lt;/a&gt; covers both budgeting and investment tracking better than Maybe ever did alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can Ghostfolio track real estate?
&lt;/h3&gt;

&lt;p&gt;Yes. Add real estate as a manual asset with periodic value updates. It won't pull Zillow estimates automatically, but you can record your property's value and track it alongside your financial portfolio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Maybe support automatic market data?
&lt;/h3&gt;

&lt;p&gt;The self-hosted version does not. The original SaaS product had Plaid integration, but that was never included in the open-source release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run both and migrate later?
&lt;/h3&gt;

&lt;p&gt;Yes, but there's no built-in migration path. You'd export Maybe's data as CSV and manually re-enter positions in Ghostfolio. For new users, start with Ghostfolio directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/maybe/"&gt;How to Self-Host Maybe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/ghostfolio"&gt;How to Self-Host Ghostfolio with Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/ghostfolio-vs-firefly-iii"&gt;Ghostfolio vs Firefly III&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/ghostfolio-vs-actual-budget"&gt;Ghostfolio vs Actual Budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/personal-finance"&gt;Best Self-Hosted Personal Finance Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/actual-budget-vs-maybe"&gt;Actual Budget vs Maybe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/ynab"&gt;Self-Hosted Alternatives to YNAB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>news</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hoarder Vs Shiori</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Fri, 20 Mar 2026 01:18:03 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/hoarder-vs-shiori-2nnl</link>
      <guid>https://forem.com/selfhostingsh/hoarder-vs-shiori-2nnl</guid>
      <description>&lt;h2&gt;
  
  
  Quick Verdict
&lt;/h2&gt;

&lt;p&gt;Hoarder is the better choice if you save a lot of content and want AI to automatically tag and categorize it. Shiori is the better choice if you want a minimal, lightweight bookmark manager that archives full pages and runs on almost any hardware. They represent opposite ends of the bookmark manager spectrum — smart automation vs. bare-bones efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hoarder&lt;/strong&gt; is a relatively new bookmark manager that uses AI (via local models or OpenAI) to automatically tag and organize saved links. It focuses on the "save now, organize later" workflow — you dump links and let the AI figure out the categorization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shiori&lt;/strong&gt; is a Go-based bookmark manager inspired by the old Pocket experience. It's a single binary with embedded SQLite — no external dependencies, no AI, no frills. You save pages, it archives the content for offline reading, and you tag things manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Hoarder&lt;/th&gt;
&lt;th&gt;Shiori&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript (Next.js)&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQLite or PostgreSQL&lt;/td&gt;
&lt;td&gt;SQLite, PostgreSQL, or MySQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI tagging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (local or OpenAI)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-categorization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (AI-powered)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Page archiving&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Screenshots + text extraction&lt;/td&gt;
&lt;td&gt;Full readable content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser extensions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chrome, Firefox&lt;/td&gt;
&lt;td&gt;Chrome (beta), Firefox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile app&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PWA + iOS/Android (beta)&lt;/td&gt;
&lt;td&gt;PWA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full-text search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pocket import&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (Netscape format)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-user&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~500 MB+&lt;/td&gt;
&lt;td&gt;~30 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum RAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~512 MB&lt;/td&gt;
&lt;td&gt;~30-50 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Installation Complexity
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hoarder&lt;/strong&gt; requires more infrastructure. The Docker Compose includes the main app, a Chromium container for page screenshots, and optionally a local AI model (Ollama). The stack uses 500+ MB of RAM at minimum, and more with local AI inference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shiori&lt;/strong&gt; runs as a single container with an embedded SQLite database. No external services needed. Total footprint is ~30-50 MB of RAM. You can run it on a Raspberry Pi Zero without issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Shiori.&lt;/strong&gt; Dramatically simpler setup and smaller footprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and Resource Usage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Hoarder&lt;/th&gt;
&lt;th&gt;Shiori&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~300-500 MB (with Chromium)&lt;/td&gt;
&lt;td&gt;~30-50 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (with AI)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~2-4 GB (with local Ollama)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate (AI inference, rendering)&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Screenshots + DB&lt;/td&gt;
&lt;td&gt;Archived text + DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10-30 seconds&lt;/td&gt;
&lt;td&gt;&amp;lt;2 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hoarder is 10-15x heavier on RAM due to the Chromium rendering engine and optional AI backend. If you're running on limited hardware or alongside many other services, this matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Shiori.&lt;/strong&gt; Runs on almost any hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Archiving
&lt;/h2&gt;

&lt;p&gt;Both save page content, but differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hoarder&lt;/strong&gt; takes screenshots (visual snapshots) and extracts text. The screenshots provide a visual reference but can't be searched. Text extraction enables full-text search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shiori&lt;/strong&gt; downloads and parses the full page into readable content (similar to Reader View in Firefox). The result is searchable text with formatting preserved, available for offline reading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Winner: Shiori&lt;/strong&gt; for offline reading and searchability. &lt;strong&gt;Hoarder&lt;/strong&gt; for visual reference (screenshots show the exact page layout).&lt;/p&gt;

&lt;h2&gt;
  
  
  Organization
&lt;/h2&gt;

&lt;p&gt;This is where they diverge most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hoarder's AI tagging&lt;/strong&gt; analyzes saved content and applies tags automatically. You configure your preferred AI backend (OpenAI or local Ollama) and the AI processes each bookmark. The more you save, the more useful the automatic organization becomes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shiori's manual tagging&lt;/strong&gt; requires you to add tags yourself when saving or editing bookmarks. It's predictable and doesn't require AI infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Winner: Hoarder&lt;/strong&gt; if you save dozens of links daily and don't want to categorize each one. &lt;strong&gt;Shiori&lt;/strong&gt; if you prefer manual control and don't want AI dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community and Support
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Hoarder&lt;/th&gt;
&lt;th&gt;Shiori&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub stars&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4,000&lt;/td&gt;
&lt;td&gt;~11,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Development pace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very active&lt;/td&gt;
&lt;td&gt;Steady (less frequent releases)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contributors&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Growing&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Shiori has a longer track record and larger community. Hoarder is newer but developing rapidly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Hoarder If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You save large volumes of content and hate manual tagging&lt;/li&gt;
&lt;li&gt;You already run Ollama or have OpenAI API access&lt;/li&gt;
&lt;li&gt;You want screenshot previews of saved pages&lt;/li&gt;
&lt;li&gt;You prefer a modern Next.js UI&lt;/li&gt;
&lt;li&gt;You have 512 MB+ RAM available for bookmark management&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Shiori If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want the lightest possible bookmark manager&lt;/li&gt;
&lt;li&gt;You run on limited hardware (Raspberry Pi, small VPS)&lt;/li&gt;
&lt;li&gt;You prefer offline-first archiving (full readable content)&lt;/li&gt;
&lt;li&gt;You want a CLI for scriptable bookmark management&lt;/li&gt;
&lt;li&gt;You don't need AI features&lt;/li&gt;
&lt;li&gt;You value a battle-tested, mature project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;For most users saving bookmarks casually (10-20 per week), &lt;strong&gt;Shiori&lt;/strong&gt; is the better choice — it does the job with minimal resources and no complexity. The full content archiving is genuinely useful for offline reading.&lt;/p&gt;

&lt;p&gt;For power users who save 50+ links weekly and need help organizing them, &lt;strong&gt;Hoarder&lt;/strong&gt; justifies its higher resource cost with AI tagging that saves real time. If you already run an AI stack, the marginal cost of adding Hoarder is minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Hoarder require a GPU for AI tagging?
&lt;/h3&gt;

&lt;p&gt;No. Hoarder's AI tagging works with cloud APIs (OpenAI, Ollama) or local CPU-based models. A GPU accelerates local model inference but is not required. With Ollama on CPU, tagging takes a few seconds per bookmark instead of sub-second with a GPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Shiori archive pages for offline reading?
&lt;/h3&gt;

&lt;p&gt;Yes. Shiori downloads and stores the full HTML content of saved pages. You can read archived pages even if the original site goes offline. The archive is stored locally in Shiori's database, not as separate files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Hoarder have a browser extension?
&lt;/h3&gt;

&lt;p&gt;Yes. Hoarder has browser extensions for Chrome and Firefox, plus a mobile app for iOS and Android. Shiori also has browser extensions but no official mobile app — you access it through the mobile browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much storage does each tool use?
&lt;/h3&gt;

&lt;p&gt;Shiori uses minimal storage — typically 10-50 MB for hundreds of bookmarks since it stores compressed HTML. Hoarder uses more storage due to AI embeddings and richer metadata — expect 100-500 MB for a similar collection, more if you enable screenshot archiving.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I import bookmarks from Pocket or Raindrop.io?
&lt;/h3&gt;

&lt;p&gt;Both support importing bookmarks from HTML files (the standard browser bookmark export format). Hoarder also supports direct Pocket import. Neither has a built-in Raindrop.io importer, but exporting from Raindrop as HTML works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which is better for a team or family?
&lt;/h3&gt;

&lt;p&gt;Hoarder supports multiple users with shared lists — better for collaborative use. Shiori is designed for single-user use. For shared bookmark management, also consider &lt;a href="https://dev.to/apps/linkwarden/"&gt;Linkwarden&lt;/a&gt; which has full team support with shared collections and permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/hoarder"&gt;How to Self-Host Hoarder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/shiori"&gt;How to Self-Host Shiori&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/bookmarks"&gt;Best Self-Hosted Bookmark Managers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/linkwarden-vs-hoarder"&gt;Linkwarden vs Hoarder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/pocket"&gt;Self-Hosted Alternatives to Pocket&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>go</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Wallabag Vs Pocket</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Thu, 19 Mar 2026 23:38:01 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/wallabag-vs-pocket-5dg3</link>
      <guid>https://forem.com/selfhostingsh/wallabag-vs-pocket-5dg3</guid>
      <description>&lt;h2&gt;
  
  
  Quick Verdict
&lt;/h2&gt;

&lt;p&gt;Wallabag is the best self-hosted alternative to Pocket. It replicates Pocket's core experience — save articles, read them later in a clean interface — while giving you full data ownership. The mobile apps are less polished than Pocket's, but the reading experience, tagging, and export capabilities are equivalent or better.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; Verified with latest Docker images and configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pocket&lt;/strong&gt; (owned by Mozilla since 2017) is the most popular read-later service. The free tier is generous, but the Premium plan ($44.99/year) adds full-text search, permanent library, and suggested tags. Pocket's main concern is privacy: it tracks reading habits, integrates with Mozilla's recommendation engine, and stores all your data on their servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wallabag&lt;/strong&gt; is a self-hosted read-later application written in PHP (Symfony). It extracts readable content from web pages, strips ads and clutter, and presents a clean reading interface. It includes browser extensions, mobile apps, and API access — all for $0 and full privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Pocket (Premium)&lt;/th&gt;
&lt;th&gt;Wallabag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$44.99/year&lt;/td&gt;
&lt;td&gt;Free (self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Article saving&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reader view&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (content extraction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full-text search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Premium only&lt;/td&gt;
&lt;td&gt;Yes (built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tagging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes + suggested tags (Premium)&lt;/td&gt;
&lt;td&gt;Yes (manual)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Permanent library&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Premium only&lt;/td&gt;
&lt;td&gt;Yes (all articles archived)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser extensions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chrome, Firefox, Safari&lt;/td&gt;
&lt;td&gt;Chrome, Firefox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile apps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;iOS, Android (polished)&lt;/td&gt;
&lt;td&gt;iOS, Android (functional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline reading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (mobile)&lt;/td&gt;
&lt;td&gt;Yes (mobile apps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RSS feed of saves&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (per-tag RSS feeds)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTML only&lt;/td&gt;
&lt;td&gt;JSON, CSV, XML, EPUB, PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (full REST API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Annotations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (text highlighting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Text-to-speech&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (Premium)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dark mode reading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sharing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;To social media&lt;/td&gt;
&lt;td&gt;Public links + social&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-user&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (per-account)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integrations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IFTTT, Zapier&lt;/td&gt;
&lt;td&gt;Many via API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data location&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mozilla's servers&lt;/td&gt;
&lt;td&gt;Your server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Reading Experience
&lt;/h2&gt;

&lt;p&gt;Both extract readable content from web pages. Pocket's extraction is slightly more reliable on complex sites (JavaScript-heavy pages, paywalled content), but Wallabag handles most sites well. Both offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adjustable font size and family&lt;/li&gt;
&lt;li&gt;Dark/light/sepia reading modes&lt;/li&gt;
&lt;li&gt;Estimated reading time&lt;/li&gt;
&lt;li&gt;Progress tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wallabag adds text &lt;strong&gt;annotations&lt;/strong&gt; (highlight passages and add notes) — a feature Pocket doesn't offer at any tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy
&lt;/h2&gt;

&lt;p&gt;This is the primary reason to switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pocket&lt;/strong&gt; tracks which articles you read, how long you spend reading, and what you save. This data feeds Mozilla's recommendation engine ("Pocket Recommendations" in Firefox). Pocket's privacy policy allows sharing usage data with third parties.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wallabag&lt;/strong&gt; stores everything on your server. No tracking, no recommendations engine, no data sharing. Your reading habits are yours alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mobile Experience
&lt;/h2&gt;

&lt;p&gt;Pocket's mobile apps are genuinely better — smoother animations, better content rendering, offline sync that "just works." Wallabag's mobile apps (available on both iOS and Android) are functional but feel like a web wrapper. They support offline reading, but the sync mechanism occasionally needs manual triggering.&lt;/p&gt;

&lt;p&gt;If mobile reading is your primary use case, this is the biggest trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation Complexity
&lt;/h2&gt;

&lt;p&gt;Wallabag requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.x with several extensions&lt;/li&gt;
&lt;li&gt;PostgreSQL, MySQL, or SQLite&lt;/li&gt;
&lt;li&gt;Redis or RabbitMQ (optional, for async processing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker Compose simplifies this:&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;wallabag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wallabag/wallabag:2.6.14&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wallabag&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DATABASE_DRIVER=pdo_pgsql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DATABASE_HOST=db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DATABASE_PORT=5432&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DATABASE_NAME=wallabag&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DATABASE_USER=wallabag&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DATABASE_PASSWORD=wallabag_password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__DOMAIN_NAME=https://read.yourdomain.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYMFONY__ENV__SECRET=change-this-to-a-random-string&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;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&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;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:17-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=wallabag&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=wallabag_password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=wallabag&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;wallabag_db:/var/lib/postgresql/data&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;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&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;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;wallabag_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migration from Pocket
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export from Pocket:&lt;/strong&gt; Go to &lt;code&gt;getpocket.com/export&lt;/code&gt; and download the HTML file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import to Wallabag:&lt;/strong&gt; In Wallabag, navigate to &lt;strong&gt;Import &amp;gt; Pocket&lt;/strong&gt; and upload the file&lt;/li&gt;
&lt;li&gt;Articles, tags, and read/unread status are preserved&lt;/li&gt;
&lt;li&gt;Wallabag re-fetches article content for its own archive&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wallabag also supports importing from Instapaper, Readability, and browser bookmarks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pocket Free&lt;/th&gt;
&lt;th&gt;Pocket Premium&lt;/th&gt;
&lt;th&gt;Wallabag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$3.75&lt;/td&gt;
&lt;td&gt;~$3 (VPS share)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Annual cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$44.99&lt;/td&gt;
&lt;td&gt;~$36 (VPS share)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-year cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$134.97&lt;/td&gt;
&lt;td&gt;~$108 (VPS share)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text search&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permanent copies&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data ownership&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;If you already run a homelab or VPS for other services, Wallabag's marginal cost is effectively $0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You Give Up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pocket's mobile app quality&lt;/strong&gt; — Pocket's apps are more polished and reliable for offline sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-to-speech&lt;/strong&gt; — Pocket Premium includes article narration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pocket Recommendations&lt;/strong&gt; — If you use Firefox's recommended articles feed, you lose that&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tag suggestions&lt;/strong&gt; — Pocket Premium suggests tags automatically; Wallabag requires manual tagging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero maintenance&lt;/strong&gt; — Self-hosting means managing updates, backups, and server uptime&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can Wallabag import my Pocket articles?
&lt;/h3&gt;

&lt;p&gt;Yes. Wallabag has a built-in Pocket import tool. Connect your Pocket account via OAuth, and Wallabag will import all your saved articles with tags and archive status preserved. The import takes a few minutes for large libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Wallabag have a mobile app?
&lt;/h3&gt;

&lt;p&gt;Yes. Wallabag has official apps for &lt;a href="https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche" rel="noopener noreferrer"&gt;Android&lt;/a&gt; and &lt;a href="https://apps.apple.com/app/wallabag-2-official/id1170800946" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;. The apps support offline reading and syncing. They are functional but less polished than Pocket's apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Wallabag hard to maintain?
&lt;/h3&gt;

&lt;p&gt;Wallabag is a PHP/Symfony application with a database (PostgreSQL, MySQL, or SQLite). Updates are straightforward with Docker (&lt;code&gt;docker compose pull &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt;). The main maintenance tasks are database backups and occasional PHP dependency updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Wallabag tag articles automatically?
&lt;/h3&gt;

&lt;p&gt;Not natively. Wallabag supports tagging rules — you can define rules like "if domain contains 'github.com', add tag 'dev'" — but it doesn't have AI-powered auto-tagging like &lt;a href="https://dev.to/apps/hoarder/"&gt;Hoarder&lt;/a&gt;. For manual tagging, the browser extension lets you add tags at save time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Wallabag's Kindle integration?
&lt;/h3&gt;

&lt;p&gt;Wallabag can send articles to your Kindle via Amazon's Send-to-Kindle email address. Configure your Kindle email in Wallabag settings, and articles are delivered in a Kindle-optimized format. This is one of the most popular features for e-reader users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Wallabag support RSS feeds?
&lt;/h3&gt;

&lt;p&gt;Yes. Each tag, starred collection, and archive in Wallabag has an RSS feed URL. You can subscribe to these feeds in your RSS reader to follow saved articles across tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/wallabag"&gt;How to Self-Host Wallabag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/bookmarks"&gt;Best Self-Hosted Bookmark Managers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/pocket"&gt;Self-Hosted Alternatives to Pocket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/instapaper"&gt;Self-Hosted Alternatives to Instapaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/linkwarden-vs-hoarder"&gt;Linkwarden vs Hoarder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/hoarder-vs-shiori"&gt;Hoarder vs Shiori&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Actual Budget Vs Maybe</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Thu, 19 Mar 2026 21:48:00 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/actual-budget-vs-maybe-1e5m</link>
      <guid>https://forem.com/selfhostingsh/actual-budget-vs-maybe-1e5m</guid>
      <description>&lt;p&gt;Want a self-hosted finance app that actively gets better? Actual Budget is the only serious choice between these two. Maybe showed promise as a modern portfolio tracker, but development stopped in mid-2025 and the project is no longer maintained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Actual Budget is an envelope budgeting app — you assign every dollar a purpose, track spending against categories, and sync across devices. It started as a paid SaaS product, went open source in 2022, and now has an active community maintaining it. The sync server is lightweight and the web app works offline.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; Verified with latest Docker images and configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe was a modern personal finance tracker that aimed to combine budgeting, investment tracking, and net worth monitoring in one app. Built with Ruby on Rails by a funded startup, it was open-sourced after the company shut down. The last release (v0.6.0) shipped in July 2025, and the repository is now archived.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Actual Budget&lt;/th&gt;
&lt;th&gt;Maybe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary focus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Envelope budgeting&lt;/td&gt;
&lt;td&gt;Portfolio + net worth tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actualbudget/actual-server:26.3.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ghcr.io/maybe-finance/maybe:0.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;Ruby on Rails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Actively maintained&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (weekly updates)&lt;/td&gt;
&lt;td&gt;No (archived July 2025)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQLite (built-in)&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bank syncing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GoCardless (EU), SimpleFIN (US)&lt;/td&gt;
&lt;td&gt;None in self-hosted version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (local-first architecture)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-device sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (via sync server)&lt;/td&gt;
&lt;td&gt;Yes (web app)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Investment tracking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~50-80 MB&lt;/td&gt;
&lt;td&gt;~300-500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile app&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PWA (installable)&lt;/td&gt;
&lt;td&gt;Responsive web only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Import formats&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OFX, QFX, QIF, CSV&lt;/td&gt;
&lt;td&gt;CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Installation Complexity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Actual Budget
&lt;/h3&gt;

&lt;p&gt;One container, no external database needed. SQLite handles everything:&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;actual-budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actualbudget/actual-server:26.3.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actual-budget&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;5006:5006"&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:/data&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;Start it, open the web UI, create a budget file. Working in under 60 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maybe
&lt;/h3&gt;

&lt;p&gt;Requires PostgreSQL and more configuration:&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;maybe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/maybe-finance/maybe:0.6.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;maybe&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_ENV=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY_BASE=generate-a-64-char-hex-string-here&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_HOST=maybe-db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PORT=5432&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_NAME=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_USERNAME=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD=changeme-maybe-db-pass&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;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;maybe-db&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;span class="na"&gt;maybe-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;maybe-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=maybe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=changeme-maybe-db-pass&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;./db:/var/lib/postgresql/data&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;Default credentials: &lt;code&gt;user@maybe.local&lt;/code&gt; / &lt;code&gt;password&lt;/code&gt;. Functional, but expect no bug fixes or security patches going forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and Resource Usage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Actual Budget&lt;/th&gt;
&lt;th&gt;Maybe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~150 MB&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~50 MB&lt;/td&gt;
&lt;td&gt;~300 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (under load)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~80 MB&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Negligible&lt;/td&gt;
&lt;td&gt;Low-medium (Rails)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQLite (no extra container)&lt;/td&gt;
&lt;td&gt;PostgreSQL (separate container)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~3 seconds&lt;/td&gt;
&lt;td&gt;~15-20 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;256 MB RAM&lt;/td&gt;
&lt;td&gt;1 GB RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Actual Budget is dramatically lighter. Its local-first architecture means the sync server does minimal work — the heavy lifting happens in the browser's IndexedDB.&lt;/p&gt;

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

&lt;p&gt;This is the decisive factor. Actual Budget has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weekly releases with bug fixes and features&lt;/li&gt;
&lt;li&gt;Active GitHub community (500+ contributors)&lt;/li&gt;
&lt;li&gt;Regular security updates&lt;/li&gt;
&lt;li&gt;New bank connection integrations being added&lt;/li&gt;
&lt;li&gt;Responsive issue tracker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maybe has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No updates since July 2025&lt;/li&gt;
&lt;li&gt;Repository archived on GitHub&lt;/li&gt;
&lt;li&gt;Known bugs will never be fixed&lt;/li&gt;
&lt;li&gt;No security patches&lt;/li&gt;
&lt;li&gt;Dependencies will gradually become vulnerable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running unmaintained software on your network with access to financial data is a risk that compounds over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Actual Budget If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want envelope-style budgeting (the YNAB methodology)&lt;/li&gt;
&lt;li&gt;Long-term maintenance and security updates matter&lt;/li&gt;
&lt;li&gt;You want bank syncing (GoCardless for European banks, SimpleFIN for US)&lt;/li&gt;
&lt;li&gt;You need offline access and mobile PWA support&lt;/li&gt;
&lt;li&gt;You want the lightest possible deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Maybe If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You specifically want net worth and investment portfolio tracking&lt;/li&gt;
&lt;li&gt;You understand the risks of running unmaintained software&lt;/li&gt;
&lt;li&gt;You're willing to fork and maintain it yourself&lt;/li&gt;
&lt;li&gt;You need a starting point for a custom finance app (Rails codebase)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;The practical choice is Actual Budget because it's actively maintained, lighter on resources, and more mature for day-to-day budgeting. Maybe's investment tracking features are genuinely useful, but a project that stopped receiving updates — including security patches — isn't something you should run long-term with access to sensitive financial data. For investment tracking, consider &lt;a href="https://dev.to/apps/ghostfolio"&gt;Ghostfolio&lt;/a&gt; alongside Actual Budget instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can Actual Budget track investments?
&lt;/h3&gt;

&lt;p&gt;No. Actual Budget is focused on envelope budgeting and expense tracking. For investment portfolio tracking, pair it with &lt;a href="https://dev.to/apps/ghostfolio"&gt;Ghostfolio&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Maybe safe to run if it's unmaintained?
&lt;/h3&gt;

&lt;p&gt;The code works, but dependencies won't receive security patches. If you isolate it on a private network and accept the risk, it functions. Don't expose it to the internet without a reverse proxy and network segmentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I import data from Maybe into Actual Budget?
&lt;/h3&gt;

&lt;p&gt;Not directly. You'd need to export from Maybe as CSV, then reformat the CSV to match Actual Budget's import expectations. The data models are different — Maybe tracks net worth and assets, while Actual Budget tracks income and expenses by category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/maybe/"&gt;How to Self-Host Maybe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/actual-budget"&gt;How to Self-Host Actual Budget with Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/actual-budget-vs-firefly-iii"&gt;Actual Budget vs Firefly III&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/ghostfolio-vs-actual-budget"&gt;Ghostfolio vs Actual Budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/personal-finance"&gt;Best Self-Hosted Personal Finance Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/replace/mint"&gt;Self-Hosted Alternatives to Mint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/docker-compose-basics"&gt;Docker Compose Basics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>software</category>
    </item>
    <item>
      <title>Forgejo Vs Gitlab Ce</title>
      <dc:creator>selfhosting.sh</dc:creator>
      <pubDate>Thu, 19 Mar 2026 20:18:00 +0000</pubDate>
      <link>https://forem.com/selfhostingsh/forgejo-vs-gitlab-ce-8a1</link>
      <guid>https://forem.com/selfhostingsh/forgejo-vs-gitlab-ce-8a1</guid>
      <description>&lt;p&gt;If you need a Git server that does one thing well and runs on minimal hardware, Forgejo is the clear winner. If you need an integrated DevOps platform with built-in CI/CD, container registry, and project management — and have the RAM to spare — GitLab CE is the full-stack option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Forgejo is a community-governed fork of Gitea, itself a fork of Gogs. It's a lightweight Git forge written in Go that provides repository hosting, pull requests, issue tracking, and GitHub Actions-compatible CI/CD. The project is governed by the Codeberg e.V. nonprofit, emphasizing community ownership over corporate control.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; Verified with latest Docker images and configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitLab Community Edition is the open-source version of the GitLab DevOps platform. It bundles Git hosting with CI/CD pipelines, container registries, package management, issue boards, wikis, and dozens of other features into a single monolithic Ruby on Rails application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Forgejo&lt;/th&gt;
&lt;th&gt;GitLab CE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Ruby on Rails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPL-3.0 (copyleft)&lt;/td&gt;
&lt;td&gt;MIT (Expat)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:14.0.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitlab/gitlab-ce:18.9.2-ce.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~120 MB&lt;/td&gt;
&lt;td&gt;~4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (recommended)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;512 MB&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~5 seconds&lt;/td&gt;
&lt;td&gt;3-5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Built-in CI/CD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forgejo Actions (GitHub Actions-compatible)&lt;/td&gt;
&lt;td&gt;GitLab CI/CD (native &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Container registry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (basic)&lt;/td&gt;
&lt;td&gt;Yes (advanced, integrated with CI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Package registry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (npm, Maven, PyPI, Cargo, etc.)&lt;/td&gt;
&lt;td&gt;Yes (extensive, 15+ formats)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Issue tracking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (with boards, weights, milestones)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wiki&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (Git-backed)&lt;/td&gt;
&lt;td&gt;Yes (Git-backed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Git LFS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Federation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ActivityPub (experimental)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSO/LDAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LDAP, OAuth2, OIDC&lt;/td&gt;
&lt;td&gt;LDAP, SAML, OAuth2, OIDC, Kerberos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Webhooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forgejo, Gitea, Slack, Discord, Teams&lt;/td&gt;
&lt;td&gt;Extensive (push, merge, pipeline, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic (issues + milestones)&lt;/td&gt;
&lt;td&gt;Advanced (boards, epics, roadmaps)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Installation Complexity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Forgejo
&lt;/h3&gt;

&lt;p&gt;Two containers — Forgejo and PostgreSQL. Runs comfortably on 512 MB RAM:&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;forgejo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codeberg.org/forgejo/forgejo:14.0.3&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;forgejo&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USER_UID=1000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USER_GID=1000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORGEJO__database__DB_TYPE=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORGEJO__database__HOST=forgejo-db:5432&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORGEJO__database__NAME=forgejo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORGEJO__database__USER=forgejo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORGEJO__database__PASSWD=changeme-forgejo-db-pass&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:/data&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;3000:3000"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2222:22"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;forgejo-db&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;span class="na"&gt;forgejo-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;forgejo-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=forgejo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=forgejo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=changeme-forgejo-db-pass&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;./db:/var/lib/postgresql/data&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;h3&gt;
  
  
  GitLab CE
&lt;/h3&gt;

&lt;p&gt;One massive container with everything bundled. Needs 4 GB RAM minimum, 8 GB recommended:&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;gitlab&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab/gitlab-ce:18.9.2-ce.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab.example.com&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GITLAB_OMNIBUS_CONFIG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;external_url 'https://gitlab.example.com'&lt;/span&gt;
        &lt;span class="s"&gt;gitlab_rails['lfs_enabled'] = true&lt;/span&gt;
        &lt;span class="s"&gt;# Reduce memory: disable monitoring&lt;/span&gt;
        &lt;span class="s"&gt;prometheus_monitoring['enable'] = false&lt;/span&gt;
        &lt;span class="s"&gt;# SSH on custom port&lt;/span&gt;
        &lt;span class="s"&gt;gitlab_rails['gitlab_shell_ssh_port'] = 2424&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;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2424:22"&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;./config:/etc/gitlab&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logs:/var/log/gitlab&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/var/opt/gitlab&lt;/span&gt;
    &lt;span class="na"&gt;shm_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256m"&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;GitLab's Omnibus container includes PostgreSQL, Redis, Sidekiq, Puma, Nginx, and Gitaly — all in one image. This simplifies deployment but means everything scales together (or doesn't).&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and Resource Usage
&lt;/h2&gt;

&lt;p&gt;This is where the difference is stark:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Forgejo&lt;/th&gt;
&lt;th&gt;GitLab CE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~100 MB&lt;/td&gt;
&lt;td&gt;~3 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~120 MB&lt;/td&gt;
&lt;td&gt;~3.5-4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM (under load)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~350 MB&lt;/td&gt;
&lt;td&gt;~6-8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Negligible&lt;/td&gt;
&lt;td&gt;~5-10% (background jobs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5-10 seconds&lt;/td&gt;
&lt;td&gt;3-5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disk (application)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;~5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum viable server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 vCPU, 512 MB RAM&lt;/td&gt;
&lt;td&gt;2 vCPU, 4 GB RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Forgejo runs on a Raspberry Pi 4. GitLab CE struggles below 4 GB RAM — if you disable Prometheus monitoring (&lt;code&gt;prometheus_monitoring['enable'] = false&lt;/code&gt;), you can reclaim ~300 MB, but it still demands significantly more resources.&lt;/p&gt;

&lt;p&gt;For teams under 20 developers, Forgejo handles Git operations, CI, and package hosting without breaking a sweat on a $5/month VPS. GitLab CE needs a $20-40/month server to run comfortably.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Forgejo Actions
&lt;/h3&gt;

&lt;p&gt;Compatible with GitHub Actions workflow syntax. You deploy a separate runner container (&lt;code&gt;codeberg.org/forgejo/runner:6.3.1&lt;/code&gt;) that picks up jobs defined in &lt;code&gt;.forgejo/workflows/&lt;/code&gt; or &lt;code&gt;.github/workflows/&lt;/code&gt; YAML files. Most GitHub Actions from the marketplace work with minor adjustments.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitLab CI/CD
&lt;/h3&gt;

&lt;p&gt;Native pipeline system using &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;. Deeply integrated — pipelines can trigger deployments, publish to the container registry, run security scans, and manage environments. The CI/CD system is more mature and has features like DAG pipelines, parent-child pipelines, and multi-project pipelines that Forgejo Actions doesn't match.&lt;/p&gt;

&lt;p&gt;If you're migrating from GitHub Actions, Forgejo is easier. If you need advanced CI/CD orchestration, GitLab's pipeline system is more capable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community and Ecosystem
&lt;/h2&gt;

&lt;p&gt;Forgejo is governed by Codeberg e.V., a German nonprofit. Development is transparent and community-driven. The project guarantees it will never become a commercial product. Federation via ActivityPub is an active development area — Forgejo aims to let instances communicate across the Fediverse.&lt;/p&gt;

&lt;p&gt;GitLab CE is maintained by GitLab Inc., a publicly traded company. The CE edition gets features after the paid tiers (EE, Premium, Ultimate). Some features available in Forgejo's free tier are paywalled in GitLab (e.g., certain security scanning, compliance features). However, GitLab's ecosystem is vastly larger — more integrations, more documentation, and more third-party tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Forgejo If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want a lightweight Git server that runs on modest hardware&lt;/li&gt;
&lt;li&gt;You're migrating from GitHub and want compatible Actions CI/CD&lt;/li&gt;
&lt;li&gt;Community governance and nonprofit ownership matter to you&lt;/li&gt;
&lt;li&gt;You're hosting personal projects or a small team (under 50 developers)&lt;/li&gt;
&lt;li&gt;Federation and Fediverse integration interest you&lt;/li&gt;
&lt;li&gt;You need multiple instances on limited server resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose GitLab CE If...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need a complete DevOps platform, not just Git hosting&lt;/li&gt;
&lt;li&gt;Your team uses advanced CI/CD features (DAG pipelines, environments, review apps)&lt;/li&gt;
&lt;li&gt;You need integrated container and package registries tightly coupled with CI&lt;/li&gt;
&lt;li&gt;You have 4+ GB RAM available and don't mind the resource footprint&lt;/li&gt;
&lt;li&gt;You need enterprise SSO (SAML, Kerberos) beyond basic OAuth/OIDC&lt;/li&gt;
&lt;li&gt;Your organization already uses GitLab and you want to self-host&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;Forgejo wins on efficiency — it's 30x lighter on RAM and starts in seconds. For personal projects, small teams, and anyone on constrained hardware, there's no reason to absorb GitLab's overhead. GitLab CE wins when you genuinely need the DevOps platform: integrated CI/CD pipelines, security scanning, container registries, and project management in one tool. If you're only using GitLab for Git hosting and basic CI, you're paying a steep resource tax for features you don't use.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I migrate repositories from GitLab to Forgejo?
&lt;/h3&gt;

&lt;p&gt;Yes. Forgejo has a built-in migration tool that imports repositories, issues, pull requests, labels, and milestones from GitLab, GitHub, Gitea, and other platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Forgejo support GitLab CI/CD syntax?
&lt;/h3&gt;

&lt;p&gt;No. Forgejo Actions uses GitHub Actions syntax (&lt;code&gt;.github/workflows/&lt;/code&gt;), not &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;. You'd need to rewrite CI pipelines if migrating from GitLab.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can GitLab CE run on a Raspberry Pi?
&lt;/h3&gt;

&lt;p&gt;Technically possible on a Pi 5 with 8 GB RAM, but not recommended. Background jobs alone consume 2-3 GB. Forgejo is a much better fit for ARM hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Forgejo compatible with Gitea?
&lt;/h3&gt;

&lt;p&gt;Yes. Forgejo forked from Gitea in late 2022 and maintains compatibility with Gitea's API and plugin ecosystem. Most Gitea themes and plugins work with Forgejo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/forgejo"&gt;How to Self-Host Forgejo with Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/apps/gitlab-ce"&gt;How to Self-Host GitLab CE with Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/gitlab-ce-vs-gitea"&gt;GitLab CE vs Gitea&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/gitea-vs-forgejo"&gt;Gitea vs Forgejo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/compare/onedev-vs-gitlab-ce"&gt;OneDev vs GitLab CE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/best/git-hosting"&gt;Best Self-Hosted Git Servers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/foundations/docker-compose-basics"&gt;Docker Compose Basics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>git</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
