In this guide
A project that only runs on your laptop is a private achievement; the same project on a public URL is a demonstrable one. Examiners open links, recruiters click portfolios, and "it's running at this URL" beats "it works on my machine" in every evaluation that matters. The good news: a student project can be hosted for genuinely zero rupees. The catch: every free tier has limits — sleep timeouts, bandwidth caps, build minutes — and choosing without reading them is how demos die the night before submission. This guide compares the platforms on their actual constraints, then walks through two complete deployments step by step.
Short answer: which host for which project
| Project shape | Put it on | Why |
|---|---|---|
| Static site (HTML/CSS/JS, or a static export) | GitHub Pages or Cloudflare Pages | Free, fast CDN, custom domains, no server to sleep |
| Node/Express API + static frontend | Render or Railway (backend) + static host (frontend) | Free tiers run persistent Node processes with auto-deploy from GitHub |
| Django / Laravel full-stack app | Render or Railway | Needs a real server process; PHP/Python don't fit serverless-first free tiers well |
| Needs a database | MongoDB Atlas M0 (512 MB free) or a free-tier Postgres | Separate free database; never bundle the DB on the same free web dyno for anything you care about |
If you remember one sentence: static goes on static hosts, servers go on Render/Railway, data goes on a free managed database — and each layer's limits are checked before you build on it.
The platforms, compared on real limits
Free-tier limits change — platforms revise them every year or two — so treat the figures below as the shape of each deal at the time of writing and re-check the pricing page before you commit your architecture to them:
| Platform | What it hosts | Key free-tier limits (verify current) | Sleeps? | Custom domain + HTTPS |
|---|---|---|---|---|
| GitHub Pages | Static sites only | ~1 GB site, ~100 GB bandwidth/month, 10 builds/hour | No (static CDN) | Yes, HTTPS automatic |
| Cloudflare Pages | Static sites + light functions | Generous bandwidth, ~500 builds/month | No (static CDN) | Yes, HTTPS automatic |
| Netlify | Static sites + serverless functions | ~100 GB bandwidth, ~300 build minutes/month | No for static | Yes, HTTPS automatic |
| Vercel (Hobby) | Frontend frameworks, serverless functions | ~100 GB bandwidth; serverless execution limits | Functions are serverless (cold starts) | Yes, HTTPS automatic |
| Render (free) | Web services, static sites, cron jobs | ~750 service hours/month; spins down after ~15 min idle | Yes — first request after idle takes ~30–60 s | Yes, HTTPS automatic |
| Railway (trial) | Services + databases | Trial credit allowance (historically a few dollars/month); usage-metered | Depends on plan activity | Yes, HTTPS automatic |
Three patterns to notice. Static hosts don't sleep — a static export on GitHub Pages answers instantly at 3 a.m. because there is no server to wake. Free server tiers sleep — Render's free web service spins down after ~15 minutes without traffic, so the first visitor waits through a cold start. And bandwidth/build caps are the quiet killers: a demo video embedded on your homepage can eat a monthly bandwidth allowance if the whole class opens it at once — host heavy assets thoughtfully.
Honesty rule for your report: write the platform, the tier, and its limits into your documentation ("hosted on Render free tier; service sleeps after 15 min idle; database on MongoDB Atlas M0, 512 MB"). An examiner who asks "what happens when 50 people open it together" should get an answer grounded in these numbers, not optimism.
Walkthrough A: static site on GitHub Pages
For a markdown blog engine with static-site export, a portfolio, or any frontend-only project, this is the path of least resistance — and it never sleeps.
- Push the site to a GitHub repository. The built files (the
dist/ordocs/output, not just sources) must be in the repo, or built by an Action. - Enable Pages: repository Settings → Pages → deploy from a branch (e.g.
gh-pages) or via GitHub Actions. The Actions route rebuilds the site on every push — set it once and forget it. - Wait for the build, then open
https://<username>.github.io/<repo>/. First deploy takes a few minutes. - Custom domain (optional): add a
CNAMEfile with your domain, point your domain's DNS at GitHub Pages, and tick "Enforce HTTPS". Student tip: a clean subdomain likeproject.yourname.devlooks far more professional in a report than a rawgithub.ioURL.
In Actions-based setups, the workflow checks out the code, runs the build (npm run build or equivalent), and publishes the output directory. Secrets the build needs go in the repository's Actions secrets — never in the workflow file or the repo. Keep the workflow minimal: checkout, setup runtime, build, deploy. If the build works locally but fails in Actions, the cause is almost always a Node version mismatch — pin the version in the workflow to match your machine.
Walkthrough B: Node + Express API on Render, with a database
For a URL shortener with click analytics or a personal expense tracker — a real backend plus a database — follow this path. It is the most common student full-stack deploy, and every step below maps to a failure in the diagnosis table later.
1. Prepare the app for a platform. Three non-negotiable changes:
- Bind to the platform's port. Hardcoding
app.listen(3000)is the #1 deploy failure. Use the environment's port with a local fallback:const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log('listening on ' + PORT)); - Externalise all secrets. Database URI, JWT secret, API keys — everything comes from environment variables. Add
.envto.gitignoreand verify withgit statusthat it is untracked. (The git and GitHub guide covers the version-control hygiene this depends on.) - Declare the start command. A
package.jsonwith"start": "node server.js"and a committedpackage-lock.jsonso the platform installs exactly your dependency tree.
2. Create the free database first. On MongoDB Atlas: create the free M0 cluster (512 MB), create a database user, and — this is the step everyone misses — set Network Access to allow connections from anywhere (0.0.0.0/0), because Render's outbound IPs are not fixed on the free tier. Copy the connection string; it goes into an environment variable, never into code.
3. Create the Render web service. New → Web Service → connect the GitHub repo. Set the build command (npm install) and start command (npm start). Add environment variables: MONGODB_URI, JWT_SECRET, NODE_ENV=production. Deploy.
4. Verify like a sceptic. curl the health endpoint, then the real ones:
curl https://your-api.onrender.com/api/health
curl -X POST https://your-api.onrender.com/api/auth/login -H "Content-Type: application/json" -d '{"email":"aarav@example.com","password":"correct-horse-battery"}'
Then open the frontend and click through the actual user journey — login, create, list, logout — against the deployed backend, not localhost. "It worked locally" is not a deploy verification.
5. Expect the cold start. After ~15 minutes idle, the first request wakes the service (~30–60 s). Design for it: a /health endpoint, a frontend loading state that says what is happening, and a demo habit of opening the URL a minute before you present. Some teams add automated keep-alive pings; note that several platforms discourage artificial traffic to dodge sleep — the durable fix is designing for cold starts, not fighting the tier's terms.
The database question, settled
| Need | Free option | Notes |
|---|---|---|
| Document data (flexible schema) | MongoDB Atlas M0 — 512 MB | Allowlist 0.0.0.0/0 for PaaS backends; connection string in env vars |
| Relational data | Free-tier Postgres (Neon/Supabase/Render) | Small storage caps; fine for project scale; backups limited — export before demo day |
| Cache / sessions | In-memory in the app | Good enough at project scale; note the limit in your report |
| File uploads (images, PDFs) | Object storage free tier or the platform's persistent disk | Never store uploads on ephemeral container filesystems — they vanish on redeploy |
Two warnings from experience: free database tiers cap concurrent connections (often single digits) — a backend that opens a new connection per request will exhaust them under class-demo load, so use a single shared connection pool. And back up before demo day: export the database the night before and keep the file. A free tier owes you nothing, and "the platform had an incident" is not a recovery plan.
Custom domains, DNS and HTTPS
A custom domain is cheap polish with real evaluation value. The mechanics, whichever platform:
- Buy (or reuse) the domain; student programs and cheap TLDs keep this near zero cost.
- For static hosts: add the domain in the platform dashboard, create the DNS
CNAME(orA) records it shows you, wait for DNS propagation (minutes to hours), enable HTTPS — the platform provisions the certificate automatically. - For Render/Railway: same flow from their custom-domain settings; the API typically lives on
api.yourdomain.comwhile the frontend takes the apex orwww.
Verify HTTPS actually serves (open the https:// URL directly) and that the API's CORS settings allow the frontend's new domain — changing domains without updating CORS is a classic post-launch 401/CORS outage.
The sleep problem, quantified
Cold starts deserve numbers, because "it's a bit slow sometimes" is not an engineering statement:
| Situation | Typical latency |
|---|---|
| Static site on GitHub Pages / Cloudflare Pages | < 1 s, any time of day |
| Warm Render free service | 0.2–1 s |
| Cold Render free service (after ~15 min idle) | 30–60 s for the first request, then warm |
| Serverless function cold start (Vercel/Netlify) | 1–5 s typically |
Design consequences: health checks and loading states are not polish, they are the UX of free hosting. For the demo, warm the backend before the audience arrives. For the report, state the numbers — "p95 latency under warm conditions, cold-start behavior documented" is the kind of line that signals you measured instead of hoped.
One platform or split: where the frontend lives
Two deployment shapes, both legitimate:
- Split (recommended for teams): frontend on a static host (GitHub Pages/Cloudflare Pages), backend API on Render/Railway. Each deploys independently from its own repo or monorepo folder; a frontend bug never takes the API down. The cost is CORS configuration and two URLs to manage — both one-time setup.
- Unified: the backend serves the frontend's static files (Express
staticmiddleware, Django/WhiteNoise, Laravel'spublic/). One service, one URL, no CORS at all. Simpler to reason about and to demo — but every frontend typo redeploys the backend, and the free service still sleeps.
The deciding question is team workflow: if two members work the frontend while two work the API, split deploys stop you stepping on each other. Either way, the frontend must read the API's base URL from its own environment config (a build-time variable), never a hardcoded localhost:3000 — search the codebase for localhost before every deploy; it is the most common string in broken production frontends.
Django and Laravel on Render: the differences from Node
The walkthrough above used Node, but the principles transfer with stack-specific details:
- Django: the start command runs a production server, not the dev server —
gunicorn myproject.wsgi:application(addgunicornto requirements). Static files need WhiteNoise or the platform's static handling; never serve them withrunserverin production. Run migrations as a deploy step (python manage.py migrate), and setALLOWED_HOSTSto the platform's domain — the debug 400 "DisallowedHost" page is the Django rite of passage. - Laravel: needs the right PHP version declared (the platform defaults may lag),
composer install --no-devat build time,php artisan migrate --forceon deploy, andAPP_KEYset in env vars. Storage links (php artisan storage:link) must survive deploys — verify uploaded files after a fresh deploy, not just after the first one.
Both need a persistent process (which is why they sit on Render/Railway rather than serverless free tiers) and both sleep on free plans — the cold-start design advice applies unchanged.
Logs and monitoring: the "it broke overnight" drill
Free tiers keep logs briefly and then they are gone — so build the habit of looking:
- Read the deploy logs first, the runtime logs second. Most failures are build failures (wrong Node version, missing env var at build time) wearing a runtime costume. The platform dashboard shows both; check in that order.
- Add a lightweight uptime check. A free external monitor pinging
/healthevery few minutes tells you the service is down before your examiner does. (This is monitoring, not a sleep-dodging hack — keep the interval honest and modest.) - Log the request, not the secret. When debugging auth failures in production, log that verification failed and why the category was — never log tokens, passwords, or full connection strings. Production logs have a way of being screenshared in reviews.
- Know the retention window. If the platform keeps 7 days of logs, your "what happened last month" investigation happens in your own notes, not the dashboard. For the project report, a one-page "incidents and fixes" log compiled from these notes reads as genuine operational experience — because it is.
The last 72 hours: a launch timeline
- T-72h: freeze features. Only deploy fixes from here. Export the database; verify the backup restores somewhere.
- T-48h: full end-to-end pass on the live URL from a fresh browser (no cached sessions): register, login, core flows, payments in test mode, logout. Fix, redeploy, re-verify.
- T-24h: custom domain and HTTPS verified; cold-start behavior measured and noted; demo script warmed — open every URL in the presentation order once, so caches and services are warm.
- T-2h: final database export; confirm the report's URLs, credentials for the demo account, and the "if the internet dies" fallback (a local run or recorded walkthrough — hope is not a plan, but a backup plan is).
- T-0: warm the backend URL one minute before presenting. Breathe. Demo the working system.
Diagnosis table: the failures you will actually meet
| Symptom | Likely cause | Fix |
|---|---|---|
Application failed to respond / deploy crash-loops |
App hardcoded port 3000; platform assigns its own via PORT |
Bind process.env.PORT with a local fallback |
| Build succeeds, app crashes on boot | Missing environment variable (DB URI, secret) | Compare the platform's env dashboard against your local .env keys |
| Works locally, 500s in production | NODE_ENV=production hiding dev-only behavior, or a missing dependency in dependencies vs devDependencies |
Move runtime deps correctly; reproduce with NODE_ENV=production locally |
| MongoDB connection timeout | IP allowlist blocking the platform's outbound IPs | Set Atlas Network Access to 0.0.0.0/0 (with a strong DB password) |
MongoServerError: bad auth |
Special characters in the DB password breaking the URI | URL-encode the password in the connection string |
| Frontend 404 on page refresh | SPA routing: the static host serves only exact file paths | Add the platform's SPA fallback rewrite (e.g. /* → /index.html, status 200) |
| CORS errors after adding a custom domain | Backend CORS still allows only the old onrender.com origin |
Update allowed origins to the new frontend domain |
| First demo click hangs ~45 s | Cold start after idle | Warm the URL before presenting; add loading states |
| Uploaded files vanish after redeploy | Stored on the container's ephemeral filesystem | Move uploads to object storage or a persistent disk |
When to leave the free tier
Free is the right start, not necessarily the right forever. Upgrade triggers, in order of likelihood:
- Sleep is hurting real users — the project has actual users beyond the demo, and cold starts cost them.
- You hit a hard cap — bandwidth, build minutes, database storage — two months running.
- You need reliability guarantees — backups with real retention, uptime SLAs, support.
Until one of these is true, the free tier is not a compromise — it is the correct budget for a student project. Spend the saved money (and the saved ops time) on the project itself.
Pre-launch checklist
- App binds
process.env.PORT; no hardcoded ports, no local-only assumptions. -
.envuntracked by git; all secrets in the platform's environment dashboard. - Database on a managed free tier (not the same container as the app); IP allowlist set; connection via env var.
- Health endpoint live; full user journey verified with curl and in the browser against the deployed URL.
- Cold-start behavior known and documented; demo warms the URL first.
- Custom domain + HTTPS working; CORS allows the frontend's domain.
- SPA fallback rewrite configured if the frontend uses client-side routing.
- Database exported and the backup file kept somewhere safe before demo day.
- Platform, tier and its limits written into the project report.
Putting it together
The deploy strategy in one paragraph: static frontends on GitHub Pages or Cloudflare Pages where they never sleep; the API on Render or Railway with process.env.PORT, secrets in env vars, and auto-deploy from GitHub; data on MongoDB Atlas M0 or a free Postgres with the IP allowlist opened; a custom domain with automatic HTTPS on top. Verify with curl against the live URL, design for cold starts instead of fighting them, and back up the database before demo day. A project at a public URL — with its hosting limits stated honestly in the report — is finished in a way that localhost never is. More software-build concepts live in the Computer / IT branch hub.