A hands-on guide to deploying a self-hosted AI blog with Docker, Astro, and MCP discovery, complete with working code, real-world gotchas, and monetization via Lightning and Nostr.

Sovereign Blog Setup: Self-Hosted AI Content Pipeline & Monetization

New to self-hosting AI? The Self-Hosted AI: Start Here hub walks the hardware-decision tree, inference-engine choice, and the operational gotchas that bite hardest in the first three months. Read it before or after this one, whichever fits your stage.

Quick Take

  • Skip the cloud middleman and run your AI-powered blog on your own hardware
  • Monetize directly via Lightning Zaps and Nostr without KYC gatekeepers
  • MCP discovery turns your blog into a self-hosted AI agent endpoint

Docker Networking for Self-Hosted AI Stacks

What it does This snippet connects cloudflared and your Astro-based blog into the same Docker network so your self-hosted AI stack can route traffic without exposing ports to the public internet.

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token ${CLOUDFLARED_TOKEN}
    networks:
      - default
      - astro_webshop_default  # connects to your Astro container

networks:
  default: {}
  astro_webshop_default:
    external: true

Why it matters Because Docker isolates containers by default, services in separate networks can’t communicate without explicit configuration. This means your cloudflared tunnel (handling public traffic) and Astro app (serving AI content) must share a network to route requests correctly.

In practice you’ll hit permission errors if /data/secrets/cloudflare_token isn’t readable by the Docker daemon, run sudo chmod 600 on the file before starting the stack.


RSS Feed with Media Enclosures and AI Discovery

What it does This rss.xml.js endpoint generates a W3C-valid RSS 2.0 feed with Media RSS extensions and an OpenAI-compatible AI plugin manifest for MCP discovery.

// src/pages/rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';

export async function GET(context) {
  const posts = await getCollection('blog');
  return rss({
    title: 'Sovereign Blog',
    description: 'AI-powered insights on sovereign tech',
    site: context.site,
    items: posts.map(post => ({
      title: post.data.title,
      pubDate: post.data.pubDate,
      description: post.data.description,
      link: `/blog/${post.slug}/`,
      enclosure: {
        url: post.data.heroImage,
        type: 'image/webp',
        length: post.data.heroImageSize
      }
    }))
  });
}

Why it matters Because feed readers like Feedly and Inoreader rely on Media RSS thumbnails and enclosures, omitting these breaks discovery. The AI plugin manifest (ai-plugin.json) exposes your blog as an MCP endpoint so agents can query your content without scraping.

In practice you’ll see broken thumbnails in FreshRSS if the enclosure type doesn’t match the actual file MIME, use file-type npm package to validate before deployment.


Mobile Layout with Overflow Defenses

What it does This CSS snippet prevents horizontal scrolling and ensures long slugs break cleanly on mobile devices.

