DEV Community

Riley Zhang
Riley Zhang

Posted on

Weekend Build Log: A $0 Status Page, a Hard Scope Cut, and What I Skipped

Friday, 9 PM. I had an idea and 48 hours. Budget: zero dollars.

I wanted to prove something. A side project can go from idea to live URL without spending money. Not a tutorial. A real demo. Something I would actually use.

There is a DEV discussion that keeps circling: "What do you do while AI codes?" My answer after this weekend: you cut scope. The model writes code fast. Your job is deciding what not to build.

The idea

I have a few small services that I check by hand. When one goes down, I notice hours later. I wanted a public status page. It should ping a URL every minute and show latency history.

Full vision: multi-user, alerting, webhooks, charts, Docker, a database.
Shipped version: one Python file, SQLite, a background thread, a plain HTML table.

The scope-cut table

Feature Full vision Shipped Why cut
Auth Multi-user login None Nobody else needs to log in
Alerts Email + Slack None A demo is not on-call
Charts Chart.js dashboard Plain HTML table One endpoint, one page
Database Postgres SQLite file One process, one user
Deploy Docker + compose python app.py Fewer moving parts

Every cut made the demo more likely to finish. That was the point.

The stack

MonkeyCode is an open-source project. Its free tier currently includes a 10M token allowance and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Free tiers change. Check the project README before you depend on the numbers.

I used the free model access to generate the first draft. I hosted the demo on the free server. I did not use the model to design anything. I used it for the boring parts: request handling, the polling loop, HTML escaping.

Step 1 — Scaffold

mkdir pingpage && cd pingpage
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn
Enter fullscreen mode Exit fullscreen mode

Step 2 — The prompt

I gave the model a tight spec. One paragraph. Explicit constraints:

Single-file Python app. FastAPI. Background thread polls a target URL every 60 seconds. Stores status and latency in SQLite. Serves a minimal HTML status page. No auth. No external chart library.

Short prompts work better when the constraints are hard rules. I listed what to exclude, not just what to include.

Step 3 — Review the generated code

The first draft ran. It also had a real bug. The model created one global SQLite connection at import time. The polling thread used it. SQLite objects are thread-bound, so the poller crashed on the first write.

The fix: create a connection inside the loop.

TARGET = "https://example.com"
INTERVAL = 60
DB = "pingpage.db"

def poll():
    while True:
        start = time.time()
        try:
            with urllib.request.urlopen(TARGET, timeout=10):
                status, latency = "up", round(time.time() - start, 3)
        except Exception:
            status, latency = "down", None
        conn = sqlite3.connect(DB)
        conn.execute(
            "INSERT INTO checks (ts, status, latency) VALUES (?, ?, ?)",
            (datetime.now(timezone.utc).isoformat(), status, latency),
        )
        conn.commit()
        conn.close()
        time.sleep(INTERVAL)
Enter fullscreen mode Exit fullscreen mode

The model wrote the structure. I found the thread bug. That split is normal. Treat generated code as a first draft, not a merge-ready PR.

Step 4 — The missing pieces

The rest is standard FastAPI. One init function, one startup hook, one route.

def init_db():
    conn = sqlite3.connect(DB)
    conn.execute("CREATE TABLE IF NOT EXISTS checks (ts TEXT, status TEXT, latency REAL)")
    conn.commit()
    conn.close()

@app.on_event("startup")
def startup():
    init_db()
    Thread(target=poll, daemon=True).start()

@app.get("/", response_class=HTMLResponse)
def index():
    conn = sqlite3.connect(DB)
    rows = conn.execute(
        "SELECT ts, status, latency FROM checks ORDER BY ts DESC LIMIT 50"
    ).fetchall()
    conn.close()
    items = "".join(
        f"<tr><td>{ts}</td><td>{status}</td><td>{latency or '-'}</td></tr>"
        for ts, status, latency in rows
    )
    return f"""<!doctype html><html><head><meta charset='utf-8'>
<title>pingpage</title></head><body>
<h1>pingpage</h1><table border='1'>
<tr><th>time</th><th>status</th><th>latency</th></tr>{items}</table>
</body></html>"""
Enter fullscreen mode Exit fullscreen mode

I skipped a chart library. A table is fine for 50 rows.

Step 5 — Run it locally

uvicorn app:app --port 8000
curl -s http://localhost:8000/ | head -20
Enter fullscreen mode Exit fullscreen mode

Wait 60 seconds. Then check the database.

sqlite3 pingpage.db "SELECT COUNT(*) FROM checks;"
Enter fullscreen mode Exit fullscreen mode

One row per poll. If the count grows, the loop works.

Step 6 — Deploy to the free server

The free server runs the same process. No Docker, no build step.

git clone <your-repo-url> pingpage
cd pingpage
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn
nohup uvicorn app:app --host 0.0.0.0 --port 8000 &
curl -s http://<your-server-address>:8000/
Enter fullscreen mode Exit fullscreen mode

That was the whole deploy. One command to start, one to verify.

Step 7 — What I skipped

  • Docker. One Python file does not need it.
  • A frontend framework. The page is 30 lines of HTML.
  • A test suite. I ran curl, checked the row count, restarted the process.
  • Alerting. The page is passive. I look at it when I remember.

Skipping is a feature. Every skipped item was time I did not spend debugging.

Limitations

The polling thread dies if the process restarts. SQLite on a free server may live on ephemeral storage, so history can disappear. One region, one process, no alerting. This is a demo, not an SLA.

Free tiers change. The 10M token allowance and the free server are not permanent promises. Verify the current terms before you build anything important on them.

Who should not use this

Teams that need real uptime monitoring. If an outage costs you money, use a paid service with alerting, multiple regions, and a guarantee.

This approach is for learning, demos, and small personal tools. It worked for that.

The takeaway

The build took one evening. Most of that time was scope cutting, not coding. The model wrote the code. I decided what not to build.

If you want to try the same flow, MonkeyCode's free tier is a low-risk place to start. Point the poller at your own URL and see what breaks. Then cut something from your own plan before you write a line of code.

Top comments (0)