Automated HTML Reports — Deploy to a Stable URL on a Schedule
Deploy cron-driven HTML reports to a permanent URL. Replace Notion, Google Sheets, or Tableau with a simple pipeline and a link that's always current.
The pattern: your data pipeline generates an HTML report (charts, tables, narrative). Deploy it to a stable VibeHost URL on a schedule. Stakeholders bookmark once; the URL always reflects the latest data.
In practice a growth team might run a fleet of date-stamped report apps (report-20260521-k7qz, report-20260520-a7k2, ...) alongside a couple of rolling apps (growth-daily, kpi-2026).
Two patterns
| Pattern | URL | Use when |
|---|---|---|
| Rolling — same URL, latest data | https://growth-daily.vibehost.space | Stakeholders want a bookmark that's always current |
| Date-stamped — new app each day | https://gr-report-20260521.vibehost.space | You need historical snapshots and want each URL to be permanent |
Rolling is simpler; date-stamped gives you a permanent "this was the state on day X" archive without relying on rollback.
Rolling pattern
Your pipeline produces report-output/index.html (plus assets) every morning; deploy that directory:
vibehost deploy ./report-output --app growth-dailyThat's the whole thing. The bookmark https://growth-daily.vibehost.space always shows the latest deploy.
Wrap it in your cron / scheduler:
#!/usr/bin/env bash
# /opt/reports/daily-deploy.sh
set -euo pipefail
cd /opt/reports
export VIBEHOST_TOKEN="$(cat /run/secrets/vibehost_pat)"
# 1. Run the pipeline
python3 generate.py --date "$(date +%Y-%m-%d)" --out ./report-output
# 2. Deploy
vibehost deploy ./report-output --app growth-daily --json > /var/log/deploys/$(date +%F).json
# 3. Smoke check
URL="https://growth-daily.vibehost.space"
curl -fsS -o /dev/null "$URL"
echo "[$(date -u +%FT%TZ)] deployed: $URL" >> /var/log/deploys/history.logsystemd timer:
[Unit]
Description=Daily growth report deploy
[Timer]
OnCalendar=*-*-* 07:00:00
Persistent=true
[Install]
WantedBy=timers.target[Unit]
Description=Daily growth report
[Service]
Type=oneshot
ExecStart=/opt/reports/daily-deploy.sh
User=reports
StandardOutput=journal
StandardError=journalDate-stamped pattern
DATE=$(date +%Y%m%d)
APP="gr-report-$DATE"
vibehost app create "$APP"
vibehost deploy ./report-output --app "$APP"
vibehost app visibility "$APP" workspaceEach day = new app. URLs stay live indefinitely; you can reference "the report on May 21, 2026" forever by its URL.
Trade-offs:
- ✓ Each historical snapshot has its own permanent URL.
- ✓ Easy to A/B compare "today vs yesterday" by opening two tabs.
- ✗ Apps accumulate. Free tier caps at 100 apps per workspace; you'll want to
vibehost app deleteold ones periodically (or move to the rolling pattern + useimmutableUrlfor archives). - ✗ The bookmark changes daily — stakeholders need a meta-page that lists "today's report" pointing at the latest.
Hybrid pattern (recommended)
Best of both — deploy to a rolling app for the always-current bookmark, capture the date-stamped immutable URL for archives, and log it to your wiki / sheet:
DEPLOY_RESP=$(vibehost deploy ./report-output --app growth-daily --json)
DEPLOY_ID=$(echo "$DEPLOY_RESP" | jq -r '.data.id')
IMMUTABLE_URL="https://${DEPLOY_ID}.vibehost.space"
echo "$(date -u +%F): $IMMUTABLE_URL" >> /opt/reports/archive-urls.tsvStakeholders bookmark growth-daily.vibehost.space. Anyone who wants to point at "the May 21 report specifically" links the immutable URL from your archive sheet. No per-day app proliferation.
CI variant
name: Daily report
on:
schedule:
- cron: '0 7 * * *' # 07:00 UTC daily
workflow_dispatch:
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with: { python-version: '3.12' }
- run: pip install -r requirements.txt
- run: python generate.py --out ./report-output
- run: curl -fsSL https://vibehost.com/install.sh | sh
- env: { VIBEHOST_TOKEN: ${{ secrets.VIBEHOST_TOKEN }} }
run: ~/.vibehost/bin/vibehost deploy ./report-output --app growth-dailyPros over a self-hosted cron: no server to maintain; logs in GitHub Actions; can re-run any day on demand.
Cons: GitHub-hosted runners have data egress, so if your pipeline pulls 10GB of raw data daily, it's faster to run on a beefier self-hosted box.
Access control
Reports usually shouldn't be public. Make it workspace-only (everyone signed into your workspace can view), or invite specific stakeholders by email — including people outside your workspace, who sign in with Google:
vibehost app visibility growth-daily workspace
vibehost app grants add-email vp@acme.com viewer --app growth-daily
vibehost app grants add-email partner@vc.com viewer --app growth-dailyFor external review without sign-up, use a share link.
Why not Notion / Sheets / a BI tool
| Sheets | Notion | Tableau | VibeHost report | |
|---|---|---|---|---|
| Interactive charts | Limited | No | ✓ | Any JS lib |
| Mobile layout | Broken | Passable | Slow | Your HTML |
| Load time | Slow at scale | Slow at scale | Slow | Static HTML — instant |
| Branding | Notion | Tableau | Yours | |
| Auth model | Google sharing | Notion sharing | License-based | VibeHost grants |
| Embed in email | ✗ | Limited | ✗ | iframe-friendly |
| Cost | $$ | $$ | $$$ | $ |
If your data team already has a BI tool, that's probably the right answer. VibeHost reports are best when you want full control over the visual (specific chart library, custom layout, brand polish) and low overhead (no server, no DB connection to manage).
See also
- Static sites — runtime details
- CI/CD with GitHub Actions — workflow patterns
- Grants and visibility — access model