DEV Community

Jordan Huang
Jordan Huang

Posted on

A Free Server Is Not a Small Server: Four Myths and a Boot Probe

I left a batch job on a free host and went to bed.

Next morning the files were gone. Was my code broken, or was my model of the host broken?

It was the model. A free server is ephemeral compute, not a tiny always-on VM.

Below are four claims I hear every week, the cheap evidence that kills them, and a boot probe you can run yourself.

The setup: two moving parts, one blind spot

My test job has two dependencies that people usually discuss separately.

  • Model calls. These consume a token allowance from a provider.
  • A host process. This runs the loop that makes the calls.

I'll use MonkeyCode's free model access and free server option as the concrete case here. There is no product in the failure, though; the same four myths apply to any free host.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

My job was simple. Fetch a queue, call a model, write results to a store, exit.

Everything worked on my laptop. Nothing worked twice on the free host. Why?

Myth 1: "A free server is just a smaller server"

Claim: the free tier is a cheap box with fewer cores. Same machine, less of it.

Evidence: check the kernel boot id and the uptime. Both reset when you are not watching.

cat /proc/sys/kernel/random/boot_id
awk '{print int($1) "s uptime"}' /proc/uptime
Enter fullscreen mode Exit fullscreen mode

Run those twice, once before a long idle period and once after. If the boot id changes, your process did not "pause". The host restarted under you.

Corrected model: the host is cattle, the state is the pet. Treat compute as replaceable and your data as the only thing worth protecting.

Write a boot record on every start. That record is your ground truth.

Myth 2: "If my process is alive, my job is alive"

Claim: nohup and a & were enough. The process survived the disconnect, so the job survives.

Evidence: reconnect and look. Usually there is no process, or a new one with a new start time.

ps -eo pid,lstart,etime,cmd | grep -i "[j]ob_runner"
Enter fullscreen mode Exit fullscreen mode

etime tells you how long the process has really lived. If it is shorter than your job, you lost a run and no alert fired.

Corrected model: liveness of one process proves nothing about the last hour. You need a supervisor that restarts, and a durable ledger that says which steps already finished.

Restarting without a ledger is just a repeated bill. Do you want to pay twice for the same step?

Myth 3: "The local filesystem is my database"

Claim: writing to /tmp or the app directory is fine. It is a disk, disks keep files.

Evidence: write a marker, force a restart, read it back. On ephemeral hosts the marker is gone.

Corrected model: classify every file before you write it. Three classes, three destinations.

State class Example Where it lives Survives restart? How to verify
Scratch Temp download, partial page Local disk No, by design Delete it on boot
Durable Job ledger, results, token log Mounted volume or external store Yes, if the volume is real Restart test, read back
Config API base URL, model id, budget Env vars or a committed file Yes, but redeploy resets Print env keys at boot

That middle row is the trap. "Free server" often ships with a volume that behaves differently than you assume. Test it, do not trust the docs alone.

A simple boot-time rule: if a file is not in durable storage, delete it and rebuild it.

Myth 4: "Free tokens mean free retries"

The team states a free allowance of 10,000,000 tokens. Free is not unlimited. Free is a budget with a nice name.

Every retry, every loop, every "just run it again" spends that budget. A crash loop that calls a model is the most expensive bug you can ship.

Corrected model: track spend per run, and fail closed. One line per call, one gate before the next call.

Here is the ledger I use. It assumes an OpenAI-style usage object; adapt the field names to your provider.

# token_ledger.py - append-only spend log with a hard per-run cap.
import json, os, pathlib, time

STATE = pathlib.Path(os.environ.get("STATE_DIR", "/data/state"))
LEDGER = STATE / "tokens.jsonl"
BUDGET = int(os.environ.get("RUN_TOKEN_BUDGET", "20000"))

def spent(run_id: str) -> int:
    if not LEDGER.exists():
        return 0
    total = 0
    for line in LEDGER.read_text().splitlines():
        row = json.loads(line)
        if row["run_id"] == run_id:
            total += row["total_tokens"]
    return total