/* src/styles/global.css */
html, body {
  overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 {
  overflow-wrap: anywhere;
  word-break: break-word;
  hyphens: auto;
}

:not(pre) > code {
  overflow-wrap: anywhere;
}

Why it matters Because mobile Safari and Chrome handle word-breaking inconsistently, long URLs and code snippets can overflow containers and break layouts. These rules ensure text wraps naturally without manual intervention.

In practice you’ll still see horizontal scroll if a <pre> block contains unbroken strings, use white-space: pre-wrap for code blocks only.


Deploying Without Container Restarts

What it does This rsync command deploys your built Astro site to a remote server, mounted as a read-only volume in Docker to avoid restarts.

cd /data/projects/sovereign-blog
npm run build
rsync -avz --delete dist/ floki:~/sovereign-blog/dist/

Why it matters Because Docker volumes are immutable when mounted read-only, you avoid container crashes from file changes. This means your AI stack stays up while you push updates.

In practice you’ll lose the volume mount if the remote directory permissions don’t match the Docker user, double-check chmod 755 on the target.


Brand Assets for AI-Optimized Sharing

What it does These files power Open Graph and Nostr profile branding with minimal file sizes for fast loading.

public/brand/
├── cipherfox-avatar.webp   # 1024×1024, 19 KB (header logo)
├── cipherfox-avatar-512.jpg # 19 KB (Nostr profile)
├── cipherfox-nostr-banner.jpg # 1500×500, 26 KB (profile banner)
├── og-default.webp         # 8 KB (default OG image)
└── og-default.jpg          # 24 KB (fallback)

Why it matters Because AI agents and social platforms cache images aggressively, small file sizes improve first-load performance. The .webp variant is used for OG tags when available, falling back to .jpg for compatibility.

In practice you’ll see broken OG images in Mastodon if the file isn’t served with the correct Content-Type, set image/webp in your server headers.


What I Actually Use

  • Mistral Small 4: handles content pipeline tasks like image conversion and feed generation without cloud APIs
  • Astro: builds static sites with zero-config SSR for AI-generated content
  • cloudflared: tunnels public traffic to self-hosted services without exposing ports

What we got wrong about monetization in the first version

The original setup leaned heavily on the assumption that Lightning V4V tipping would be the primary monetization signal. After the first month live with the infrastructure ready, the actual signal is zero zaps. That is not a Lightning-stack problem; the plumbing works (test zaps from a fresh Alby account register correctly through the address). It is a distribution-and-audience problem that no monetization architecture can solve from the supply side.

The pivot in the working backlog: V4V infrastructure stays, no extra effort spent optimizing it, distribution effort gets the time. The three affiliate links (Alby, BitBox , FlokiNET ) handle the lower bar of “passive monetization that does not require reader action” and are honest about what they are. The blog’s commercial floor is “covers its own hosting cost roughly” rather than “generates revenue”, and that framing is more honest in the README than the original “V4V-first” framing was.

What the next refactor target is

The single highest-leverage technical change that has not yet been made is the post-deploy MCP-restart hook. Today: deploy.sh rsyncs the new knowledge-base.json to the MCP container but the container needs a manual or dashboard restart to pick it up. Closing that loop means the MCP corpus is always within one deploy of the published-blog corpus, and the lag-window where readers see new articles but agents do not goes from “until someone restarts the MCP” to “the duration of a docker restart”. Tracked in the open-issues bucket on /about; not yet implemented because the manual path works and is unambiguous.

The other piece of the second refactor pass is collapsing the deploy.sh + run_image_pipeline.sh + nightly cron into a single coherent state machine instead of three scripts that mostly-but-not-quite agree on what state the system is in. The state-machine refactor would make it possible to ask “what is the system doing right now” and get a precise answer (drafting, reviewing, building, deploying, indexing, idle) rather than the current pattern of “check this log, check that log, infer the state from timestamps”. Tracked but not yet prioritized; the existing scripts work, the orchestration tax is small enough that it has not paid off to fix yet.

Worth naming the friction that is the most visible and the smallest fix: the dashboard’s “restart MCP” button works but is not styled like a confirmation-required action, so accidental clicks do happen. A confirmation modal is the kind of one-line fix that should ship the next time someone is in the dashboard code anyway, not as its own dedicated work.

The bigger lesson from the first months live: the Astro static-site model removed an entire class of problems (no DB, no session state, no SSR errors, no runtime JavaScript-framework upgrades) that would have eaten time forever on a hosted blog. Static was the right tradeoff for this scale and content type; it would not be the right choice for a forum or a per-user-customized site, but for a publishing surface where content is the product, static is essentially free.

Stack

Self-Hosted AI Blog

Technical architecture for sovereign content pipeline

6
AI Pipeline MCP endpoint
5
Astro App Blog frontend
4
Cloudflared Secure tunnel proxy
3
Docker Isolated containers
2
OS Linux container host
1
Hardware Self-hosted server

Was this worth it? Zap the article.

Value for value, no signup. Sats go straight to the writer.