Daily reports
Cron-driven HTML reports. Same URL every day. 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.
Real-world: Dcard's growth team runs ~19+ date-stamped report apps (gr-report-20260521-k7qz, gr-report-20260520-a7k2, ...) plus rolling apps (growth-daily, dcard-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
# Pipeline produces report-output/index.html (+ assets/) every morning
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:
# Rolling app — the always-current bookmark
vibehost deploy ./report-output --app growth-daily
# Date-stamped immutable URL for archives
DEPLOY_ID=$(vibehost deploy ./report-output --app growth-daily --json | jq -r '.data.id')
IMMUTABLE_URL="https://${DEPLOY_ID}.vibehost.space"
# Log the immutable URL in your wiki / sheet
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@v4
- uses: actions/setup-python@v5
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.
# Workspace-only (everyone signed into your workspace can view)
vibehost app visibility growth-daily workspace
# Or invite specific stakeholders
vibehost app grants add-email vp@acme.com viewer --app growth-daily
# Or stakeholders outside your workspace, via Google sign-in
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