You deployed it. It worked. Then the instance restarted overnight.

Now your chat endpoint answers as if the conversation never happened. Was that disk supposed to hold your state?

This is a myth-busting FAQ about persistence on small, free-hosted apps. It ships with a restart probe you can run in about ten minutes. All code below is a template; I am not publishing measured results for your host.

The five myths I keep hearing

Myth 1: "If I write it to disk, it is persisted"

The claim. fs.writeFile is durable, so a local JSON file is fine storage.

Why it feels true. It survives your process crashing. That is real.

What to check. Does the instance keep its disk across a restart? Across a rebuild? Those are different events. Ask your host explicitly, and reread the storage docs.

Corrected model. Local disk is scratch space with a marketing problem. Treat it as a cache you cannot trust.

Myth 2: "A restart and a redeploy are the same thing"

The claim. If the disk survived last time, it will survive next time.

Why it feels true. You restarted the service and your files were still there.

What to check. A redeploy often builds a fresh image on a fresh machine. A restart may reuse the old one. Your test must name which event it exercised.

Corrected model. Restart, redeploy, and resize are three separate experiments. Run all three, or stop claiming durability.

Myth 3: "My in-memory cache will just refill"

The claim. Losing a warm cache is fine, because the cache warms itself.

Why it feels true. In steady traffic, yes, it does.

What to check. What happens on the first request after a cold start, with five hundred users arriving at once? That is a thundering herd, not a cache miss.

Corrected model. A cold cache is a load event. Budget for it, or gate the refill.

Myth 4: "Session state can live in a local file until we get a DB"

The claim. A sessions.json on disk is a fine interim solution.

Why it feels true. On a single instance, it is trivially simple and fast.

What to check. Restart the box while a user is mid-conversation. If the session vanishes, users see a forgotten session and a fresh greeting. They blame the model.

Corrected model. Local-file sessions are a single-instance lock-in. Fine for a demo, dishonest as a default.

Myth 5: "My local logs are an audit trail"

The claim. I log every prompt and response to app.log, so I can debug later.

Why it feels true. The file exists, and it grows.

What to check. Restart, then look for the file. If it is gone, your audit trail had a lifetime measured in uptime.

Corrected model. A log on ephemeral disk is a debugging aid for the current boot, nothing more.

Corrected mental model: three tiers of state

Stop asking "is it stored?" Start asking "where does it live, and what kills it?"

Tier Survives process crash Survives restart Survives rebuild Right home for
Process memory No No No Hot caches, connection pools
Instance-local disk Usually Sometimes Rarely Temp files, build output, scratch
Attached volume Usually Usually Depends on config Single-writer scratch
External store Yes Yes Yes Sessions, prompt journals, ledgers

The rule I use: anything a user would notice missing goes in the bottom row. Everything else may live above it.

Artifact: a restart probe in forty lines

Here is the probe. Standard library only, no dependencies.

# restart_probe.py import json, os, sys, time, uuid STATE = os.environ.get("PROBE_STATE", "./probe_state") LEDGER = os.environ["PROBE_LEDGER"] # MUST live outside this instance MARKER = os.path.join(STATE, "marker.json") def write_phase(): os.makedirs(STATE, exist_ok=True) rec = {"token": uuid.uuid4().hex, "written_at": time.time()} with open(MARKER, "w") as f: json.dump(rec, f) with open(LEDGER, "a") as f: f.write(json.dumps({"phase": "write", **rec}) + "\n") print("wrote token=%s" % rec["token"]) def verify_phase(): want = None with open(LEDGER) as f: for line in f: rec = json.loads(line) if rec["phase"] == "write": want = rec["token"] if want is None: sys.exit("no write recorded in ledger") try: with open(MARKER) as f: got = json.load(f)["token"] except FileNotFoundError: print("MISSING: expected %s, marker gone" % want) return 1 if got == want: print("SURVIVED: %s" % want) return 0 print("DRIFT: expected %s, found %s" % (want, got)) return 1 if __name__ == "__main__": sys.exit(write_phase() if sys.argv[1] == "write" else verify_phase()) 

The one rule that makes or breaks this test. PROBE_LEDGER must point outside the instance. Put it on your laptop, in paste buffer output you capture, or in any store that does not die with the box. A ledger on the same disk proves nothing.

Run protocol

  1. Write phase: PROBE_LEDGER=$HOME/probe.jsonl python restart_probe.py write.
  2. Restart the service only. Verify.
  3. Redeploy or rebuild. Verify again.
  4. If your host suspends idle instances, let it idle, wake it, verify a third time.

Three runs, three answers. Now you know which event eats your files. Do not guess.

The model part: your prompt journal is state too

If your app calls a model, the only record of what the model saw is the journal you wrote. Lose it, and you cannot explain a bad answer later.

So extend the probe. Log a prompt hash and a response hash per call, to the same external ledger.

import hashlib def journal(ledger_path, prompt, response): row = { "prompt_sha256": hashlib.sha256(prompt.encode()).hexdigest(), "response_sha256": hashlib.sha256(response.encode()).hexdigest(), "ts": time.time(), } with open(ledger_path, "a") as f: f.write(json.dumps(row) + "\n") 

Hashes keep the ledger small and avoid storing user text. You lose readability, and you gain the ability to prove a call happened. That trade is usually the right one for a prototype.

Why hash instead of skip? Because "the model gave a weird answer" is unfalsifiable without a record. Twelve hours after a restart, your log is gone and so is your argument.

Where a free server and free models fit

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

This probe is cheap to run and cheaper to run repeatedly. That is where a free server option and free model access genuinely help: you can host the tiny probe app, drive it with real model calls, and repeat the restart cycle without a paid round trip each time.

The workflow that makes sense:

  • Host the probe app on a free server, and keep the ledger off that server.
  • Drive it with real prompts through free model access, so the journal has traffic in it.
  • Restart, rebuild, and wake it. Compare runs.
  • Only then decide what belongs in durable storage.

What this does not give you: any promise about durability, uptime, or retention. Availability of a free tier says nothing about persistence. That is exactly why you probe.

If you want to run this loop yourself, the free server option on MonkeyCode is enough to start, as long as you bring your own off-box ledger.

Limits, and who should skip this

  • The probe tests restart behavior, not crash-during-write. Use a real database for atomicity questions.
  • It cannot tell you about partial writes, fsync semantics, or replication lag.
  • One instance tells you nothing about multi-instance races. Two writers on one file will teach you the hard way.
  • If your platform already gives you managed durable storage with documented guarantees, skip the probe. Read the docs instead.
  • If your data is regulated, this toy ledger is not a compliance control. Talk to someone qualified.

The question to ask before the next deploy

Not "where did I write it?" Ask "what kills this, and have I watched it die?"

Run the probe once. If it prints MISSING, you just saved yourself a confusing support ticket. If it prints SURVIVED, you now have evidence instead of a feeling.