Compare commits

..
3 Commits
20 changed files with 2829 additions and 16 deletions

No files matched your search

+2
View File
@@ -4,7 +4,9 @@
"private": true,
"scripts": {
"dev": "next dev",
"prebuild": "node scripts/generate-sitemap.mjs",
"build": "next build",
"postbuild": "node scripts/inject-structured-data.mjs",
"start": "next start",
"lint": "eslint"
},
+129
View File
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>DashCaddy Blog</title>
<link>https://dashcaddy.net/blog/</link>
<description>Self-hosting deep-dives, product updates, and practical guides to running your own infrastructure.</description>
<language>en-us</language>
<atom:link href="https://dashcaddy.net/feed.xml" rel="self" type="application/rss+xml" />
<item>
<title>Docker Dashboard for Self-Hosted Apps: Why DashCaddy Beats Portainer</title>
<link>https://dashcaddy.net/blog/docker-dashboard-self-hosted/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/docker-dashboard-self-hosted/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Compare DashCaddy vs Portainer for managing self-hosted Docker containers. Learn about AI-powered deployment, automatic SSL, and multi-host management.</description>
</item>
<item>
<title>What is DashCaddy? The AI-Powered Self-Hosting Platform Explained</title>
<link>https://dashcaddy.net/blog/what-is-dashcaddy/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/what-is-dashcaddy/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Learn what DashCaddy is—an AI-native platform for deploying, securing, and managing self-hosted applications with plain English commands.</description>
</item>
<item>
<title>Self-Hosted Docker Container Management Made Simple</title>
<link>https://dashcaddy.net/blog/docker-container-management/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/docker-container-management/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Master Docker container management in your homelab. Compare methods, learn best practices, and discover how DashCaddy simplifies running containers.</description>
</item>
<item>
<title>Why Self-Host Your Applications? The Complete Benefits Guide</title>
<link>https://dashcaddy.net/blog/why-self-host-benefits/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/why-self-host-benefits/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Discover why self-hosting your applications saves money, improves privacy, and gives you control. Learn what's possible when you run your own services.</description>
</item>
<item>
<title>How to Access Self-Hosted Apps Remotely (Without Exposing Your Network)</title>
<link>https://dashcaddy.net/blog/access-self-hosted-apps-remotely/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/access-self-hosted-apps-remotely/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Port forwarding, VPNs, and private networks compared. Learn the safe way to reach self-hosted apps from anywhere, and how to scope a service to your own devices.</description>
</item>
<item>
<title>One-Click Docker Deployment: From Blank Server to Running App</title>
<link>https://dashcaddy.net/blog/one-click-docker-deployment/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/one-click-docker-deployment/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>What one-click Docker deployment really involves — image, volumes, ports, DNS, TLS, and health checks — and how to get all of it in a single action.</description>
</item>
<item>
<title>Self-Hosting Platform: What It Is and How to Choose One in 2026</title>
<link>https://dashcaddy.net/blog/self-hosting-platform-guide/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/self-hosting-platform-guide/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>A self-hosting platform turns a bare Linux server into somewhere you can run apps safely. Learn the categories, the questions that matter, and how to pick one.</description>
</item>
<item>
<title>77 Self-Hosted Apps You Can Install in One Click (Full Catalog)</title>
<link>https://dashcaddy.net/blog/self-hosted-app-catalog-guide/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/self-hosted-app-catalog-guide/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>A complete map of the 77 apps DashCaddy can deploy in one click — media, files, photos, DNS, security, databases, automation and more, each with its own install guide.</description>
</item>
<item>
<title>Self-Host Your Photo Library: Immich in 10 Minutes</title>
<link>https://dashcaddy.net/blog/self-host-your-photo-library/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/self-host-your-photo-library/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Stop paying a monthly subscription to look at your own photos. Deploy Immich with one click, put it behind HTTPS, and get automatic phone backup onto hardware you own.</description>
</item>
<item>
<title>Self-Host Vaultwarden in 10 Minutes (Your Own Password Manager)</title>
<link>https://dashcaddy.net/blog/self-host-vaultwarden/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/self-host-vaultwarden/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>Run your own Bitwarden-compatible password manager on your own server: install Vaultwarden with one click, put it on HTTPS, and keep your vaults off someone else's cloud.</description>
</item>
<item>
<title>Getting Started with DashCaddy: From Blank Server to a Running App</title>
<link>https://dashcaddy.net/blog/getting-started-with-dashcaddy/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/getting-started-with-dashcaddy/</guid>
<pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
<description>A step-by-step walkthrough: pick a server, install DashCaddy with one command, open the dashboard, deploy your first self-hosted app, and get it on HTTPS.</description>
</item>
<item>
<title>Homelab Monitoring: Know When a Docker Service Goes Down</title>
<link>https://dashcaddy.net/blog/homelab-monitoring-guide/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/homelab-monitoring-guide/</guid>
<pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
<description>Build practical homelab monitoring across hosts, Docker, application health, DNS, HTTPS, and backups, with DashCaddy as the operating view.</description>
</item>
<item>
<title>Homelab Reverse Proxy Guide: HTTPS, DNS, and Caddy</title>
<link>https://dashcaddy.net/blog/homelab-reverse-proxy-guide/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/homelab-reverse-proxy-guide/</guid>
<pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
<description>Learn how a homelab reverse proxy connects DNS, HTTPS, Caddy, and private services—and how DashCaddy reduces repeated configuration work.</description>
</item>
<item>
<title>15 Best Self-Hosted Apps for Your Homelab in 2026</title>
<link>https://dashcaddy.net/blog/best-self-hosted-apps-homelab/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/best-self-hosted-apps-homelab/</guid>
<pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
<description>Discover 15 useful self-hosted apps for media, photos, files, automation, monitoring, and privacy—all available in DashCaddy's app catalog.</description>
</item>
<item>
<title>Best Homelab Dashboard: What to Look For in 2026</title>
<link>https://dashcaddy.net/blog/best-homelab-dashboard/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/best-homelab-dashboard/</guid>
<pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
<description>Compare homelab dashboard types and learn which deployment, health, HTTPS, security, and automation features make a home server easier to manage.</description>
</item>
<item>
<title>How to Build a Homelab: A Beginner's Home Server Guide</title>
<link>https://dashcaddy.net/blog/homelab-beginners-guide/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/homelab-beginners-guide/</guid>
<pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
<description>Learn how to build a homelab from spare hardware, Docker, DNS, HTTPS, monitoring, and backups—and see how DashCaddy simplifies the work.</description>
</item>
<item>
<title>Deploy Self-Hosted Apps From Plain English: DashCaddy's AI Intent Router</title>
<link>https://dashcaddy.net/blog/deploy-self-hosted-apps-with-ai/</link>
<guid isPermaLink="true">https://dashcaddy.net/blog/deploy-self-hosted-apps-with-ai/</guid>
<pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
<description>How DashCaddy turns a plain-English request into a running, SSL-secured container — 77 one-click app templates, an MCP server for AI assistants, and 400+ registered REST route handlers.</description>
</item>
</channel>
</rss>
+572 -14
View File
@@ -1,17 +1,575 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://dashcaddy.net/</loc><changefreq>weekly</changefreq><priority>1.0</priority></url>
<url><loc>https://dashcaddy.net/features/</loc><changefreq>monthly</changefreq><priority>0.9</priority></url>
<url><loc>https://dashcaddy.net/pricing/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/docs/</loc><changefreq>weekly</changefreq><priority>0.9</priority></url>
<url><loc>https://dashcaddy.net/docs/installation/</loc><changefreq>monthly</changefreq><priority>0.9</priority></url>
<url><loc>https://dashcaddy.net/docs/catalog/</loc><changefreq>weekly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/about/</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://dashcaddy.net/blog/</loc><changefreq>weekly</changefreq><priority>0.9</priority></url>
<url><loc>https://dashcaddy.net/blog/deploy-self-hosted-apps-with-ai/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/blog/homelab-beginners-guide/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/blog/best-homelab-dashboard/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/blog/best-self-hosted-apps-homelab/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/blog/homelab-reverse-proxy-guide/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://dashcaddy.net/blog/homelab-monitoring-guide/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url>
<loc>https://dashcaddy.net/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://dashcaddy.net/about/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/</loc>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/access-self-hosted-apps-remotely/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/best-homelab-dashboard/</loc>
<lastmod>2026-10-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/best-self-hosted-apps-homelab/</loc>
<lastmod>2026-10-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/deploy-self-hosted-apps-with-ai/</loc>
<lastmod>2026-10-05</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/docker-container-management/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/docker-dashboard-self-hosted/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/getting-started-with-dashcaddy/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/homelab-beginners-guide/</loc>
<lastmod>2026-10-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/homelab-monitoring-guide/</loc>
<lastmod>2026-10-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/homelab-reverse-proxy-guide/</loc>
<lastmod>2026-10-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/one-click-docker-deployment/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/self-host-vaultwarden/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/self-host-your-photo-library/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/self-hosted-app-catalog-guide/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/self-hosting-platform-guide/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/what-is-dashcaddy/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/blog/why-self-host-benefits/</loc>
<lastmod>2026-10-07</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/</loc>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/api/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/actual-budget/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/adminer/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/airsonic/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/audiobookshelf/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/authentik/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/bazarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/bind9/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/bookstack/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/calibre-web/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/changedetection/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/coredns/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/crowdsec/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/dashca/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/digital-clock/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/dozzle/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/drone/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/emby/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/excalidraw/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/filebrowser/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/gitea/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/grafana/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/homarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/homeassistant/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/homepage/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/immich/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/it-tools/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/jdownloader/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/jellyfin/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/jenkins/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/kavita/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/komga/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/lidarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/mailserver/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/matrix/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/mealie/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/minecraft/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/mongodb/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/navidrome/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/nextcloud/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/nodered/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/nzbget/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/outline/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/paperless-ngx/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/photoprism/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/pihole/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/plex/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/portainer/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/postgres/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/powerdns/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/prowlarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/qbittorrent/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/radarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/readarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/redis/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/rocketchat/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/roundcube/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/sabnzbd/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/sami-files/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/seerr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/sonarr/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/speedtest/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/standardnotes/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/stirling-pdf/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/syncthing/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/tautulli/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/technitium/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/transmission/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/trilium/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/uptime-kuma/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/valheim/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/vaultwarden/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/vintage-radio/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/vscode-server/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/watchtower/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/weather/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/whoami/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/catalog/wireguard/</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/desktop-app/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/first-service/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/installation/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/integrations/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/overview/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/premium/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/docs/troubleshooting/</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dashcaddy.net/features/</loc>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dashcaddy.net/policies/privacy/</loc>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://dashcaddy.net/policies/terms/</loc>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://dashcaddy.net/pricing/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env node
/**
* Generate public/sitemap.xml and public/feed.xml from the real route tree
* and the blog manifest.
*
* Runs automatically before `next build` (see the "prebuild" npm script), so
* the deployed sitemap can never drift from the pages that actually exist.
* Fails loudly if the blog manifest cannot be parsed, rather than silently
* emitting a partial sitemap.
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const APP_DIR = path.join(root, "src", "app");
const MANIFEST = path.join(root, "src", "content", "blog", "manifest.ts");
const PUBLIC_DIR = path.join(root, "public");
const ORIGIN = "https://dashcaddy.net";
const SITE_DESCRIPTION =
"Self-hosting deep-dives, product updates, and practical guides to running your own infrastructure.";
// Pages that must never be advertised to crawlers.
const EXCLUDE = new Set(["/success/", "/404/", "/500/"]);
function walk(dir) {
const found = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) found.push(...walk(full));
else if (entry.name === "page.tsx") found.push(full);
}
return found;
}
function routeFor(file) {
const rel = path.relative(APP_DIR, path.dirname(file));
const parts = rel
.split(path.sep)
.filter((part) => part && part !== "." && !/^\(.*\)$/.test(part));
// Dynamic segments ([slug]) are expanded from data sources, not enumerated.
if (parts.some((part) => part.startsWith("["))) return null;
return "/" + parts.join("/") + (parts.length ? "/" : "");
}
function meta(route) {
if (route === "/") return { priority: "1.0", changefreq: "weekly" };
if (route === "/blog/" || route === "/docs/" || route === "/features/")
return { priority: "0.9", changefreq: "weekly" };
if (route === "/pricing/" || route === "/docs/catalog/")
return { priority: "0.8", changefreq: "weekly" };
if (route.startsWith("/docs/catalog/")) return { priority: "0.6", changefreq: "monthly" };
if (route.startsWith("/docs/")) return { priority: "0.7", changefreq: "monthly" };
if (route === "/about/") return { priority: "0.6", changefreq: "monthly" };
if (route.startsWith("/policies/")) return { priority: "0.3", changefreq: "yearly" };
return { priority: "0.5", changefreq: "monthly" };
}
function xmlEscape(value) {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
// --- Blog manifest ---------------------------------------------------------
const manifestSrc = fs.readFileSync(MANIFEST, "utf8");
const POSTS = [
...manifestSrc.matchAll(
/\{\s*slug:\s*"([^"]+)",\s*title:\s*"((?:[^"\\]|\\.)*)",\s*description:\s*"((?:[^"\\]|\\.)*)",\s*date:\s*"([^"]+)"/g
),
].map((m) => ({ slug: m[1], title: m[2], description: m[3], date: m[4] }));
const manifestSlugCount = [...manifestSrc.matchAll(/slug:\s*"([^"]+)"/g)].length;
if (POSTS.length === 0 || POSTS.length !== manifestSlugCount) {
throw new Error(
`generate-sitemap: parsed ${POSTS.length} posts but manifest declares ${manifestSlugCount} slugs — refusing to emit a partial sitemap`
);
}
// --- Sitemap ---------------------------------------------------------------
const staticRoutes = [...new Set(walk(APP_DIR).map(routeFor).filter(Boolean))]
.filter((route) => !EXCLUDE.has(route))
.sort();
const urls = [
...staticRoutes.map((route) => ({ loc: ORIGIN + route, ...meta(route) })),
...POSTS.map((post) => ({
loc: `${ORIGIN}/blog/${post.slug}/`,
lastmod: post.date,
changefreq: "monthly",
priority: "0.8",
})),
].sort((a, b) => (a.loc < b.loc ? -1 : 1));
const sitemap =
`<?xml version="1.0" encoding="UTF-8"?>\n` +
`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n` +
urls
.map(
(u) =>
` <url>\n <loc>${u.loc}</loc>` +
(u.lastmod ? `\n <lastmod>${u.lastmod}</lastmod>` : "") +
`\n <changefreq>${u.changefreq}</changefreq>\n <priority>${u.priority}</priority>\n </url>`
)
.join("\n") +
`\n</urlset>\n`;
// --- RSS feed --------------------------------------------------------------
const sortedPosts = [...POSTS].sort((a, b) => (a.date < b.date ? 1 : -1));
const feed =
`<?xml version="1.0" encoding="UTF-8"?>\n` +
`<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">\n <channel>\n` +
` <title>DashCaddy Blog</title>\n` +
` <link>${ORIGIN}/blog/</link>\n` +
` <description>${xmlEscape(SITE_DESCRIPTION)}</description>\n` +
` <language>en-us</language>\n` +
` <atom:link href="${ORIGIN}/feed.xml" rel="self" type="application/rss+xml" />\n` +
sortedPosts
.map(
(p) =>
` <item>\n` +
` <title>${xmlEscape(p.title)}</title>\n` +
` <link>${ORIGIN}/blog/${p.slug}/</link>\n` +
` <guid isPermaLink="true">${ORIGIN}/blog/${p.slug}/</guid>\n` +
` <pubDate>${new Date(`${p.date}T00:00:00Z`).toUTCString()}</pubDate>\n` +
` <description>${xmlEscape(p.description)}</description>\n` +
` </item>`
)
.join("\n") +
`\n </channel>\n</rss>\n`;
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(path.join(PUBLIC_DIR, "sitemap.xml"), sitemap);
fs.writeFileSync(path.join(PUBLIC_DIR, "feed.xml"), feed);
console.log(
`generate-sitemap: ${urls.length} sitemap URLs (${staticRoutes.length} pages + ${POSTS.length} posts), ${sortedPosts.length} feed items`
);
+492
View File
@@ -0,0 +1,492 @@
#!/usr/bin/env node
/**
* inject-structured-data.mjs
*
* Adds JSON-LD structured data to every built DashCaddy app-guide page
* (out/docs/catalog/<app>/index.html) after `next build` has produced the
* static export.
*
* Design rules:
* - Runs on npm `postbuild`, so it operates on the real rendered HTML, not on
* the TSX source. Everything it emits is therefore provably derived from
* text a visitor can already see on the page (Google's requirement for
* FAQPage / HowTo markup).
* - Emits nothing it cannot ground. A page with no category badge gets no
* applicationCategory; a page with no troubleshooting list gets no FAQPage;
* a page with no dashboard install steps gets no HowTo. Missing fields are
* reported, never invented.
* - Idempotent: an already-injected page is detected and skipped.
*
* Never hand-edit this file's OUTPUT. Re-run the build.
*/
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const HERE = dirname(fileURLToPath(import.meta.url));
const REPO = join(HERE, "..");
const OUT = join(REPO, "out");
const CATALOG = join(OUT, "docs", "catalog");
const ORIGIN = "https://dashcaddy.net";
const MARKER = 'data-dashcaddy-jsonld="app-guide"';
const BLOG_MARKER = 'data-dashcaddy-jsonld="blog-breadcrumb-faq"';
/* ------------------------------------------------------------------ text --- */
const ENTITIES = {
amp: "&",
lt: "<",
gt: ">",
quot: '"',
apos: "'",
nbsp: " ",
"39": "'",
"x27": "'",
"x2F": "/",
"47": "/",
};
function decodeEntities(s) {
return s.replace(/&#?([a-zA-Z0-9]+);/g, (whole, code) => {
if (Object.prototype.hasOwnProperty.call(ENTITIES, code)) return ENTITIES[code];
if (Object.prototype.hasOwnProperty.call(ENTITIES, "#" + code)) {
return ENTITIES["#" + code];
}
if (/^#?\d+$/.test(code)) {
const n = Number(code.replace("#", ""));
if (Number.isFinite(n) && n > 0 && n < 0x10ffff) return String.fromCodePoint(n);
}
if (/^#?x[0-9a-f]+$/i.test(code)) {
const n = parseInt(code.replace(/^#?x/i, ""), 16);
if (Number.isFinite(n) && n > 0 && n < 0x10ffff) return String.fromCodePoint(n);
}
return whole;
});
}
/** Strip tags, decode entities, collapse whitespace. */
function text(html) {
return decodeEntities(
html
.replace(/<br\s*\/?>/gi, " ")
.replace(/<[^>]+>/g, " "),
)
.replace(/\s+/g, " ")
.trim();
}
/** Tags stripped / entities decoded for the WHOLE document — used to prove that
* anything we mark up is genuinely visible to a visitor. */
function visibleText(doc) {
return text(
doc
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " "),
);
}
/* ------------------------------------------------------------- extraction --- */
/** Return the inner HTML of the first <tag ...> ... </tag> starting at `from`. */
function innerOf(html, tag, from = 0) {
const open = new RegExp(`<${tag}\\b[^>]*>`, "gi");
open.lastIndex = from;
const m = open.exec(html);
if (!m) return null;
let depth = 1;
const re = new RegExp(`<${tag}\\b[^>]*>|</${tag}>`, "gi");
re.lastIndex = open.lastIndex;
let mm;
while ((mm = re.exec(html)) !== null) {
if (mm[0].startsWith("</")) depth -= 1;
else depth += 1;
if (depth === 0) {
return { inner: html.slice(open.lastIndex, mm.index), end: re.lastIndex };
}
}
return null;
}
/** All top-level <li> entries inside an HTML fragment containing list items. */
function listItems(ulInner) {
const items = [];
let pos = 0;
for (;;) {
const li = innerOf(ulInner, "li", pos);
if (!li) break;
items.push(li.inner);
pos = li.end;
}
return items;
}
/**
* FAQ entries, taken ONLY from the visitor-visible troubleshooting list.
*
* The question is the page's own list label, verbatim (minus the trailing list
* punctuation); the answer is the page's own explanation, verbatim. Nothing is
* rephrased, and no question is synthesised — if the label is not visible on the
* page, it does not get marked up. Pages whose troubleshooting entry has no bold
* label are skipped.
*/
function extractFaqs(html) {
const h = html.indexOf("<h2>Troubleshooting</h2>");
if (h === -1) return [];
const ul = innerOf(html, "ul", h);
if (!ul) return [];
const faqs = [];
for (const li of listItems(ul.inner)) {
// <strong>Label:</strong> answer
const m = /<strong>([\s\S]*?)<\/strong>([\s\S]*)/.exec(li);
if (!m) continue;
const q = text(m[1]).replace(/[:\s]+$/, "");
const a = text(m[2]);
if (!q || !a) continue;
faqs.push({ question: q, answer: a });
}
return faqs;
}
function extractSteps(html, appName) {
const h = html.indexOf("<h2>Install via the DashCaddy dashboard</h2>");
if (h === -1) return [];
const ol = innerOf(html, "ol", h);
if (!ol) return [];
const steps = listItems(ol.inner)
.map((li) => text(li))
.filter((s) => s.length > 0);
return steps.length >= 3 ? steps : [];
}
function extractMeta(html) {
const title = /<title>([\s\S]*?)<\/title>/.exec(html);
let appName = title ? text(title[1]) : "";
appName = appName.replace(/\s*[—–-]\s*DashCaddy Docs\s*$/i, "").replace(/^Install\s+/i, "").trim();
let description = "";
const intro = /<p class="mt-4 max-w-3xl[^"]*">([\s\S]*?)<\/p>/.exec(html);
if (intro) description = text(intro[1]);
const cat = /Category:\s*([^<]+)</.exec(html);
const diff = /Difficulty:\s*([^<]+)</.exec(html);
const img = /Docker image:[\s\S]*?<code[^>]*>([\s\S]*?)<\/code>/.exec(html);
return {
appName,
description,
category: cat ? text(cat[1]) : null,
difficulty: diff ? text(diff[1]) : null,
dockerImage: img ? text(img[1]) : null,
};
}
/* ------------------------------------------------------------ blog posts --- */
/**
* Blog FAQ entries, taken only from a real, visible "Frequently asked questions"
* section: each `### question` heading plus the paragraphs that follow it, both
* verbatim. A post with no such section (or fewer than two entries) gets no
* FAQPage node.
*/
function extractBlogFaqs(html) {
const m = /<h2>(?:Frequently asked questions|FAQ)<\/h2>/i.exec(html);
if (!m) return [];
const rest = html.slice(m.index + m[0].length);
let end = rest.length;
for (const marker of ["<h2", "<footer", "<nav", "<aside", "<script", "<main"]) {
const i = rest.indexOf(marker);
if (i !== -1 && i < end) end = i;
}
const region = rest.slice(0, end);
const faqs = [];
const parts = region.split(/<h3>([\s\S]*?)<\/h3>/);
for (let i = 1; i < parts.length; i += 2) {
const q = text(parts[i]);
const a = text(parts[i + 1] || "");
if (q && a) faqs.push({ question: q, answer: a });
}
// Fallback shape: "**Question?** answer" rendered as <p><strong>…</strong>…</p>.
if (faqs.length < 2) {
const re = /<p><strong>([\s\S]*?)<\/strong>([\s\S]*?)<\/p>/g;
let m2;
while ((m2 = re.exec(region)) !== null) {
const q = text(m2[1]);
const a = text(m2[2]);
if (q && q.endsWith("?") && a) faqs.push({ question: q, answer: a });
}
}
return faqs.length >= 2 ? faqs : [];
}
function buildBlogGraph(slug, html, faqs) {
const h1 = /<h1[^>]*>([\s\S]*?)<\/h1>/.exec(html);
const title = h1 ? text(h1[1]) : slug;
const page = `${ORIGIN}/blog/${slug}/`;
const graph = [
{
"@type": "BreadcrumbList",
"@id": `${page}#breadcrumb`,
itemListElement: [
{ "@type": "ListItem", position: 1, name: "DashCaddy", item: `${ORIGIN}/` },
{ "@type": "ListItem", position: 2, name: "Blog", item: `${ORIGIN}/blog/` },
{ "@type": "ListItem", position: 3, name: title, item: page },
],
},
];
if (faqs.length >= 2) {
graph.push({
"@type": "FAQPage",
"@id": `${page}#faq`,
mainEntity: faqs.map((f) => ({
"@type": "Question",
name: f.question,
acceptedAnswer: { "@type": "Answer", text: f.answer },
})),
});
}
return { "@context": "https://schema.org", "@graph": graph };
}
/* ------------------------------------------------------------------ build --- */
function buildGraph(appId, meta, faqs, steps) {
const page = `${ORIGIN}/docs/catalog/${appId}/`;
const graph = [];
graph.push({
"@type": "BreadcrumbList",
"@id": `${page}#breadcrumb`,
itemListElement: [
{ "@type": "ListItem", position: 1, name: "DashCaddy", item: `${ORIGIN}/` },
{ "@type": "ListItem", position: 2, name: "Documentation", item: `${ORIGIN}/docs/` },
{ "@type": "ListItem", position: 3, name: "App Catalog", item: `${ORIGIN}/docs/catalog/` },
{ "@type": "ListItem", position: 4, name: `Install ${meta.appName}`, item: page },
],
});
const app = {
"@type": "SoftwareApplication",
"@id": `${page}#software`,
name: meta.appName,
url: page,
operatingSystem: "Linux",
applicationCategory: meta.category || "UtilitiesApplication",
};
if (meta.description) app.description = meta.description;
if (meta.dockerImage) app.softwareRequirements = `Docker image ${meta.dockerImage}`;
graph.push(app);
if (faqs.length >= 2) {
graph.push({
"@type": "FAQPage",
"@id": `${page}#faq`,
mainEntity: faqs.map((f) => ({
"@type": "Question",
name: f.question,
acceptedAnswer: { "@type": "Answer", text: f.answer },
})),
});
}
if (steps.length >= 3) {
graph.push({
"@type": "HowTo",
"@id": `${page}#howto`,
name: `Install ${meta.appName} with DashCaddy`,
description: `Deploy ${meta.appName} on a DashCaddy host from the dashboard.`,
step: steps.map((s, i) => ({
"@type": "HowToStep",
position: i + 1,
name: `Step ${i + 1}`,
text: s,
})),
});
}
return { "@context": "https://schema.org", "@graph": graph };
}
/* ------------------------------------------------------------------- main --- */
function main() {
if (!statSync(OUT, { throwIfNoEntry: false })) {
console.error("inject-structured-data: out/ not found — run next build first");
process.exit(1);
}
const appIds = readdirSync(CATALOG).filter((d) => {
try {
return statSync(join(CATALOG, d)).isDirectory();
} catch {
return false;
}
});
if (appIds.length < 50) {
console.error(`inject-structured-data: only ${appIds.length} app dirs found; refusing`);
process.exit(1);
}
const report = { pages: 0, injected: 0, skipped: 0, faq: 0, howto: 0, image: 0, missing: [], ungrounded: 0 };
for (const appId of appIds) {
const file = join(CATALOG, appId, "index.html");
if (!statSync(file, { throwIfNoEntry: false })) {
report.missing.push(`${appId}: no index.html`);
continue;
}
report.pages += 1;
let html = readFileSync(file, "utf8");
if (html.includes(MARKER)) {
report.skipped += 1;
continue;
}
const meta = extractMeta(html);
const vis = visibleText(html);
const faqs = extractFaqs(html).filter((f) => {
if (vis.includes(f.question) && vis.includes(f.answer)) return true;
report.ungrounded += 1;
return false;
});
const steps = extractSteps(html, meta.appName).filter((s) => {
if (vis.includes(s)) return true;
report.ungrounded += 1;
return false;
});
if (!meta.appName) {
report.missing.push(`${appId}: no app name`);
continue;
}
const gaps = [];
if (!meta.category) gaps.push("category");
if (!meta.difficulty) gaps.push("difficulty");
if (!meta.dockerImage) gaps.push("dockerImage");
if (gaps.length) report.missing.push(`${appId}: ${gaps.join(",")}`);
if (faqs.length >= 2) report.faq += 1;
if (steps.length >= 3) report.howto += 1;
if (meta.dockerImage) report.image += 1;
const json = JSON.stringify(buildGraph(appId, meta, faqs, steps))
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e")
.replace(/&/g, "\\u0026");
const block =
`<script type="application/ld+json" ${MARKER}>${json}</script>\n`;
if (!html.includes("</head>")) {
report.missing.push(`${appId}: no </head>`);
continue;
}
html = html.replace("</head>", `${block}</head>`);
writeFileSync(file, html);
report.injected += 1;
}
console.log("inject-structured-data: app guide pages");
console.log(` pages_seen=${report.pages}`);
console.log(` pages_injected=${report.injected}`);
console.log(` pages_skipped_already=${report.skipped}`);
console.log(` pages_with_faqpage=${report.faq}`);
console.log(` pages_with_howto=${report.howto}`);
console.log(` pages_with_docker_image=${report.image}`);
console.log(` pages_with_gaps=${report.missing.length}`);
console.log(` ungrounded_strings_dropped=${report.ungrounded}`);
for (const m of report.missing) console.log(` gap: ${m}`);
if (report.injected + report.skipped !== report.pages) {
console.error("inject-structured-data: not every page was injected or skipped");
process.exit(1);
}
injectBlogPosts();
}
/* ------------------------------------------------------------- blog pass --- */
function injectBlogPosts() {
const blogDir = join(OUT, "blog");
if (!statSync(blogDir, { throwIfNoEntry: false })) {
console.log("inject-structured-data: no out/blog dir — skipping blog pass");
return;
}
const slugs = readdirSync(blogDir).filter((d) => {
try {
return statSync(join(blogDir, d)).isDirectory();
} catch {
return false;
}
});
let seen = 0;
let injected = 0;
let skipped = 0;
let withFaq = 0;
let questions = 0;
let ungrounded = 0;
const gaps = [];
for (const slug of slugs) {
const file = join(blogDir, slug, "index.html");
if (!statSync(file, { throwIfNoEntry: false })) {
gaps.push(`${slug}: no index.html`);
continue;
}
seen += 1;
let html = readFileSync(file, "utf8");
if (html.includes(BLOG_MARKER)) {
skipped += 1;
continue;
}
const vis = visibleText(html);
const faqs = extractBlogFaqs(html).filter((f) => {
if (vis.includes(f.question) && vis.includes(f.answer)) return true;
ungrounded += 1;
return false;
});
if (faqs.length >= 2) {
withFaq += 1;
questions += faqs.length;
} else {
gaps.push(`${slug}: no visible FAQ section`);
}
const json = JSON.stringify(buildBlogGraph(slug, html, faqs))
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e")
.replace(/&/g, "\\u0026");
const block = `<script type="application/ld+json" ${BLOG_MARKER}>${json}</script>\n`;
if (!html.includes("</head>")) {
gaps.push(`${slug}: no </head>`);
continue;
}
writeFileSync(file, html.replace("</head>", `${block}</head>`));
injected += 1;
}
console.log("inject-structured-data: blog posts");
console.log(` posts_seen=${seen}`);
console.log(` posts_injected=${injected}`);
console.log(` posts_skipped_already=${skipped}`);
console.log(` posts_with_faqpage=${withFaq}`);
console.log(` faq_questions=${questions}`);
console.log(` ungrounded_strings_dropped=${ungrounded}`);
console.log(` notes=${gaps.length}`);
for (const g of gaps) console.log(` note: ${g}`);
}
main();
+9 -2
View File
@@ -38,10 +38,14 @@ function getPost(slug: string) {
return POSTS.find((p) => p.slug === slug) ?? null;
}
function stripFrontmatter(markdown: string): string {
return markdown.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
}
function readBody(slug: string): string | null {
const file = path.join(BLOG_DIR, `${slug}.md`);
if (!fs.existsSync(file)) return null;
return fs.readFileSync(file, "utf8");
return stripFrontmatter(fs.readFileSync(file, "utf8"));
}
function formatDate(iso: string): string {
@@ -64,7 +68,10 @@ export async function generateMetadata({
return {
title: `${post.title} — DashCaddy Blog`,
description: post.description,
alternates: { canonical: `https://dashcaddy.net/blog/${post.slug}/` },
alternates: {
canonical: `https://dashcaddy.net/blog/${post.slug}/`,
types: { "application/rss+xml": "https://dashcaddy.net/feed.xml" },
},
keywords: post.tags,
authors: [{ name: post.author }],
openGraph: {
+3
View File
@@ -30,6 +30,9 @@ export const metadata: Metadata = {
description:
"Deploy 77 Docker apps with one click. AI-powered self-hosting.",
},
alternates: {
types: { "application/rss+xml": "https://dashcaddy.net/feed.xml" },
},
};
// Inline script to set theme class BEFORE first paint — prevents FOUC
@@ -0,0 +1,90 @@
---
title: "How to Access Self-Hosted Apps Remotely (Without Exposing Your Network)"
description: "Port forwarding, VPNs, and private networks compared. Learn the safe way to reach self-hosted apps from anywhere, and how to scope a service to your own devices."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["access self-hosted apps remotely", "remote access homelab", "tailscale homelab", "secure remote access", "self-hosted vpn"]
readingMinutes: 8
---
A self-hosted app you cannot reach from outside your house is a hobby. The moment you can open it from a phone on cellular data, it becomes infrastructure. The difference between those two states is one honest decision about **how** you expose it.
Get that decision right and you have a service. Get it wrong and you have an open port on your home network and a very long weekend.
## The three ways to reach a self-hosted app
### 1. Port forwarding
You forward a port on your router to the server, and the app is on the public internet.
**Pros:** works from any device, no client software, no third party.
**Cons:** you have now published a service to the entire internet. Anything unpatched, misconfigured, or weakly authenticated is fair game. You also need a public IP, which many connections no longer provide.
Port forwarding is not automatically wrong — public sites need it. But it should be a deliberate decision for a service you intend to be public, not the default for your dashboard.
### 2. A VPN back into your network
You run WireGuard or OpenVPN on your server and connect to it from your devices. Once connected, you are effectively on your LAN.
**Pros:** nothing is exposed except the VPN port; strong cryptography; you reach every service at once.
**Cons:** your traffic routes through home while connected, the VPN endpoint itself is public, and per-device onboarding gets tedious as the device count grows.
This is the classic answer and it is a good one. Its weak point is that the tunnel is all-or-nothing: connected means *everything* is reachable.
### 3. A private mesh network
Tools like Tailscale and Headscale build an encrypted peer-to-peer network between your devices and servers, without opening ports at all. Each machine gets a stable private address.
**Pros:** no inbound ports to open; works behind NAT and CGNAT; identity-based access controls; you can share one service with one person instead of the whole network.
**Cons:** it is a managed overlay (unless you self-host the control plane), and you should understand the trust model before you rely on it.
For most homelabs in 2026, this is the best default for private services — and it pairs well with a public reverse proxy for the few services that genuinely need to be public.
## The rule that keeps you out of trouble
**Expose deliberately, per service. Never expose a whole machine by accident.**
Every service should have an answer to: "who is allowed to reach this, and from where?" The common failure is a platform that treats *everything* as public the moment it is deployed. The correct behavior is a per-service choice:
- **Private** — reachable only from your own devices over the private network.
- **Restricted** — reachable from the internet, but only from specified IP ranges or behind an identity provider.
- **Public** — reachable by anyone, which should be a short and intentional list.
## How DashCaddy handles this
DashCaddy's deploy options map directly onto that rule, which is why they are worth knowing before you expose anything:
- **Private-network scoping.** A service can be restricted to your private network so it never appears on the public proxy at all. For a dashboard, an admin panel, or a database UI, this is the right default.
- **Allowed IP ranges.** When a service must be reachable from outside, you can permit specific CIDR ranges rather than the whole internet.
- **Automatic HTTPS.** Services that *are* public still get a real certificate, so you are not trading security for convenience.
- **A security surface.** The Security Center exists so the answer to "what is exposed right now?" is a screen you can look at, not a guess.
These are per-deployment settings, not global switches — so exposing one service never quietly exposes the rest. The [installation docs](/docs/installation/) cover the dashboard, and the [catalog guides](/docs/catalog/) document the deploy payload per app; the [WireGuard template](/docs/catalog/wireguard/) is there if you prefer to build your own tunnel instead of using a mesh network.
## A practical setup, start to finish
1. **Install DashCaddy** on the host that will run your private services.
2. **Join your devices to a private network.** Install Tailscale (or point clients at your own Headscale) on your laptop, phone, and the server.
3. **Deploy private services with network scoping on.** Your dashboard, monitoring, and admin tools should not be public. Ever.
4. **Pick the one or two services that must be public** — a media server for the family, a photo server you share with a relative — and deploy those normally so they get a certificate and a real URL.
5. **Verify from outside.** Turn off Wi-Fi on your phone. The private services should be reachable over the private network; the public ones should load at their URL; nothing else should answer.
That five-step sequence gets you remote access without a port-forwarding free-for-all.
## Mistakes worth avoiding
- **Forwarding 443 to a whole host.** You publish every service that host runs, including the ones you forgot about.
- **Relying on "nobody knows the URL."** Security by obscurity fails the first time a certificate transparency log is read.
- **Leaving an admin UI on the public internet.** Dashboards, database admin tools, and container managers belong on the private network.
- **Forgetting the renewal.** A private setup with an expired certificate trains you to click through warnings, which is exactly the habit that gets people phished.
- **No monitoring on the remote path.** Remote access that silently stops working is worse than none — see the [homelab monitoring guide](/blog/homelab-monitoring-guide/) for what to watch.
## Getting started
Install DashCaddy on any Linux host:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Then deploy something, scope it to your private network, and confirm you can reach it from your phone with Wi-Fi off. If you are still choosing what to run, the [full catalog](/blog/self-hosted-app-catalog-guide/) and the [best self-hosted apps roundup](/blog/best-self-hosted-apps-homelab/) are good next reads, and [one-click Docker deployment](/blog/one-click-docker-deployment/) explains what happens during a deploy.
@@ -0,0 +1,136 @@
---
title: "Self-Hosted Docker Container Management Made Simple"
description: "Master Docker container management in your homelab. Compare methods, learn best practices, and discover how DashCaddy simplifies running containers."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["docker container", "container management", "homelab docker", "docker-compose", "docker swarm"]
readingMinutes: 7
---
Docker has become the backbone of modern self-hosting. Whether you're running a media server, a home automation system, or personal cloud storage, containers make deployment and management significantly easier.
## Understanding Docker Container Basics
Before diving into management tools, let's cover the fundamentals:
### What Are Containers?
Containers are lightweight, standalone packages that include everything needed to run software: code, runtime, libraries, and system settings. Unlike virtual machines, containers share the host OS kernel, making them much more efficient.
### Key Docker Concepts
- **Image** — A read-only template for creating containers
- **Container** — A running instance of an image
- **Volume** — Persistent storage outside containers
- **Network** — How containers communicate
- **Compose** — Multi-container definitions
## Methods for Managing Docker Containers
### 1. Command Line (Docker CLI)
The native approach:
```bash
docker run -d --name jellyfin jellyfin/jellyfin
docker ps
docker logs jellyfin
```
**Pros:** Full control, no extra software
**Cons:** Steep learning curve, manual everything
### 2. Portainer
Web-based GUI for Docker:
```bash
docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
```
**Pros:** Visual interface, container inspection
**Cons:** Manual SSL setup, basic multi-host support
### 3. CasaOS
User-friendly homelab OS:
```bash
curl -fsSL https://get.casaos.io | bash
```
**Pros:** Extremely simple, good for beginners
**Cons:** Limited customization, few advanced features
### 4. DashCaddy
AI-powered container management:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Then deploy with plain English:
> "Set up Jellyfin for my media library"
**Pros:** AI automation, automatic SSL, reverse proxy, multi-host
**Cons:** Newer project, smaller template library
## Best Practices for Container Management
### Resource Limits
Always set memory and CPU limits:
```yaml
services:
jellyfin:
image: jellyfin/jellyfin
deploy:
resources:
limits:
memory: 4G
cpus: '2.0'
```
### Health Checks
DashCaddy includes built-in health monitoring. For manual setups:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
interval: 30s
timeout: 10s
retries: 3
```
### Regular Updates
Keep containers updated:
```bash
docker-compose pull
docker-compose up -d
```
DashCaddy handles this automatically across your fleet.
### Backup Strategy
Always back up:
- Volumes (your data)
- Docker Compose files (your configuration)
- Environment variables (secrets)
## Why DashCaddy for Container Management?
1. **Plain English Deployment** — No more copying docker-compose files
2. **Automatic SSL** — HTTPS out of the box
3. **Reverse Proxy Built-In** — No manual Nginx/Caddy config
4. **Multi-Host** — Manage all your servers from one dashboard
5. **AI Assistance** — Ask questions, get help with configuration
## Get Started
Ready to simplify your Docker management?
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Then try: "Deploy Jellyfin" and watch it happen automatically.
@@ -0,0 +1,63 @@
---
title: "Docker Dashboard for Self-Hosted Apps: Why DashCaddy Beats Portainer"
description: "Compare DashCaddy vs Portainer for managing self-hosted Docker containers. Learn about AI-powered deployment, automatic SSL, and multi-host management."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["docker dashboard", "portainer alternative", "self-hosted docker", "docker management", "container management"]
readingMinutes: 8
---
Managing Docker containers shouldn't require a computer science degree. Whether you're running a homelab with a few containers or managing a fleet of production servers, the right Docker dashboard makes all the difference.
## Why Self-Hosted Docker Management Matters
Running self-hosted applications gives you control over your data, privacy, and costs. But managing those applications through the command line gets old fast. That's where Docker dashboards come in.
Popular options include:
- **Portainer** — the longtime favorite for container management
- **CasaOS** — user-friendly but limited
- **DashCaddy** — newer entrant with AI-powered features
## DashCaddy vs Portainer: Feature Comparison
| Feature | DashCaddy | Portainer |
|---------|------------|-----------|
| AI-Powered Deployment | ✓ Plain English | ✗ Manual |
| Automatic SSL | ✓ Built-in | ✓ Via Caddy |
| Multi-Host Fleet | ✓ Yes | ✓ Business tier |
| App Templates | 77 one-click | 100+ |
| MCP Server | ✓ For AI agents | ✗ |
| Reverse Proxy | Automatic | Manual |
## Plain English Deployment
Here's where DashCaddy stands out. Instead of clicking through configuration screens, you can deploy apps using natural language:
> "Deploy Jellyfin with automatic SSL and make it accessible at media.mydomain.com"
DashCaddy understands intent, configures DNS, issues SSL certificates, and sets up the reverse proxy—all automatically.
## When to Choose Portainer
Portainer remains excellent for:
- Organizations already invested in Portainer
- Teams needing fine-grained container inspection
- Scenarios requiring legacy Docker Compose support
## When DashCaddy Wins
DashCaddy shines when you want:
- **Speed** — Deploy apps in seconds, not minutes
- **Simplicity** — No manual reverse proxy configuration
- **AI integration** — MCP server for automation
- **Multi-host management** — Single dashboard for your entire fleet
## Getting Started
Install DashCaddy on any Linux server:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Then access the dashboard at `http://your-server:3000` to start deploying apps with plain English commands.
@@ -0,0 +1,139 @@
---
title: "Getting Started with DashCaddy: From Blank Server to a Running App"
description: "A step-by-step walkthrough: pick a server, install DashCaddy with one command, open the dashboard, deploy your first self-hosted app, and get it on HTTPS."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["dashcaddy getting started", "self-host apps", "docker deployment", "beginner homelab", "self-hosting tutorial"]
readingMinutes: 7
---
Self-hosting means running software on hardware you control. The part that stops most people is everything around the app: the container, the reverse proxy, the DNS record, the certificate. A self-hosting platform handles that layer so installing a password manager becomes one decision instead of twenty. [What a self-hosting platform is](/blog/self-hosting-platform-guide/) covers the bigger picture.
This walkthrough takes you from a bare Linux box to your first app running behind a real HTTPS URL, using Vaultwarden as the example because it is small, useful, and a good first pick.
## What DashCaddy does for you
DashCaddy is a control plane for Docker applications. When you deploy a service, it creates the container, writes the Caddy reverse-proxy route, creates the DNS record, issues the HTTPS certificate, and starts health checks. Apps get a subdomain and a padlock without you touching a config file. See the [overview](/blog/what-is-dashcaddy/) for the layers; here we get one running.
## What you need before you start
- **A Linux host that stays on.** Ubuntu 22.04 LTS, Debian 12, or any recent distribution. A spare mini-PC, an old desktop, or a cheap VPS works. **2 GB of RAM and a few GB of disk** is enough.
- **Root or sudo access.** The installer writes to `/etc/dashcaddy`, `/opt/dockerdata`, and the Docker daemon.
- **Ports 80 and 443 reachable** if you want public HTTPS.
- **A domain, if you want automatic DNS and SSL.** A real domain gets a trusted certificate automatically; a custom TLD like `dashcaddy.home` works on your LAN instead.
- **Docker does not have to be installed already.** If it is missing, the installer adds Docker Engine and the Compose plugin.
## Install DashCaddy with one command
On the host you want to use, run:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Answer the prompts (domain, install location, storage size); the defaults are sensible. The installer runs seven idempotent steps; re-running it reconciles an existing install rather than clobbering it:
1. Detects your environment (OS, package manager, disk, memory, IP addresses)
2. Installs Docker Engine and the Compose plugin if missing
3. Writes configuration to `/etc/dashcaddy` and `/opt/dockerdata`
4. Builds the DashCaddy container image
5. Starts the API container, `dashcaddy-api`, on port 3001
6. Waits for the API to answer its `/healthz` check
7. Prints the dashboard URL
If a step fails, it stops and tells you what to re-run. The [installation guide](/docs/installation/) covers the details.
### The scripted install variant
To install without prompts, pass your domain inline:
```bash
curl -fsSL https://get.dashcaddy.net | bash -s -- --domain dashcaddy.example.com
```
This is the non-interactive form; you can pass a custom TLD or a quick local address instead.
## Open the dashboard for the first time
Open the URL the installer printed. The docs use `https://dashcaddy.home` as the default dashboard address, but the hostname is configurable, so yours may differ.
On first visit you land on the setup wizard, which runs once and saves your choices:
1. **Pick a configuration profile** (*Professional Home Lab*, *Simple*, or *Public* for an internet-exposed VPS)
2. **Configure your network** (your domain and the LAN IP used for routing)
3. **Set storage limits** (a cap on Docker data and free-space headroom)
4. **Review your choices**
5. **Confirm disk safety**
Then create your first admin account and enroll a TOTP authenticator. Store the recovery codes somewhere safe.
## Deploy your first app: Vaultwarden
Vaultwarden is a lightweight, Bitwarden-compatible password manager. It is a good first deployment: a single small container in the Security category, no media library or host paths required, and immediately useful. It follows the same path any template does, which [one-click Docker deployment](/blog/one-click-docker-deployment/) describes in general terms.
### Deploy it from the dashboard
1. Sign in, then click the **App Selector** button on the dashboard home page.
2. Pick **Vaultwarden** from the Security category. The template uses the image `vaultwarden/server:latest`.
3. Fill in the deployment form: **subdomain** (the suggested default is `vault`) and **host port** (the default is `8088`).
4. Click **Deploy**.
DashCaddy pulls the image, creates the persistent volume, writes the Caddy route, creates the DNS record, and waits up to **30 seconds** for the container's health check on `/` to pass. It is atomic: if a step fails, the deployment rolls back rather than leaving a half-wired service. Other templates follow the same flow, documented in [deploy your first service](/docs/first-service/).
When the service card shows **Running**, the URL becomes clickable. It is built from your subdomain and configured TLD, so a host using the `.home` TLD gives you `https://vault.home`.
### Deploy it from the API instead
Everything the dashboard does is also a REST call:
```bash
curl -X POST https://dashcaddy.home/api/v1/apps/deploy \
-H "X-API-Key: $DASHCADDY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"appId": "vaultwarden",
"config": { "subdomain": "vault", "port": 8088 }
}'
```
The response is synchronous and includes `success`, `containerId`, `url`, `message`, and `setupInstructions`. There is no separate status-poll endpoint; the dashboard updates live. See the [API docs](/docs/api/) for the full surface and the [Vaultwarden entry](/docs/catalog/vaultwarden/) for its volume path and environment variables.
There is also an AI path: `POST /api/v1/ai/intent` returns a structured intent from plain English but does not deploy; your client or the built-in MCP server then calls `/api/v1/apps/deploy`.
## Verify it actually works
**Open the URL and check the padlock.** Browse to `https://vault.<your-tld>`. A real domain gets a trusted Let's Encrypt certificate automatically; a custom TLD is trusted once the DashCA root certificate is installed on the device. A browser warning means the app runs but trust is not set up yet.
**Restart the container and confirm data persists.** Vaultwarden stores its data in the host volume `/opt/vaultwarden/data:/data`. Restart the container from the dashboard (or `docker restart <containerId>`) and confirm your account is still there. Containers are disposable; volumes are not, and this proves your data is on disk.
DashCaddy keeps watching: a health check runs every 30 seconds, and three consecutive failures mark the service **Unhealthy** on the dashboard.
## Five things to do next
1. **Deploy a second app.** Try [Jellyfin](/docs/catalog/jellyfin/) for media, [Immich](/docs/catalog/immich/) for photos, or [Uptime Kuma](/docs/catalog/uptime-kuma/) to watch the first two. Each gets its own subdomain and certificate on the same host.
2. **Point a real domain at it.** With DNS integration configured, DashCaddy creates records automatically and serves a trusted certificate. Behind CGNAT, a tunnel works instead; [reaching self-hosted apps remotely](/blog/access-self-hosted-apps-remotely/) covers the options.
3. **Browse the catalog.** There are 77 one-click templates across 17 categories in the [app catalog](/docs/catalog/); the [catalog guide](/blog/self-hosted-app-catalog-guide/) explains how to choose.
4. **Set up backups.** Your state lives in `/opt/dockerdata` and `/etc/dashcaddy`; snapshot both off the host. The dashboard's Backup button exports a single JSON file for a no-setup option.
5. **Learn the API.** The platform exposes 400+ REST route handlers, so anything you can click, you can script. Start with the [API docs](/docs/api/).
## When DashCaddy is not the right choice
- **You want exactly one app and enjoy hand-editing compose files.** If you run a single container and like tuning your own Caddyfile, plain Docker Compose will do the job without a control plane.
- **You need Kubernetes-style orchestration.** DashCaddy runs Docker and Compose on a Linux host. A scheduler with Kubernetes semantics across a large container fleet is a different tool.
- **You want deep per-container inspection in one narrow workflow.** A focused container tool may fit better than a full platform.
## FAQ
### Do I need a domain to start?
No. A real domain gives you public DNS and a trusted Let's Encrypt certificate, but you can begin with a custom TLD on your LAN or a direct IP address and add a domain later. Nothing in the deployment flow changes.
### How long does deploying an app take?
For a template like Vaultwarden, the deploy waits up to 30 seconds for the health check to pass. Most of that time is pulling the image and starting the container.
### What happens to my data if a container is removed?
As long as your data lives in a mounted volume, it survives restarts and redeploys. Vaultwarden keeps everything in `/opt/vaultwarden/data` on the host, so removing the container alone does not delete it. That is also why backing up those volumes matters.
If a step above did not behave as described, the [troubleshooting guide](/docs/troubleshooting/) walks the stack layer by layer, and the [product overview](/docs/overview/) explains how the pieces fit together. When you are ready to compare plans, see [pricing](/pricing/).
+120
View File
@@ -14,6 +14,126 @@ export interface PostMeta {
}
export const POSTS: PostMeta[] = [
{
slug: "why-shipdeck-replaced-docker",
title: "Why ShipDeck Replaced Docker at DashCaddy: One Command to Deploy",
description:
"ShipDeck is the deployer DashCaddy uses for its own services — a single Go binary that replaces Docker, Docker Compose, and handwritten deploy scripts with one command. Here's what it does, why Docker wasn't the right fit for fleet deployments, and what makes ShipDeck better.",
date: "2026-10-08",
author: "DashCaddy Team",
tags: ["shipdeck", "docker alternative", "deployment tool", "fleet deployer", "systemd deployment", "self-hosted deployment"],
readingMinutes: 7,
},
{
slug: "getting-started-with-dashcaddy",
title: "Getting Started with DashCaddy: From Blank Server to a Running App",
description:
"A step-by-step walkthrough: pick a server, install DashCaddy with one command, open the dashboard, deploy your first self-hosted app, and get it on HTTPS.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["dashcaddy getting started", "self-host apps", "docker deployment", "beginner homelab", "self-hosting tutorial"],
readingMinutes: 7,
},
{
slug: "self-host-vaultwarden",
title: "Self-Host Vaultwarden in 10 Minutes (Your Own Password Manager)",
description:
"Run your own Bitwarden-compatible password manager on your own server: install Vaultwarden with one click, put it on HTTPS, and keep your vaults off someone else's cloud.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["self-host vaultwarden", "self hosted password manager", "bitwarden alternative", "vaultwarden docker", "homelab security"],
readingMinutes: 6,
},
{
slug: "self-host-your-photo-library",
title: "Self-Host Your Photo Library: Immich in 10 Minutes",
description:
"Stop paying a monthly subscription to look at your own photos. Deploy Immich with one click, put it behind HTTPS, and get automatic phone backup onto hardware you own.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["self-host immich", "self hosted photo backup", "google photos alternative", "immich docker", "photo library"],
readingMinutes: 6,
},
{
slug: "self-hosted-app-catalog-guide",
title: "77 Self-Hosted Apps You Can Install in One Click (Full Catalog)",
description:
"A complete map of the 77 apps DashCaddy can deploy in one click — media, files, photos, DNS, security, databases, automation and more, each with its own install guide.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["self-hosted apps", "docker app catalog", "self-hosted app list", "one-click apps", "homelab apps"],
readingMinutes: 9,
},
{
slug: "self-hosting-platform-guide",
title: "Self-Hosting Platform: What It Is and How to Choose One in 2026",
description:
"A self-hosting platform turns a bare Linux server into somewhere you can run apps safely. Learn the categories, the questions that matter, and how to pick one.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["self-hosting platform", "self hosted platform", "self-hosting software", "docker platform", "homelab"],
readingMinutes: 8,
},
{
slug: "one-click-docker-deployment",
title: "One-Click Docker Deployment: From Blank Server to Running App",
description:
"What one-click Docker deployment really involves — image, volumes, ports, DNS, TLS, and health checks — and how to get all of it in a single action.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["one-click docker deployment", "deploy docker app", "docker deployment tool", "docker templates", "docker install"],
readingMinutes: 7,
},
{
slug: "access-self-hosted-apps-remotely",
title: "How to Access Self-Hosted Apps Remotely (Without Exposing Your Network)",
description:
"Port forwarding, VPNs, and private networks compared. Learn the safe way to reach self-hosted apps from anywhere, and how to scope a service to your own devices.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["access self-hosted apps remotely", "remote access homelab", "tailscale homelab", "secure remote access", "self-hosted vpn"],
readingMinutes: 8,
},
{
slug: "why-self-host-benefits",
title: "Why Self-Host Your Applications? The Complete Benefits Guide",
description:
"Discover why self-hosting your applications saves money, improves privacy, and gives you control. Learn what's possible when you run your own services.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["self-hosting benefits", "why self-host", "homelab advantages", "privacy", "data ownership"],
readingMinutes: 7,
},
{
slug: "docker-container-management",
title: "Self-Hosted Docker Container Management Made Simple",
description:
"Master Docker container management in your homelab. Compare methods, learn best practices, and discover how DashCaddy simplifies running containers.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["docker container", "container management", "homelab docker", "docker-compose", "docker swarm"],
readingMinutes: 7,
},
{
slug: "what-is-dashcaddy",
title: "What is DashCaddy? The AI-Powered Self-Hosting Platform Explained",
description:
"Learn what DashCaddy is—an AI-native platform for deploying, securing, and managing self-hosted applications with plain English commands.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["what is dashcaddy", "self-hosting platform", "AI self-hosting", "docker deployment", "automated ssl"],
readingMinutes: 6,
},
{
slug: "docker-dashboard-self-hosted",
title: "Docker Dashboard for Self-Hosted Apps: Why DashCaddy Beats Portainer",
description:
"Compare DashCaddy vs Portainer for managing self-hosted Docker containers. Learn about AI-powered deployment, automatic SSL, and multi-host management.",
date: "2026-10-07",
author: "DashCaddy Team",
tags: ["docker dashboard", "portainer alternative", "self-hosted docker", "docker management", "container management"],
readingMinutes: 8,
},
{
slug: "homelab-beginners-guide",
title: "How to Build a Homelab: A Beginner's Home Server Guide",
@@ -0,0 +1,94 @@
---
title: "One-Click Docker Deployment: From Blank Server to Running App"
description: "What one-click Docker deployment really involves — image, volumes, ports, DNS, TLS, and health checks — and how to get all of it in a single action."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["one-click docker deployment", "deploy docker app", "docker deployment tool", "docker templates", "docker install"]
readingMinutes: 7
---
"One-click deploy" sounds like marketing until you have done the manual version. Then it sounds like a description of a very long list of things you no longer have to do.
This article breaks down what actually happens between "I want to run Immich" and "Immich is live at a real URL with a valid certificate" — and why doing it by hand is where most homelab evenings go.
## The nine things that happen when you deploy a container
A container is the easy part. Everything around it is the work:
1. **Pick the image and pin it.** `:latest` is convenient and occasionally a surprise. A platform should know the image it is going to pull.
2. **Decide the persistent paths.** Which directories must survive a container rebuild? Get this wrong and your photo library lives inside a container that disappears on the next update.
3. **Map ports.** Host port 8080 is taken. Again.
4. **Set environment variables and secrets.** Database passwords, admin users, API tokens, claim tokens.
5. **Create or attach a network.** So the container can talk to its database and nothing else it should not.
6. **Write a reverse-proxy entry.** Caddy or Nginx config that routes `app.example.com` to the right container port.
7. **Create the DNS record.** The subdomain has to resolve, and it has to resolve to the right host.
8. **Issue and install a TLS certificate.** Then renew it forever.
9. **Wait for a health check.** "Started" is not "working." The container can be running and still return 500s.
You can do all nine by hand. People do. It is also the reason so many homelabs have a service that has been "almost done" since March.
## What "one-click" should mean
A deployment button that only covers step 1 is not one-click; it is `docker run` with a nicer font. Genuine one-click deployment covers the whole list, in order, with a real result at the end: a URL that opens, over HTTPS, with a green health check.
DashCaddy's deploy path does exactly that. You pick a template from the [app catalog](/docs/catalog/), fill in the few values that are genuinely yours (a subdomain, maybe a media path or a claim token), and press Deploy. DashCaddy then:
- pulls the pinned image;
- creates the persistent volumes;
- attaches the container to its network;
- writes the reverse-proxy route;
- creates the DNS record;
- issues the certificate;
- and polls the app's health endpoint before reporting success.
If the health check does not pass, you get a failure — not a container that is technically running and practically broken.
## The same deploy, two ways
Every template is available through the dashboard and through the REST API. The API path is what makes automation possible. A deploy is a single authenticated POST whose body names the app and the handful of settings that matter:
```bash
curl -X POST https://your-host/api/v1/apps/deploy \
-H "X-API-Key: <your key>" \
-H "Content-Type: application/json" \
-d '{
"appId": "immich",
"config": { "subdomain": "photos" }
}'
```
Because the endpoint is a normal REST route, the same call works from a shell script, a CI job, or an AI assistant connected over the [MCP server](/blog/deploy-self-hosted-apps-with-ai/). The install guides in the catalog document the exact payload for each app — see the [Plex guide](/docs/catalog/plex/) or the [Jellyfin guide](/docs/catalog/jellyfin/) for the pattern.
## Where manual deployment still makes sense
Automation is not a religion. Reach for the manual path when:
- you are **evaluating** a new app and want to poke at it without a permanent footprint;
- the image needs a **custom build** or a patch the template does not cover;
- you are **debugging** and need to run the container with different flags than the template uses.
That is also why a platform should let you **attach to an existing container** rather than forcing every service to be born inside it. DashCaddy supports adopting an already-running container so existing work is not thrown away.
## A deployment checklist that fits on a napkin
Whatever tool you use, verify these five things after the first deploy:
- **The URL opens over HTTPS** with a valid, non-self-signed certificate.
- **The data path is a host directory you recognize** — check with `docker inspect` if unsure.
- **The DNS record is yours** and points where you think it does.
- **The health check is green**, not merely "running."
- **A restart brings it back.** `reboot` the host and confirm the service returns without you.
If all five hold, you have done real one-click deployment, regardless of which tool did it.
## Getting started
Install DashCaddy on any Linux host:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Then open the dashboard, browse the [catalog](/docs/catalog/), and deploy something. The [installation docs](/docs/installation/) cover host requirements, and [First Service](/docs/first-service/) walks through the very first deploy in detail.
Once the first app is up, the natural next questions are [how to access it remotely](/blog/access-self-hosted-apps-remotely/) and [how to know when it goes down](/blog/homelab-monitoring-guide/).
+98
View File
@@ -0,0 +1,98 @@
---
title: "Self-Host Vaultwarden in 10 Minutes (Your Own Password Manager)"
description: "Run your own Bitwarden-compatible password manager on your own server: install Vaultwarden with one click, put it on HTTPS, and keep your vaults off someone else's cloud."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["self-host vaultwarden", "self hosted password manager", "bitwarden alternative", "vaultwarden docker", "homelab security"]
readingMinutes: 6
---
If you are deciding between Bitwarden and Vaultwarden, you are asking the wrong question. They are not competing products. Bitwarden is the company that runs a cloud service and publishes the open-source client apps. Vaultwarden is a lightweight, unofficial server that speaks the same protocol, so the official Bitwarden apps and browser extensions can talk to it. The real question is simpler: do you want your password database sitting on someone else's server, or on hardware you control? That is the whole decision. Vaultwarden just happens to be the easiest way to answer it.
## What Vaultwarden is
Vaultwarden is an unofficial, Bitwarden-compatible password manager server written in Rust. It implements the Bitwarden API closely enough that the official Bitwarden clients work against it unchanged. Because it is built for small deployments rather than millions of accounts, it stays small: one container, a data directory, and very little memory. DashCaddy ships it as a one-click template in the [app catalog](/docs/catalog/), which means the container, the reverse proxy, the DNS record, and the certificate are handled for you.
## Why this is the best thing to self-host first
If you self-host one service, make it the password manager. Every other service you deploy will need credentials: an admin account, an API key, a database password. If that pile of secrets lives in a consumer cloud vault, you have moved the most valuable data you own to a third party and accepted their uptime, their breach history, and their business model. A self-hosted vault flips that. Your encrypted database stays on your disk, and the only thing crossing the network is ciphertext the server cannot read. The [case for self-hosting](/blog/why-self-host-benefits/) is strongest here.
## What "10 minutes" actually covers
Ten minutes is honest if you have the prerequisites ready. It covers deploying the container, getting a valid TLS certificate, creating your account, and pointing the Bitwarden apps at your server. It does not cover buying a domain, waiting for DNS to propagate, or setting up backups, which is why those are worth doing before you start.
## Prerequisites
- **A domain.** This is not optional. Bitwarden's browser extensions and mobile apps refuse to talk to a plain `http://` origin; they need a real TLS origin. So you need a hostname like `vault.example.com` that resolves to your server.
- **A server** running Linux with Docker, with ports 80 and 443 reachable from the internet.
- **The decision to keep working backups.** A password vault on one disk is one disk failure away from losing every credential you own. Decide now where the second copy goes.
Then install DashCaddy on that host:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
The [installation guide](/docs/installation/) covers the details.
## Deploy Vaultwarden
From the dashboard:
1. Sign in and open the App Selector.
2. Pick **Vaultwarden** from the Security category.
3. Accept the suggested subdomain (`vault`) and port (`8088`), or change them.
4. Click **Deploy**.
DashCaddy pulls the `vaultwarden/server:latest` image, creates the persistent volume at `/opt/vaultwarden/data`, writes the Caddy reverse-proxy route, creates the DNS record, and requests the certificate. It waits for the container's health check to pass before it reports the service as **Running**. When it does, the URL is clickable.
If you would rather script it, the same deploy is one API call with an `X-API-Key` header:
```bash
curl -X POST https://your-dashboard/api/v1/apps/deploy \
-H "X-API-Key: $DASHCADDY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"appId":"vaultwarden","config":{"subdomain":"vault","port":8088}}'
```
The response is synchronous and returns `success`, `containerId`, `url`, `message`, and `setupInstructions`. There is no polling step.
## Make sure it is on HTTPS
If the DNS record resolved and ports 80/443 were reachable, the certificate was issued during deploy and you are already done. If DashCaddy fell back to direct-IP access or a self-signed certificate, the usual cause is DNS not pointing at the host yet, or a firewall blocking the certificate challenge. The [troubleshooting guide](/docs/troubleshooting/) walks through it layer by layer. Verify the certificate is trusted before you create your account, because the apps will refuse a bad origin anyway.
## Create your account, then close the door
The default Vaultwarden template ships a `DOMAIN` and an `ADMIN_TOKEN`, and the first thing to do is change the admin token. Then create your own account, and once it exists, disable further signups so nobody else can register on your instance. This is the single most common mistake with a fresh Vaultwarden: leaving public registration open on a server that is now findable. The [Vaultwarden install guide](/docs/catalog/vaultwarden/) lists this in its post-install checklist.
## Connect the real Bitwarden apps
You do not need any Vaultwarden-specific client. Install the official Bitwarden browser extension, desktop app, or iOS/Android app, and before logging in, open the settings and set the self-hosted environment server URL to your own `https://vault.example.com`. Log in with the account you just created. Your vault now syncs to your hardware. If you want to reach it from outside your home network safely, read [accessing self-hosted apps remotely](/blog/access-self-hosted-apps-remotely/).
## Backups are not optional here
An encrypted vault on one disk is a single point of failure for every account you own. Export your vault periodically from the client, and back up the volume, which DashCaddy keeps at `/opt/vaultwarden/data`. Store the copy somewhere that is not the same machine. If you have not thought about this before, the [catalog guide](/blog/self-hosted-app-catalog-guide/) covers how backups fit into a self-hosted stack.
## Honest caveats
- **You are now the operator.** Uptime, upgrades, and backups are your job. DashCaddy manages the container lifecycle and the certificate, but it will not fix a broken restore for you.
- **Vaultwarden is not the official Bitwarden server.** It is a compatible reimplementation, and it can occasionally lag new Bitwarden client features. For most personal and family use this does not matter; for an enterprise with compliance requirements, it might.
- **Real secrets deserve real protection.** Turn on two-factor authentication on your account, use a strong master password you have not reused anywhere, and keep the admin token private.
If self-hosting is new to you, start with [what DashCaddy is](/blog/what-is-dashcaddy/) and [getting started](/blog/getting-started-with-dashcaddy/), then come back. The software itself is free; what you pay for, if anything, is DashCaddy's premium features, which are described on the [pricing page](/pricing/).
## FAQ
### Is Vaultwarden the same as Bitwarden?
No. Bitwarden is the official product and cloud service. Vaultwarden is an unofficial, Bitwarden-compatible server you run yourself. The official Bitwarden client apps connect to it because it implements the same API.
### Does the official Bitwarden app work with my own server?
Yes. In the app's settings, before you log in, select the self-hosted environment option and enter your own server URL. Everything else works the way it does against Bitwarden's cloud.
### What happens if my server goes down?
Your vault stops syncing, but the copies already downloaded to your phone, browser, and desktop remain readable offline. That is exactly why you keep a second backup copy somewhere else.
You can run every one of these steps from one dashboard. Browse the [catalog](/docs/catalog/) to see what else deploys the same way, or add [Uptime Kuma](/docs/catalog/uptime-kuma/) to get alerted if the vault ever goes offline.
@@ -0,0 +1,94 @@
---
title: "Self-Host Your Photo Library: Immich in 10 Minutes"
description: "Stop paying a monthly subscription to look at your own photos. Deploy Immich with one click, put it behind HTTPS, and get automatic phone backup onto hardware you own."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["self-host immich", "self hosted photo backup", "google photos alternative", "immich docker", "photo library"]
readingMinutes: 6
---
Your photos are the one cloud subscription you will pay for forever, and the one library that only grows. Every year you add more, the backup gets bigger, and the cost that felt trivial at the start quietly compounds. Moving that library onto a machine you own ends the meter. Immich is the self-hosted photo and video app that makes it practical, and with DashCaddy it deploys with one click.
## What Immich is
Immich is a self-hosted photo and video library: automatic backup from your phone camera roll, a searchable timeline, albums, and sharing, with features such as object and face recognition running on machine learning. Its own project describes it as a Google Photos alternative, and DashCaddy lists it in the Photos category of the [app catalog](/docs/catalog/). It is not a thin file browser. It indexes your library, generates thumbnails, and gives you the timeline and albums you expect from a hosted service.
## Why photos are the hardest thing to leave a cloud for
Passwords can be exported in a file you carry around. Photos cannot. The library grows monotonically, it usually runs to tens or hundreds of gigabytes, and it is tied to a mobile app that has spent years syncing in the background without you thinking about it. Leaving means re-uploading everything, retraining your family's habits, and trusting that the new place will not lose a decade of images. That friction is the whole reason the subscription model works. Self-hosting removes the friction, but it also moves the responsibility to you, which is why the backup section below matters more than the install section.
## Doing the storage maths honestly
The number that matters is the free space you need at the destination, not the size of the upload. Two rules of thumb:
- Estimate the library size by multiplying your photo count by an average file size. Phone photos commonly land in the 3 to 5 MB range; assume the higher end so you are not surprised.
- Then plan for **at least double that figure in free space**, plus a second copy elsewhere.
The doubling is not padding. Immich keeps the original as you uploaded it plus generated derivatives such as thumbnails and transcoded video, and any import or migration temporarily holds both source and destination. Running a photo server on a disk that is already 90 percent full is how people corrupt libraries. If your library is 200 GB, do not point Immich at a 250 GB disk. Point it at something with room to grow, because the library will keep growing.
## Prerequisites
- **A domain** for HTTPS, because the mobile app wants a real TLS origin and you will be reaching this from your phone over cellular. A hostname like `photos.example.com` resolving to your server is enough.
- **A Linux host with Docker**, with ports 80 and 443 reachable.
- **Storage with headroom**, per the maths above.
- **A decision about the second copy.** Decide where the off-machine backup goes before you upload anything you cannot replace.
Install DashCaddy on that host:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
## Deploy Immich
From the dashboard:
1. Sign in and open the App Selector.
2. Pick **Immich** from the Photos category.
3. Accept the suggested subdomain (`photos`) and port (`2283`), or change them.
4. Click **Deploy**.
DashCaddy pulls the `ghcr.io/immich-app/immich-server:latest` image, provisions the database and cache dependencies Immich needs, creates the persistent volumes at `/opt/immich/upload` and `/opt/immich/library`, writes the Caddy route, creates the DNS record, and requests the certificate. It waits for the container's health check at `/api/server-info/ping` before it reports the service as **Running**.
Via the API, it is the same single call:
```bash
curl -X POST https://your-dashboard/api/v1/apps/deploy \
-H "X-API-Key: $DASHCADDY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"appId":"immich","config":{"subdomain":"photos","port":2283}}'
```
The response comes back synchronously with `success`, `containerId`, `url`, `message`, and `setupInstructions`.
## Point your phone at your own server
Create your admin account in the web UI, then install the Immich mobile app on iOS or Android. On the login screen, set the server URL to your own `https://photos.example.com` instead of the hosted default. Log in and enable automatic backup for the camera roll. From then on, new photos upload over your network to your hardware, and the first sync will pull up the existing back-catalogue. If you want to reach the server outside your home network without opening it to the world, the [remote access guide](/blog/access-self-hosted-apps-remotely/) covers the options.
## The warning that matters
Immich is under very active development, and this is the one thing its own documentation is loudest about. The project warns users to keep their own backups and to read the release notes before every upgrade, because breaking changes do happen between versions. Take that seriously. DashCaddy does not auto-update your containers, so upgrading is a deliberate action: pull the new image and restart the container yourself. Read the release notes before you do, and confirm your backup is current first. The [Immich install guide](/docs/catalog/immich/) repeats the same caution, and the [troubleshooting guide](/docs/troubleshooting/) covers what to do if an upgrade goes wrong.
## Hardware notes, kept short
Immich will run on a modest machine. Machine learning features such as face recognition are the hungry part: they want more RAM and CPU, and they are much faster on a GPU. If your hardware is small, you can run Immich without those features and still get the timeline, albums, and automatic backup, which is the core of the product. Start there and add horsepower only if you find you miss the smart features.
## What about the photos already in your current cloud
Do not delete anything until the new library is populated and verified. Before you start, use your current provider's bulk-download tool to export your entire library, then upload it to your Immich instance, either through the web UI in batches or by placing files directly in the library path. Confirm the photos and their dates are showing in the timeline, and only then cancel the subscription. Keep the old account read-only for a while if you can, as cheap insurance.
## FAQ
### Do I have to stop using my current photo app?
No, but you should not run two sources of truth forever. The clean path is to export your existing library, import it into Immich, verify it, and then stop adding to the old one.
### How much storage do I need?
Roughly your library size times two or more, in free space, plus a separate copy on another machine or drive. Plan for it to keep growing.
### Will Immich lose my photos on an update?
Not if you follow the project's advice: keep your own backups and read the release notes before each upgrade. That is the honest answer, and it is exactly why the backup step comes before the install step.
Immich is one of 77 one-click templates in the [catalog](/docs/catalog/), and it deploys the same way you have read here. The software is free and open source; if you want the premium platform features, they are described on the [pricing page](/pricing/). Start with [what DashCaddy is](/blog/what-is-dashcaddy/) if you are new, or see the wider [self-hosted app catalog guide](/blog/self-hosted-app-catalog-guide/) for what to run next.
@@ -0,0 +1,171 @@
---
title: "77 Self-Hosted Apps You Can Install in One Click (Full Catalog)"
description: "A complete map of the 77 apps DashCaddy can deploy in one click — media, files, photos, DNS, security, databases, automation and more — each with an install guide."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["self-hosted apps", "docker app catalog", "self-hosted app list", "one-click apps", "homelab apps"]
readingMinutes: 9
---
The hardest part of a homelab is rarely the hardware. It is deciding what to run, then discovering that "install it" hides six more steps.
This page is the complete, current map of what DashCaddy deploys in one click: **77 application templates across 17 categories**, each with a dedicated install guide covering prerequisites, the exact deploy payload, and post-install verification. Every link below goes to that app's guide.
Everything here is deployable from the dashboard, from the REST API, or through the MCP server.
## Media (10)
Stream, read, and listen to your own library — no subscription, no regional licensing, no vendor deciding what you are allowed to own.
- [Plex](/docs/catalog/plex/) — stream your personal media collection anywhere
- [Jellyfin](/docs/catalog/jellyfin/) — free software media system, an alternative to Plex
- [Emby](/docs/catalog/emby/) — personal media server with apps for all devices
- [Audiobookshelf](/docs/catalog/audiobookshelf/) — self-hosted audiobook and podcast server
- [Navidrome](/docs/catalog/navidrome/) — modern music server and streamer
- [Calibre-Web](/docs/catalog/calibre-web/) — browse and read your ebook library in a browser
- [Kavita](/docs/catalog/kavita/) — fast, cross-platform manga and comic reader
- [Komga](/docs/catalog/komga/) — comics and manga server with a clean web reader
- [Airsonic Advanced](/docs/catalog/airsonic/) — stream your music collection to any device
- [Vintage Stereo](/docs/catalog/vintage-radio/) — a retro-styled internet radio player
## Media Management (8)
The tooling that turns a folder of files into an organized library.
- [Seerr](/docs/catalog/seerr/) — request management for your media stack
- [Sonarr](/docs/catalog/sonarr/) — TV series collection management
- [Radarr](/docs/catalog/radarr/) — movie collection management
- [Lidarr](/docs/catalog/lidarr/) — music collection management
- [Readarr](/docs/catalog/readarr/) — book collection management
- [Prowlarr](/docs/catalog/prowlarr/) — indexer manager for the whole stack
- [Bazarr](/docs/catalog/bazarr/) — automatic subtitle management
- [Tautulli](/docs/catalog/tautulli/) — monitoring and statistics for Plex
## Downloads (5)
- [qBittorrent](/docs/catalog/qbittorrent/) — feature-rich BitTorrent client with a web UI
- [Transmission](/docs/catalog/transmission/) — lightweight, no-fuss torrent client
- [SABnzbd](/docs/catalog/sabnzbd/) — Usenet downloader
- [NZBGet](/docs/catalog/nzbget/) — efficient Usenet binary downloader
- [JDownloader 2](/docs/catalog/jdownloader/) — download manager for file hosts
## Photos (2)
- [Immich](/docs/catalog/immich/) — self-hosted photo and video backup, the open answer to Google Photos
- [PhotoPrism](/docs/catalog/photoprism/) — AI-powered photo organization and search
## Files (3)
- [FileBrowser](/docs/catalog/filebrowser/) — a web file manager for a directory on your server
- [Syncthing](/docs/catalog/syncthing/) — continuous peer-to-peer file synchronization
- [Sami Files](/docs/catalog/sami-files/) — a multi-server file manager for your fleet
## Productivity (9)
- [Nextcloud](/docs/catalog/nextcloud/) — the complete self-hosted productivity and file platform
- [Paperless-ngx](/docs/catalog/paperless-ngx/) — index, search, and archive your documents
- [BookStack](/docs/catalog/bookstack/) — structured wiki and documentation platform
- [Outline](/docs/catalog/outline/) — fast, modern team knowledge base
- [Trilium Notes](/docs/catalog/trilium/) — hierarchical personal knowledge base
- [Standard Notes](/docs/catalog/standardnotes/) — encrypted notes with a long-term focus
- [Mealie](/docs/catalog/mealie/) — recipe manager and meal planner
- [Actual Budget](/docs/catalog/actual-budget/) — local-first personal budgeting
- [Excalidraw](/docs/catalog/excalidraw/) — hand-drawn-style virtual whiteboard
## Development (4)
- [Gitea](/docs/catalog/gitea/) — lightweight self-hosted Git service
- [VS Code Server](/docs/catalog/vscode-server/) — a full VS Code instance in the browser
- [Jenkins](/docs/catalog/jenkins/) — automation server for builds and pipelines
- [Drone CI](/docs/catalog/drone/) — container-native continuous integration
## Management (2)
- [Portainer](/docs/catalog/portainer/) — the well-known Docker container dashboard
- [Watchtower](/docs/catalog/watchtower/) — automatic container image updates
## Monitoring (4)
- [Uptime Kuma](/docs/catalog/uptime-kuma/) — self-hosted uptime and alerting
- [Grafana](/docs/catalog/grafana/) — dashboards and visualization for your metrics
- [Dozzle](/docs/catalog/dozzle/) — live container log viewer
- [Speedtest Tracker](/docs/catalog/speedtest/) — scheduled internet speed monitoring
## Networking (2)
- [Pi-hole](/docs/catalog/pihole/) — network-wide ad and tracker blocking
- [WireGuard VPN](/docs/catalog/wireguard/) — fast, modern VPN tunnels
## DNS (4)
- [Technitium DNS Server](/docs/catalog/technitium/) — authoritative and recursive DNS in one
- [BIND9 DNS Server](/docs/catalog/bind9/) — the classic, battle-tested nameserver
- [PowerDNS](/docs/catalog/powerdns/) — high-performance authoritative DNS
- [CoreDNS](/docs/catalog/coredns/) — plugin-based DNS server
## Communication (4)
- [Rocket.Chat](/docs/catalog/rocketchat/) — self-hosted team chat
- [Matrix Synapse](/docs/catalog/matrix/) — decentralized, federated messaging
- [Roundcube](/docs/catalog/roundcube/) — webmail for your own mail server
- [Docker Mailserver](/docs/catalog/mailserver/) — a full mail stack in one container
## Home Automation (2)
- [Home Assistant](/docs/catalog/homeassistant/) — local-first home automation
- [Node-RED](/docs/catalog/nodered/) — flow-based automation wiring
## Database (4)
- [PostgreSQL](/docs/catalog/postgres/) — the reliable relational workhorse
- [Redis](/docs/catalog/redis/) — in-memory key-value store and cache
- [MongoDB](/docs/catalog/mongodb/) — document database for modern apps
- [Adminer](/docs/catalog/adminer/) — a single-file database admin UI
## Security (4)
- [Vaultwarden](/docs/catalog/vaultwarden/) — a lightweight Bitwarden-compatible password manager
- [Authentik](/docs/catalog/authentik/) — identity provider and single sign-on
- [CrowdSec](/docs/catalog/crowdsec/) — collaborative, behavior-based intrusion prevention
- [DashCA](/docs/catalog/dashca/) — internal certificate authority
## Utilities (8)
- [Homepage](/docs/catalog/homepage/) — a modern dashboard for all your services
- [Homarr](/docs/catalog/homarr/) — a customizable service dashboard
- [IT Tools](/docs/catalog/it-tools/) — a collection of everyday developer utilities
- [Stirling PDF](/docs/catalog/stirling-pdf/) — the web-based PDF toolbox
- [Change Detection](/docs/catalog/changedetection/) — watch any web page for changes
- [Whoami](/docs/catalog/whoami/) — a tiny container that reports request details
- [Digital Clock](/docs/catalog/digital-clock/) — a simple always-on clock display
- [Weather](/docs/catalog/weather/) — a self-hosted weather page
## Gaming (2)
- [Minecraft Server](/docs/catalog/minecraft/) — a managed Minecraft Java server
- [Valheim Server](/docs/catalog/valheim/) — a dedicated Valheim world for you and your friends
## Why a catalog beats five tabs of documentation
Categories matter more than they look. They are what turn "I have some containers" into "I know what my server does." Grouping the same services makes it obvious when something is missing — no monitoring, no backups, no identity layer — and gives you a natural next thing to add.
The catalog is also the reason deployment stays consistent. Every template defines its image, its ports, its volumes, and its health check in one place, so a deploy behaves the same way whether it is the first app on the host or the fortieth. The platform takes care of the DNS record, the reverse-proxy entry, and the certificate in the same pass.
## What to deploy first
If you are starting from nothing, this order tends to work well:
1. **A dashboard** — [Homepage](/docs/catalog/homepage/) or [Homarr](/docs/catalog/homarr/) — so you can see everything at once.
2. **Monitoring** — [Uptime Kuma](/docs/catalog/uptime-kuma/) — so you find out when something breaks.
3. **The thing you actually wanted** — photos, media, or files. Deploy it while the setup is fresh.
4. **A password manager** — [Vaultwarden](/docs/catalog/vaultwarden/) — before you have forty logins to track.
## Getting started
Install DashCaddy on any Linux host with Docker:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
The [installation docs](/docs/installation/) cover requirements, [First Service](/docs/first-service/) walks through your first deploy, and the [catalog index](/docs/catalog/) lists all 77 apps. If you would rather understand the strategy first, start with [what makes a good self-hosting platform](/blog/self-hosting-platform-guide/) or [the best self-hosted apps for a homelab](/blog/best-self-hosted-apps-homelab/).
@@ -0,0 +1,111 @@
---
title: "Self-Hosting Platform: What It Is and How to Choose One in 2026"
description: "A self-hosting platform turns a bare Linux server into a place you can run apps safely. Learn the categories, the questions that actually matter, and how to pick one."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["self-hosting platform", "self hosted platform", "self-hosting software", "docker platform", "homelab"]
readingMinutes: 8
---
If you have ever typed `docker run` on a fresh server, watched it fail because the port was already taken, and then spent an evening editing a reverse-proxy config by hand, you already understand why self-hosting platforms exist.
A **self-hosting platform** is the layer between your operating system and the applications you want to run. It handles the parts that are boring and easy to get wrong — container lifecycle, networking, DNS records, TLS certificates, storage, and updates — so that "install Nextcloud" becomes one decision instead of twenty.
This guide explains what these platforms actually do, how the categories differ, and which questions separate a platform you will still be using in a year from one you abandon in a weekend.
## What a self-hosting platform is (and is not)
Self-hosting is the act of running software on hardware you control instead of paying a SaaS vendor. A **platform** is the tooling that makes that repeatable.
It is not the same thing as:
- **An operating system** — Ubuntu, Debian, or a hypervisor like Proxmox. The platform runs *on top* of these.
- **A hypervisor or VM manager** — that virtualizes machines; a self-hosting platform deploys applications.
- **A single app** — Nextcloud, Jellyfin, and Immich are things you host; the platform is what hosts them.
In practice, most modern self-hosting platforms are built around Docker. Containers give you isolation and reproducibility for free. What Docker does *not* give you is DNS, HTTPS, backups, an app catalog, or a way to see the health of everything at once — which is exactly the gap a platform fills.
## The three rough categories
Most tools you will encounter fall into one of three buckets.
### 1. Container dashboards
Lightweight web UIs over the Docker socket. Portainer and similar tools let you start, stop, inspect, and edit containers.
**Best for:** people who already know Docker and just want a GUI.
**Limit:** they stop at the container. You still wire up reverse proxying, certificates, and DNS yourself.
### 2. App-launcher OS bundles
Opinionated distros that ship a curated set of apps with one-click installs, usually on a fixed host.
**Best for:** a first homelab and a small, predictable app list.
**Limit:** departing from the curated catalog — or adding a second host — often means fighting the abstraction.
### 3. Full self-hosting platforms
These combine a catalog, deployment automation, networking and TLS, security controls, monitoring, and multi-host management in one system. They assume you will outgrow a single box and a single app.
**Best for:** anyone who wants to keep adding services without rebuilding their setup each time.
DashCaddy is built in this third category: an AI-native platform that deploys from a catalog of 77 one-click application templates, provisions the reverse proxy and certificate for you, and keeps the whole fleet on one dashboard. You can see the full app list in the [catalog](/docs/catalog/) and the operating model in the [docs](/docs/).
## The questions that actually matter
Feature checklists are easy to write and rarely decide anything. These five questions do.
### Can it deploy an app without you touching a config file?
The test is whether adding a service is a *decision* or a *task*. If you still have to write a Caddy or Nginx block, create a DNS record, and remember the right port mapping every time, the platform is only saving you the `docker run` line.
With DashCaddy you describe the outcome — "deploy Jellyfin at media.example.com with HTTPS" — and it resolves the container, the DNS record, the reverse-proxy entry, and the certificate. The [AI intent router](/blog/deploy-self-hosted-apps-with-ai/) is the piece that turns plain English into a running container.
### Does HTTPS happen automatically?
A self-hosted app without TLS is a liability the moment it is reachable from outside your LAN. Automatic certificate issuance and renewal is table stakes; a platform that leaves it to you is a platform that will eventually serve an expired certificate.
### Can it grow past one machine?
Almost everyone starts with one server. Almost everyone who sticks with self-hosting adds a second — a storage box, a media box, a machine for the things that must not go down with the lab. Ask whether the platform has a concept of *hosts*, or whether "adding a server" means starting over.
### Does it tell you when something breaks?
The failure mode that hurts is not the crash you notice. It is the service that quietly stops answering on a Saturday and is discovered on Thursday. Health checks, uptime history, and container-level visibility are what turn "I have a homelab" into "I operate a service." See the [homelab monitoring guide](/blog/homelab-monitoring-guide/) for what to actually watch.
### How does it treat security?
A platform that exposes Docker and a reverse proxy on the public internet needs an opinion about access control. Look for authentication, network scoping (the ability to restrict a service to a private network such as Tailscale), and a security view that surfaces the state of the exposed surface rather than hiding it.
## Where DashCaddy fits
If you are comparing options, here is the honest positioning.
DashCaddy is a strong fit when:
- you want to **deploy from a catalog** rather than hand-write Compose files;
- you want **automatic DNS, reverse proxy, and TLS** as part of deployment;
- you expect to run **more than one host**;
- you want an **AI/MCP interface** so assistants and automation can drive deployments;
- you want a **security and monitoring surface** in the same tool you deploy from.
It is a weaker fit when you need fine-grained, per-container inspection with a long history in exactly one niche workflow, or when you are already deeply standardized on another platform's configuration format. Portainer, for instance, remains excellent for raw container inspection — the comparison in [Docker dashboard for self-hosted apps](/blog/docker-dashboard-self-hosted/) goes into that in detail.
## A practical way to choose
1. **Write down the next three apps you actually want to run.** Not the twenty-app wish list — the next three. Then check whether each is supported out of the box.
2. **Check the failure story.** Search the docs for how the platform handles a dead container, a failed certificate, or a disk filling up. Thin answers here predict a painful month three.
3. **Install it once on a throwaway VM.** Not on your main server. Deploy two apps, restart the host, and confirm both come back. That single test rules out most options.
4. **Confirm data lives where you expect.** Containers are disposable; volumes are not. Know where your data is on disk before you trust the platform with it.
## Getting started
DashCaddy installs on any Linux host with Docker in one command:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
That gives you a dashboard with the [app catalog](/docs/catalog/), automatic HTTPS, and a security view. From there you can deploy your first service, then add a second host when you are ready — without changing how you work.
If you are still deciding what to run first, start with the [15 best self-hosted apps](/blog/best-self-hosted-apps-homelab/) or the [beginner's homelab guide](/blog/homelab-beginners-guide/).
+78
View File
@@ -0,0 +1,78 @@
---
title: "What is DashCaddy? The AI-Powered Self-Hosting Platform Explained"
description: "Learn what DashCaddy is—an AI-native platform for deploying, securing, and managing self-hosted applications with plain English commands."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["what is dashcaddy", "self-hosting platform", "AI self-hosting", "docker deployment", "automated ssl"]
readingMinutes: 6
---
If you've ever wanted to self-host your applications but felt overwhelmed by Docker, DNS configuration, SSL certificates, and reverse proxies—DashCaddy is the solution you've been looking for.
## What Makes DashCaddy Different
DashCaddy isn't just another Docker management tool. It's an **AI-native self-hosting platform** that lets you deploy applications using plain English commands.
### Traditional Self-Hosting vs DashCaddy
**Traditional approach (without DashCaddy):**
1. Research which Docker image to use
2. Write a docker-compose.yml file
3. Configure environment variables
4. Set up DNS A and CNAME records
5. Configure reverse proxy (Nginx, Caddy, or Traefik)
6. Generate or obtain SSL certificates
7. Configure SSL certificates in your reverse proxy
8. Test everything
**With DashCaddy:**
1. Type: "Deploy Jellyfin for my media server"
2. Done. DNS, SSL, and reverse proxy are automatic.
## Key Features
### 77 One-Click App Templates
DashCaddy includes pre-configured templates for popular self-hosted applications:
- Media servers: Jellyfin, Plex, Emby
- Communication: Matrix, Element, Mattermost
- Productivity: Home Assistant, Paperless-NGX, Firefly III
- And 70+ more
### AI Intent Router
The AI analyzes your plain English requests, determines which app you want, and configures everything automatically.
### Built-in Security Center
- Automatic SSL via Let's Encrypt
- TOTP two-factor authentication
- Rate limiting
- IP blocking
- Security headers
### Multi-Host Fleet Management
Manage multiple servers from a single dashboard. Deploy apps across your entire homelab or production fleet with one command.
### MCP Server for AI Agents
Machine Chain Protocol (MCP) server allows AI assistants to manage your infrastructure programmatically.
## Who Is DashCaddy For?
- **Homelab enthusiasts** who want easier container management
- **Self-hosters** tired of manual reverse proxy configuration
- **Small businesses** needing simple internal tool deployment
- **Developers** who want infrastructure-as-code without the complexity
## Try It Now
Install DashCaddy on any Linux server:
```bash
curl -fsSL https://get.dashcaddy.net | bash
```
Visit [dashcaddy.net](https://dashcaddy.net) to learn more.
+133
View File
@@ -0,0 +1,133 @@
---
title: "Why Self-Host Your Applications? The Complete Benefits Guide"
description: "Discover why self-hosting your applications saves money, improves privacy, and gives you control. Learn what's possible when you run your own services."
date: "2026-10-07"
author: "DashCaddy Team"
tags: ["self-hosting benefits", "why self-host", "homelab advantages", "privacy", "data ownership"]
readingMinutes: 7
---
The cloud was supposed to simplify everything. And for many years, it did. But as subscription costs rise and privacy concerns grow, more people are asking: why not host our own applications?
## The Rise of Self-Hosting
Self-hosting means running your own applications on your own hardware—whether that's a Raspberry Pi in your closet or a dedicated server in a data center.
## Top Benefits of Self-Hosting
### 1. Cost Savings
Cloud subscriptions add up fast:
- Netflix: $15-23/month
- Google One: $10-50/month
- Dropbox: $12/month
- Notion: $10/month
- Slack: $8-15/user/month
Total: $50-150/month minimum for a small team or family.
**Self-hosting alternative:** One-time server cost + electricity. Break-even in 6-12 months.
### 2. Data Privacy
When you host your own data:
- Your files stay on your hardware
- No third-party access to personal data
- No targeted ads based on your content
- GDPR and compliance headaches disappear
### 3. Complete Control
Want to:
- Keep photos forever without deletion limits?
- Run your own chat server?
- Customize every aspect of your tools?
- Never lose access because you missed a payment?
Self-hosting gives you all this.
### 4. Learning Opportunity
Running your own infrastructure teaches:
- Linux administration
- Networking (DNS, SSL, reverse proxies)
- Containerization with Docker
- Security best practices
- Troubleshooting skills
### 5. No Vendor Lock-In
Cloud providers can:
- Raise prices overnight
- Change features you rely on
- Shut down entirely (RIP Google Reader, Google+, Inbox)
- Lock you into their ecosystem
When you self-host, you own your infrastructure.
### 6. Performance & Reliability
Your homelab can be:
- Faster than shared cloud (local network)
- More reliable (you control updates)
- Always available (no internet needed for local apps)
## What Can You Self-Host?
**Media & Entertainment:**
- Jellyfin/Plex — personal streaming
- Navidrome — music streaming
- Readarr/Booksonic — ebooks/audiobooks
**Productivity:**
- Nextcloud — Google Drive alternative
- Paperless-NGX — document management
- Firefly III — personal finance
**Communication:**
- Matrix/Element — Slack/Discord alternative
- Mailu — email server
- Bitwarden — password manager
**Home Automation:**
- Home Assistant — smart home hub
- AdGuard Home — network-wide ad blocking
**And 77+ more with DashCaddy.**
## Common Concerns
### "It's too hard"
Tools like DashCaddy make it easy. Plain English deployment, automatic SSL, built-in reverse proxy.
### "My data isn't backed up"
You control your backups. Use external drives, rsync to cloud storage, or both.
### "My server might go down"
Build redundancy. Multiple drives, UPS backup, or simply accept that occasionally things need maintenance.
### "I don't have the hardware"
Start small:
- Raspberry Pi 4: $50-100
- Old laptop: free
- Mini PC: $100-200
## Getting Started with Self-Hosting
The easiest path:
1. Get a Linux server (old computer, VPS, or dedicated hardware)
2. Install DashCaddy: `curl -fsSL https://get.dashcaddy.net | bash`
3. Try: "Deploy Nextcloud" or "Set up Jellyfin"
Within minutes, you'll have running applications that you own.
## The Future is Self-Hosted
As cloud costs rise and privacy concerns grow, self-hosting isn't just for geeks anymore. It's for anyone who wants control over their digital life.
Ready to start? Visit [dashcaddy.net](https://dashcaddy.net) to learn more.
@@ -0,0 +1,153 @@
---
title: "Why ShipDeck Replaced Docker at DashCaddy: One Command to Deploy"
description: "ShipDeck is the deployer DashCaddy uses for its own services — a single Go binary that replaces Docker, Docker Compose, and handwritten deploy scripts with one command. Here's what it does, why Docker wasn't the right fit for fleet deployments, and what makes ShipDeck better."
date: "2026-10-08"
author: "DashCaddy Team"
tags: ["shipdeck", "docker alternative", "deployment tool", "fleet deployer", "systemd deployment", "self-hosted deployment"]
readingMinutes: 7
---
When you visit dashcaddy.net, or the DashCaddy dashboard hits its license server, or a monitoring check pings one of our backend services — most of those reached production through ShipDeck. It is the deployer DashCaddy uses for its own fleet services, and it replaced Docker for those deployments.
ShipDeck is a single Go binary. It takes a source repo and a TOML config file, then runs the full pipeline: build → package → SSH transfer → systemd install → Caddy reverse proxy → DNS record → live verification — one command, and the prior release is preserved so rollback is one command away after the first successful deploy.
This is the story of why we built it and why Docker wasn't the right tool for that job.
## What DashCaddy needed from a deployer
DashCaddy runs as a constellation of services across multiple hosts. There's the product dashboard itself, a license server, monitoring agents, and auxiliary tools that talk to each other over Tailscale. Each service needs:
- A build step that produces a binary or static bundle
- A way to land that artifact on the right host
- A systemd unit so it stays running after a reboot
- A Caddy reverse-proxy block with a proper TLS certificate
- A DNS record that resolves in public and over the tailnet
- Health checks that confirm the deploy worked before it is marked done
That is six steps that have to happen in order, and every step can fail. Before ShipDeck, each of these was a hand-run script or a Docker Compose file that covered maybe three of the six.
## Why Docker wasn't the right tool
Docker is excellent at running isolated processes. It solves the "works on my machine" problem by shipping the entire filesystem along with the process. For a homelab running third-party apps — Vaultwarden, Immich, a media server — that isolation is genuinely useful. Those apps ship as Docker images and updating them is a `docker pull` and a restart.
But for your own software, which you already build from source, the container adds overhead that may not be worth the trade-off.
**The Docker daemon is another moving part.** Every Docker deploy requires the daemon to be running, healthy, and configured the same way on every host. It consumes memory even when idle. A systemd unit needs only systemd — which is already running on every Linux host and has been for a decade.
**Docker images can be harder to debug.** When something goes wrong inside a container, the debugging path is `docker exec` into a filesystem that may not have the tools you need. A bare binary under systemd gives you `journalctl`, `coredumpctl`, and your own tooling — no extra layer.
**Docker Compose has no built-in rollback.** If you push a bad image and the container fails to start, Compose leaves you to fix it by hand. ShipDeck's blue-green release directories mean the previous working release is still sitting right there, and `shipdeck rollback <name>` swaps back to it — provided a prior successful deployment is recorded in the journal.
**Docker has no DNS layer.** ShipDeck's DNS step calls an operator-configured hook (`SHIPDECK_DNS_CMD` — in DashCaddy's fleet, this is wired to `fleet-dns`) to register the service record and gates on the resolver confirming it resolves before marking the deploy done. Containers just expose ports; connecting a name to them is always a separate step.
**Docker needs separate tooling for TLS.** ShipDeck writes and validates a Caddy block inline in the same deploy, with atomic swap and automatic rollback if the reload fails. With containers, you wire up Traefik or Caddy separately and hope the labels match.
## What ShipDeck does instead
A ShipDeck deploy runs these steps in order, stopping on the first failure:
1. **Build** — runs the build command you defined in the `Shipdeckfile`, producing a binary
2. **Package** — bundles the binary and any assets into a release directory
3. **Transfer** — copies the release over SSH to the target host under `/opt/<name>/releases/<epoch>/`
4. **Install** — symlinks `current` → the new release, writes (or validates) the systemd unit, and restarts the service
5. **Caddy** — writes a candidate Caddy block, validates it with `caddy validate`, then atomically swaps it in and reloads
6. **DNS** — calls the operator-configured DNS hook (in our fleet, `fleet-dns add`), then gates on the DNS resolver confirming the record resolves
7. **Verify** — probes the HTTP health endpoint over the tailnet (or public URL) and confirms a `200`
If the build, package, or transfer step fails, the active installation is unchanged. If a later step (install, Caddy, DNS, or verify) fails, the deploy stops but the prior release directory is preserved on disk. `shipdeck rollback <name>` swaps `current` back to it and restarts the service — provided a prior successful deployment is recorded in the journal. Caddy's last-good config is automatically restored from the pre-deploy snapshot on validation or reload failure — that part is unattended. Successful deploys append a completion row to `/var/lib/shipdeck/journal.jsonl` recording the sanitized repository URL and exact commit — `status` and `rollback` reference the journal, so neither needs the original checkout.
## Reproducible deploys with commit pinning
One feature that makes ShipDeck fundamentally different from Docker workflows: it pins Git commits.
When you deploy from a Git source, the Shipdeckfile declares an exact 40-character lowercase commit hash. ShipDeck fetches that ref, checks out that exact commit, verifies `HEAD` matches, and builds from that tree. No ambiguity about what code is running in production. No "the image tag says `latest` but nobody remembers which commit built it." This is table stakes for fleet operations; Docker Compose offers no equivalent without a separate registry and tag discipline.
## Blue-green with instant rollback
Every deploy lands in a timestamped release directory:
```
/opt/myservice/releases/20261008T120000Z/
/opt/myservice/releases/20261007T090000Z/ ← previous
/opt/myservice/current → releases/20261008T120000Z
```
The `current` symlink is the atomic switch. If the new release fails verification, `shipdeck rollback myservice` re-points it to the previous directory and restarts — provided a prior successful deployment is recorded in the journal. That previous release is never touched by the deploy, so the rollback is instant — no pulling old images, no rebuilding, no guessing what the last working version was.
## No daemon. No registry. No layered filesystems.
ShipDeck is a CLI tool, not a daemon. You run `shipdeck deploy .` from a checkout; it does the work and exits. There is no persistent shipdeck process to monitor, upgrade, or debug. The journal is a flat JSONL file. The host registry is a TOML file at `/etc/shipdeck/hosts.toml`.
This means ShipDeck can be called from CI, from a Makefile, from a cron job, or from another CLI tool (DashCaddy's Deploys panel calls ShipDeck as its engine). Docker, by contrast, needs the daemon running on every host that builds or runs containers — and if the daemon is down, the pipeline is dead.
## When to keep using Docker
None of this means Docker is bad. Docker is the right choice for:
- Running third-party apps that already ship as Docker images
- Services that need filesystem isolation from the host
- Development environments where containerization is part of the workflow
DashCaddy itself still uses Docker for the apps it deploys — the 77 templates in the catalog all run as Docker containers. ShipDeck replaces Docker for *DashCaddy's own services*: the dashboard, the license server, the monitoring stack. Software we build from source and deploy to our own fleet.
If you self-host apps with DashCaddy, Docker is still your runtime. If you want to deploy your own Go or Node services across your fleet the way DashCaddy does, ShipDeck is the tool.
## Getting started with ShipDeck
ShipDeck ships as a single binary. The target host needs an SSH server and `systemd`; the invoking machine needs the SSH client. Caddy and the DNS hook (`SHIPDECK_DNS_CMD`) are configured per-deployment — ShipDeck works without either for local-mode deploys, but in fleet operation both are typically wired in. Build-time tools (like Go for Go projects) run on the machine that invokes ShipDeck. There is no persistent daemon or agent on either side.
```bash
# Register a fleet host
shipdeck host add edge-1 --address 100.64.1.10 --user root --key ~/.ssh/edge-1
# Scaffold from a template
shipdeck init --template=go
# Fill in the Shipdeckfile:
# [service]
# name = "myservice"
# repo = "https://git.example.com/me/myservice.git"
# ref = "main"
# commit = "0123456789abcdef0123456789abcdef01234567"
# build_cmd = "go build -buildvcs=false -o bin/myservice ./cmd/myservice"
# binary = "bin/myservice"
#
# [deploy]
# host = "edge-1"
# systemd_unit = "myservice.service"
# port = 8901
#
# [caddy]
# block = \"\"\"
# myservice.sami {
# reverse_proxy 127.0.0.1:8901
# }
# \"\"\"
# tailnet_only = true
#
# [dns]
# zone = "sami"
# record = "myservice.sami"
# target = "100.64.1.10"
#
# [verify]
# http = "https://myservice.sami/health"
# timeout = 30
# Set the DNS hook in your environment
export SHIPDECK_DNS_CMD="fleet-dns"
# Dry-run, then deploy
shipdeck plan .
shipdeck deploy .
```
Omitting `[caddy]` and `[dns]` selects local mode — the service runs on the target host without TLS or DNS registration.
After a successful deploy, the service is live with the configured Caddy TLS block and DNS record. `shipdeck status myservice` re-probes everything and reports health. `shipdeck rollback myservice` swaps to a prior successful deployment — provided one is recorded in the journal.
## One command, not six
The thing that made Docker the wrong tool for DashCaddy's fleet wasn't any single missing feature. It was the gap between "I built this binary" and "it is running behind TLS on every host with a DNS record and health checks." Docker closed some of that gap. ShipDeck closes all of it in one command.
More importantly, it closes it in a way that can be rolled back, verified, and replayed — which is what fleet operations actually needs.