I'm a solo student developer. I built Naval PM, a real project-management tool with real signed-up users, on a $200 DigitalOcean credit from the GitHub Student Developer Pack — advertised, like every year before it, as valid for one year from signup.
I created my account on July 4, 2026.
What I didn't know: DigitalOcean had already started winding the whole partnership down. On June 8, they quietly cut what the credit could be spent on. On June 12, they started emailing existing pack users that the program was ending, with every credit — no matter when it was redeemed — expiring August 1, 2026.
I signed up after that email went out, so I never saw it. What I did see was a redemption page that, as far as I could tell, still said "valid for one year." Other students who redeemed in that same window later found their actual billing page showed the real expiration — August 1 — nowhere near what the page had promised. Some only found out by asking support directly.
July 4 to August 1 is less than four weeks.
That's not a 60-90 day courtesy notice. That's not even the year I signed up expecting. That's "the marketing page and the real terms were already two different things by the time I hit redeem."
So I migrated. Fast.
I moved everything to Oracle Cloud's free tier: 1 shared vCPU, 956MB of RAM, a 45GB disk. It's small — genuinely, uncomfortably small for a Node + Postgres app with real users — but it's mine, and nobody can pull it out from under me with three weeks' notice.
I hardened it properly: three layers of firewall (Oracle's own NSG, ufw, and iptables), real Let's Encrypt TLS, and a 2GB swap file tuned so real RAM gets used first and swap is a safety net, not routine behavior. On a box this small, that last part is the difference between "a little slow" and "the OOM killer just ended your Postgres connection mid-request."
I thought that was the hard part. It wasn't.
Then GitHub flagged a "High" severity CVE
Right after finishing the migration, a Dependabot alert showed up:
React Router: RSC Mode CSRF Bypass Allows Action Execution Before 400 Response Severity: High (7.1) · Patched in
react-router@8.3.0
The instinct is to panic-patch. I didn't — I checked first. The CVE only applies to React Router's unstable RSC (React Server Components) mode. My app uses plain, stable BrowserRouter / Routes / Route. Not exploitable, technically. I could've closed the alert and moved on.
I didn't. I decided to actually take the upgrade — which turned out to be a much bigger decision than it sounded, because react-router@8.3.0 requires React ≥19.2.7. One "patch a CVE" ticket became "upgrade React, react-router, and everything downstream of both, on a live app, on a box with less than 1GB of RAM."
What that actually took
- Auditing every library that touches React to see what would actually break:
react-leaflet(v4→v5, checked the one map component that uses it),resend(the email SDK — jumped 3→6 mainly to drop a dependency pinned to React 18, then double-checked the actual send API hadn't changed shape),rechartsandlucide-react(deliberately held back from their newest majors — no reason to stack more risk than the upgrade already required). - Rewriting 29 files' worth of imports, because React Router v8 folded
react-router-domintoreact-routerand stopped publishing it separately. - Catching a genuinely nasty gotcha: the upgrade had been prepared in a separate work session, from a snapshot taken before I'd hardened my production config (a capped database connection pool, Docker memory limits). Merging it in silently reverted all of that hardening — no error, no warning, just a quiet regression sitting in the diff. Had to catch it by explicitly diffing the exact files I knew I'd touched before, not by trusting a clean
git status. - A full verification pass before deploying anything: clean install, clean production build, 348 passing tests across 37 suites, clean security/audit checks in CI — and then, because none of that actually proves a browser renders the page correctly, an honest manual click-through of the live site.
- A ~10-minute container rebuild on a single shared vCPU, because bumping the Docker base image from Node 20 to Node 22 invalidated every cached layer.
It worked. Zero downtime, zero broken pages, and — as a nice side effect — the original CVE alert and its matching Dependabot PR both auto-closed themselves the moment the fix landed on master.
The actual lesson
None of this — the migration, the hardening, the forced three-library upgrade — was optional or was work I chose to sign up for on July 4. It was the cost of building on infrastructure I didn't control, with terms that could (and did) change under me with barely any notice.
If you're a student building something real on free credits: back up everything, assume the free tier is a loan that can be called at any time, and budget real hours for "unplanned infrastructure work" into every project — because eventually, something you didn't choose will force your hand, usually at the worst possible time.
Naval PM is a project management tool I built and run solo. If you've been through a similar forced migration or upgrade, I'd genuinely like to hear how it went for you.
No comments:
Post a Comment