DEV Community

Cover image for Self-Hosting prompts.chat: A Privacy-First Prompt Library Behind Traefik
Clara Dupont
Clara Dupont

Posted on

Self-Hosting prompts.chat: A Privacy-First Prompt Library Behind Traefik

Why prompts.chat Belongs in Your Homelab

When testing f/prompts.chat — the open-source prompt library formerly known as Awesome ChatGPT Prompts — I needed a self-hosted instance that wouldn't leak internal prompts to third-party SaaS. The project ships as a static site, but real production deployments need:

  • Reverse proxy with automatic HTTPS
  • Zero-trust network access (no public exposure)
  • Persistent storage for custom prompts
  • Local authentication layer

Here's the battle-tested stack I landed on after two failed attempts with bare Nginx and accidental public exposure.

Architecture: Traefik + Tailscale + Docker

# docker-compose.yml
version: '3.8'

services:
  prompts-chat:
    image: ghcr.io/f/prompts.chat:latest
    container_name: prompts-chat
    restart: unless-stopped
    volumes:
      - ./data:/app/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.prompts.rule=Host(`prompts.homelab.local`)"
      - "traefik.http.routers.prompts.entrypoints=websecure"
      - "traefik.http.routers.prompts.tls=true"
      - "traefik.http.services.prompts.loadbalancer.server.port=3000"
    networks:
      - homelab

  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.tailscale.acme.tlschallenge=true"
    ports:
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik-certs:/letsencrypt
    networks:
      - homelab

networks:
  homelab:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Zero-Trust Routing with Tailscale ACL

Instead of exposing port 443 to the internet, bind Traefik to your Tailscale interface:

// tailscale-acl.json
{
  "acls": [
    {
      "action": "accept",
      "src": ["group:engineering"],
      "dst": ["tag:homelab:443"]
    }
  ],
  "tagOwners": {
    "tag:homelab": ["admin@yourorg.com"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Apply the policy:

tailscale up --advertise-tags=tag:homelab
sudo ufw allow in on tailscale0 to any port 443
sudo ufw deny 443  # block public interface
Enter fullscreen mode Exit fullscreen mode

Deployment & Verification

# Start stack
docker-compose up -d

# Verify Traefik routes
docker logs traefik | grep prompts

# Test internal access
curl -k https://prompts.homelab.local

# Confirm external block
curl -I https://<public-ip>:443  # should timeout
Enter fullscreen mode Exit fullscreen mode

War Story: SQLite Corruption Under Load

During initial testing with three concurrent users editing prompts, the app crashed with:

SQLite database is locked (5)
Enter fullscreen mode Exit fullscreen mode

Root cause: prompts.chat uses a single SQLite file without WAL mode. Fix:

# Enable Write-Ahead Logging
sqlite3 data/prompts.db "PRAGMA journal_mode=WAL;"

# Verify
sqlite3 data/prompts.db "PRAGMA journal_mode;"
# Should return: wal
Enter fullscreen mode Exit fullscreen mode

After enabling WAL, sustained 10+ concurrent write operations with zero locks.

Accidental Public Exposure (and the Fix)

First deployment mistake: I exposed Traefik dashboard on :8080 without authentication. Shodan indexed it within 4 hours.

The hard lesson: every listening port needs explicit access control.

Fixed config:

# Traefik dynamic config (traefik-config.yml)
http:
  middlewares:
    basic-auth:
      basicAuth:
        users:
          - "admin:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"  # htpasswd generated

  routers:
    traefik-dashboard:
      rule: "Host(`traefik.homelab.local`)"
      service: api@internal
      middlewares:
        - basic-auth
      entryPoints:
        - websecure
Enter fullscreen mode Exit fullscreen mode

Mount this config:

volumes:
  - ./traefik-config.yml:/etc/traefik/dynamic/config.yml:ro
Enter fullscreen mode Exit fullscreen mode

Performance Notes

  • Cold start: ~2.3s (Node.js SSR)
  • Warm response: <80ms (cached static assets)
  • Memory footprint: 140MB (prompts.chat) + 95MB (Traefik)
  • Disk I/O: negligible after WAL mode

When NOT to Self-Host This

Skip this setup if:

  1. Your team is distributed without VPN — the Tailscale ACL model assumes a private network backbone
  2. You need multi-region replication — SQLite won't scale horizontally
  3. Compliance requires audit trails — you'll need to add event logging middleware

Next Steps

For production-grade deployments, add:

  • Automated SQLite backups (litestream to S3)
  • Prometheus metrics export from Traefik
  • Fail2ban rules for repeated auth failures
  • Health check endpoint monitoring

The complete stack runs on a $5/month VPS with 1GB RAM. Total setup time: 18 minutes after sorting out the SQLite locking issue.

No affiliate links. No sponsored tools. Just a working reference architecture for keeping your prompt library private.

Top comments (0)