免費伺服器的六個部署迷思與重啟演練
Your Free Server Forgets Everything: Six Deploy Myths and a Restart Drill
作者整理六個免費伺服器部署迷思,並提供可自行執行的檢查與重啟演練,涵蓋冷啟動、檔案持久性、模型 API 契約、健康檢查及重試。重啟前後讀取 /notes 並比較 /boot 的啟動 ID,可判斷資料是否留存及進程是否重啟;文中另列出十分鐘發布門檻。
You ship a small demo on a free server. It works.
You go to sleep. Next morning the app has no data.
Nothing crashed. Nothing paged you. The process just restarted.
So which assumption died overnight? Six of them, probably.
Below are claims I keep seeing repeated in review threads. Each one gets a check you can run yourself. No vendor screenshots. No numbers from my dashboard. Your box, your evidence.
Myth 1: "Free means always on"
The claim: a free service runs like a tiny paid VM.
The reality: many free tiers sleep, pause, or replace the process. Read your provider's current docs, not a blog post from last year.
The check: measure a cold request against a warm one.
BASE_URL="https://your-app.example"
# warm
curl -sS -o /dev/null -w 'ttfb=%{time_starttransfer}\n' "$BASE_URL/healthz"
# idle for a while, then hit it again
curl -sS -o /dev/null -w 'ttfb=%{time_starttransfer}\n' "$BASE_URL/healthz"
If the cold number dwarfs the warm number, you have a wake-up step. That is a design constraint, not a bug.
Corrected model: your app is a process that may start at any instant. Make boot cheap and keep state elsewhere.
Myth 2: "My files survive a restart"
The claim: the container disk behaves like a normal filesystem.
The reality: on many free tiers the filesystem is ephemeral. Restart, redeploy, or reschedule, and files are gone.
Guessing is pointless here. Prove it.
The restart drill
This assumes your app exposes POST /notes, GET /notes/:id, and GET /boot.
#!/usr/bin/env bash
# restart_drill.sh — proves what survives a restart.
set -euo pipefail
BASE="${BASE_URL:?set BASE_URL}"
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
echo "run=$RUN_ID"
curl -fsS "$BASE/boot"; echo
# 1. write through the app, not through the shell
curl -fsS -X POST "$BASE/notes" \
-H 'content-type: application/json' \
-d "{\"id\":\"$RUN_ID\",\"body\":\"survive-me\"}"; echo
# 2. read it back before the restart
curl -fsS "$BASE/notes/$RUN_ID"; echo
echo "restart the service now, then press enter"
read -r _
# 3. read again, and note the new boot id
curl -fsS "$BASE/boot"; echo
code=$(curl -s -o /tmp/after.json -w '%{http_code}' "$BASE/notes/$RUN_ID")
echo "after restart: http=$code body=$(cat /tmp/after.json)"
Read the result carefully:
-
200with the same body → storage survived. Do not celebrate yet. -
404→ the filesystem is ephemeral. Move state to a real store. -
500→ your code crashes on a missing file. That is a bug you can fix today. - A new boot id → the process really did restart. Good, the drill worked.
If you need a host for this toy endpoint, MonkeyCode's free server option plus its free model access is one place to run the drill end to end. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Myth 3: "The free model is a drop-in swap"
The claim: the API shape matches, so behavior matches.
The reality: schema adherence, refusal style, and max output length all differ. Your parser is the part that breaks.
Test the contract, not the vibes.
# contract_check.py — run before you swap providers
import json, os, urllib.error, urllib.request
URL, KEY = os.environ["MODEL_URL"], os.environ["MODEL_KEY"]
RUNS = int(os.environ.get("RUNS", "10"))
PAYLOAD = {"messages": [
{"role": "system", "content": "Reply with JSON only. No prose."},
{"role": "user", "content": 'Return {"status":"ok","n":3}.'},
]}
def valid(obj):
data = json.loads(obj["choices"][0]["message"]["content"])
return data.get("status") == "ok" and isinstance(data.get("n"), int)
def call():
req = urllib.request.Request(
URL, data=json.dumps(PAYLOAD).encode(),
headers={"content-type": "application/json",
"authorization": f"Bearer {KEY}"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())
ok = errs = 0
for i in range(RUNS):
try:
ok += bool(valid(call()))
except (urllib.error.HTTPError, json.JSONDecodeError, KeyError, TypeError) as e:
errs += 1
print(f"run {i}: {type(e).__name__}: {e}")
print(f"valid={ok}/{RUNS} errors={errs}")
Keep the payload byte-for-byte identical to production. Check your quota before setting RUNS high.
A low valid count is not a failed model. It is a prompt or parser problem you now know about.
Myth 4: "A 200 from /healthz means ready"
The claim: if the probe passes, traffic can flow.
The reality: most health checks only prove the HTTP server is listening. They say nothing about config, storage, or dependencies.
Split the two probes:
-
/healthz— liveness only. No I/O. Returns fast, always. -
/readyz— checks local config and a cheap dependency. No model calls.
Why exclude model calls? Because a readiness probe that burns quota is a self-inflicted outage. Probe the model on a schedule instead, once every few minutes.
Myth 5: "Retries are free"
The claim: a retry loop just adds resilience.
The reality: retries amplify load, burn quota, and stack latency. Three layers of retries become 27 calls.
Pick one retry layer. Make it bounded, jittered, and loud.
import random, time
def backoff(attempt, base=1.0, cap=20.0):
return min(cap, base * 2 ** attempt) * (0.5 + random.random())
Log every retry with a counter. If the counter looks flat, your retries are lying to you.
Myth 6: "A free server can't do real releases"
The claim: you need paid infrastructure to have a release process.
The reality: you need a gate, not a bigger bill. Free tiers fail in specific, repeatable ways. That makes them easy to gate.
The ten-minute release gate
- Run the restart drill. Note what survived.
- Record the cold-start budget. Write the number down.
- Run the contract check at your production payload.
- Confirm
/readyzfails when config is missing. - Confirm retries are capped and logged.
- Confirm one restart loses no user-visible data.
Six steps. Ten minutes. Same gate for free and paid boxes.
When free tier plus free model is the right call
| Situation | Use it? | Why |
|---|---|---|
| Demo, internal tool, prototype | Yes | Cheap, fast, easy to throw away |
| Async worker that can resume | Yes | Restarts are survivable by design |
| Session data in the process | No | The restart drill will shame you |
| Anything with an uptime promise | No | You cannot promise what you do not control |
| Regulated or sensitive data | No | Check your own obligations first |
| Hard latency SLO | No | Cold starts are not negotiable |
Who should not use this approach
Do not run this if you owe someone an SLA. Do not run it for long-lived worker processes. Do not run it if your data cannot leave a specific region.
Also skip it if you cannot read your provider's current quota docs. Free tiers change. A stale assumption is worse than a paid invoice.
What the drill actually teaches
It teaches you to stop guessing about state. Every myth above collapses into one question: where does the data live when the process dies?
Answer that, and the rest is config.
Run the drill before your next deploy, not after. If your free box passes all six checks, tell me which one surprised you.
來源:DEV Community · dev.to