def charge(run_id: str, step: str, usage: dict) -> int:
    prompt = int(usage.get("prompt_tokens", 0))
    completion = int(usage.get("completion_tokens", 0))
    row = {
        "run_id": run_id,
        "step": step,
        "at": round(time.time(), 3),
        "prompt_tokens": prompt,
        "completion_tokens": completion,
        "total_tokens": prompt + completion,
    }
    STATE.mkdir(parents=True, exist_ok=True)
    with LEDGER.open("a") as f:
        f.write(json.dumps(row) + "\n")
    used = spent(run_id)
    if used > BUDGET:
        raise SystemExit(f"budget exceeded: run={run_id} used={used} cap={BUDGET}")
    return used
Enter fullscreen mode Exit fullscreen mode

Call charge() after each response, before the next request. A crash loop now stops itself.

Two details matter. Store the ledger on durable storage, not scratch. And key it by run_id, so a restart resumes instead of re-billing.

The boot probe: label every run with the host's identity

This is the artifact that changed my debugging. It runs first, always, and it answers one question: is this a fresh host or a continuation?

# boot_probe.py - write one record per host start.
import json, os, pathlib, uuid
from datetime import datetime, timezone

STATE = pathlib.Path(os.environ.get("STATE_DIR", "/data/state"))

def read(path: str, default: str = "unknown") -> str:
    try:
        return pathlib.Path(path).read_text().strip()
    except OSError:
        return default

record = {
    "event": "boot",
    "run_id": uuid.uuid4().hex[:12],
    "boot_id": read("/proc/sys/kernel/random/boot_id"),
    "uptime_s": round(float(read("/proc/uptime", "0 0").split()[0]), 1),
    "state_dir": str(STATE),
    "config_keys": sorted(k for k in os.environ if k.startswith("MC_")),
    "at": datetime.now(timezone.utc).isoformat(),
}

STATE.mkdir(parents=True, exist_ok=True)
with (STATE / "boots.jsonl").open("a") as f:
    f.write(json.dumps(record) + "\n")

print(json.dumps(record))
Enter fullscreen mode Exit fullscreen mode

Expected output shape on a fresh host looks like this:

{"event": "boot", "run_id": "a91f0c3d2b7e", "boot_id": "3f2a...", "uptime_s": 4.1, "state_dir": "/data/state", "config_keys": ["MC_API_BASE", "MC_MODEL"], "at": "2026-11-08T05:12:44+00:00"}
Enter fullscreen mode Exit fullscreen mode

Read boots.jsonl after a quiet night. Three or more boot records means your host restarted three times.

Now the question changes. It is no longer "why did my job stop?" It is "which step was in flight at boot 2?"

A restart test you can run in one sitting

Do this before you trust any free host. It is five steps and no guessing.

  1. Deploy the probe and the ledger together.
  2. Write one durable marker and one scratch marker.
  3. Force a restart: redeploy, or wait out the idle window.
  4. Read boots.jsonl, then read both markers back.
  5. Compare: new boot_id, durable marker present, scratch marker gone.

If step 5 disagrees with that expectation, stop. Your durable volume is not durable.

Run the same test with the token budget set very low. The ledger should exit non-zero. If it does not, your gate is decorative.

What this approach cannot do

The probe is honest about its limits. Yours should be too.

  • /proc/sys/kernel/random/boot_id is Linux-specific. On other systems, fall back to a start timestamp.
  • Containers on the same host can share a boot id. Pair it with a container start time.
  • It detects restarts, not silent data loss on a misbehaving volume.
  • It cannot prove an allowance is permanent. Free terms change; measure, do not assume.
  • The ledger adds one write per call. On high-volume jobs, batch those writes.

Who should skip this

Be honest with yourself here.

  • Long single-process jobs. Hours of in-memory work do not fit an ephemeral host.
  • Strict SLA work. Free compute has no uptime promise you can quote to a customer.
  • Stateful databases. Use managed storage, not a scratch disk with hope.
  • Teams that will not run the restart test. Without it, you are guessing in production.

Small batch jobs, scrapers, evals, and prototypes fit well. That is the sweet spot.

Where MonkeyCode fits in this workflow

I introduced it as the concrete case because it has both halves: free model access and a free server option. The allowance is where the ledger earns its keep, and the host is where the boot probe earns its keep.

If you want to try the workflow without buying hardware, that pairing is a reasonable starting point. Bring your own restart test, and bring a budget cap you actually enforce.

What would your boots.jsonl say about last night?

Top comments (0)