You deploy a small worker on a free server. It runs fine for a week.
Then the host restarts, and the worker wakes up confused.
This post is about that morning. Not the deploy. The wake-up.
The claim I keep hearing
"It's just a tiny script. Local files are enough."
That sentence is usually true until the first restart.
Free hosts are cheap because something is disposable. Usually it is you.
Below are four myths, the corrected mental model, and a drill you can adapt.
All code here is illustrative. I did not execute it for this article.
Myth 1 — "The disk is a database"
The claim: writes to /data or ./state survive anything.
Why it breaks: a container's writable layer belongs to that container.
Destroy the container and the layer goes with it.
The Twelve-Factor App calls this out under processes: processes are stateless, and state belongs in a backing service.
Docker's storage documentation describes the writable layer the same way.
Corrected model: local disk is a cache, not a source of truth.
Treat every local file as recomputable or lossy.
Quick check, before you trust a path:
# Illustrative. Read your host's docs for the actual mount map.
mount | grep -E ' /data | /tmp '
df -h /data /tmp 2>/dev/null
touch /data/.probe && echo ok || echo read-only-or-missing
If /data is not a documented persistent volume, assume it is scratch space.
Verify that claim in the provider's docs, not in a blog post.
Myth 2 — "A process that starts once keeps running"
The claim: the worker is up, so the scheduler is unnecessary.
Why it breaks: free tiers often idle, suspend, or reap instances.
The exact policy varies by provider and by plan.
I am not going to quote a number I cannot verify for your host.
Corrected model: assume the process dies without warning, and design for a supervisor.
Two habits fix most of this:
- Make startup idempotent. Running it twice must be safe.
- Make shutdown quick. Handle
SIGTERM, stop accepting work, flush or hand off.
# Illustrative sketch, not executed.
import signal, sys
def on_term(signum, frame):
accepting.clear() # stop pulling new jobs
drain(timeout=20) # finish or hand back in-flight work
sys.exit(0)
signal.signal(signal.SIGTERM, on_term)
The important part is not the code. It is the question: what happens to in-flight work?
Myth 3 — "A crash leaves clean state"
The claim: either the job wrote its result or it did not.
Why it breaks: a job can write output, then die before acknowledging upstream.
Now you have a result nobody owns and a job somebody will resend.
This is the classic write-then-ack gap, and restarts find it.
Corrected model: state transitions need an explicit order and a reconciliation pass at boot.
Write the result first. Mark it durable. Acknowledge upstream last.
If you cannot make those steps atomic, make them replayable.
Myth 4 — "Cold start is just a number"
The claim: start-up time is a constant you measured once.
Why it breaks: the second start is different from the first.
Caches are empty, connections are cold, and any warm-up routine reruns.
A p50 from your laptop tells you nothing about a restarted instance.
Corrected model: measure time-to-first-useful-work, not time-to-boot.
Log a timestamp at process start. Log another when the first job completes.
Compare across restarts. The gap is your real cost.
The artifact: a restart drill
This drill is the point of the post. Run it three times.
Label results as observations, not benchmarks, until you have many runs.
The setup used below is a small worker on a free server option, calling a model endpoint through free model access — both availability claims come from the operator, not from measurements here.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Substitute your own host and endpoint; the drill is provider-agnostic.
Step 1 — Write a boot marker
#!/usr/bin/env sh
# Illustrative only. Not executed for this article.
set -eu
STATE_DIR="${STATE_DIR:-/data}"
BOOT_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"
MARKER="$STATE_DIR/boot-id.txt"
mkdir -p "$STATE_DIR"
echo "boot=$BOOT_ID start=$(date -u +%s)" >> "$STATE_DIR/boot-log.txt"
if [ -f "$MARKER" ]; then
echo "persisted_marker=$(cat "$MARKER")"
else
echo "persisted_marker=missing"
fi
echo "$BOOT_ID" > "$MARKER"
Run it. Restart the host. Run it again.
- Second run prints a marker → the path persisted across that restart.
- Second run prints
missing→ the path did not. Move state off the host.
Step 2 — Make one job replay-safe
# Illustrative request shape. Verify the real endpoint in the provider docs.
curl -sS -X POST "$MODEL_BASE_URL/chat/completions" \
-H "Authorization: Bearer $MODEL_API_KEY" \
-H "Content-Type: application/json" \
-d @request.json > "/data/outbox/job-001.json"
The file exists before any acknowledgement happens. That order matters.
Step 3 — Reconcile at boot
# Illustrative pseudocode. Requeue is your client, your transaction.
import json, pathlib
for path in sorted(pathlib.Path("/data/outbox").glob("*.json")):
record = json.loads(path.read_text())
if not record.get("acked"):
requeue(record)
path.unlink()
If the outbox did not survive, this loop silently does nothing. That is the failure mode to watch for.
Step 4 — Record what the drill showed
| Observation | Where it comes from | What it tells you |
|---|---|---|
| Marker survived restart | Step 1 output | Local disk is persistent for this host |
| Marker missing | Step 1 output | State must move to external storage |
| Duplicate job after restart | Step 3 requeue count | Your ack ordering has a gap |
| Time-to-first-useful-work | Your start and finish logs | Cold-start cost, per restart |
Where state should live
| Data | Ephemeral disk | External store |
|---|---|---|
| Build artifacts, temp files | Yes | No |
| Model responses awaiting ack | No | Yes |
| Job queue | No | Yes |
| Logs you need later | No | Yes |
| Recomputable cache | Yes | Optional |
Who should not use this approach
- Teams with a managed database and durable queues. You already have this.
- Workloads that must never drop a job. Use a real queue with delivery guarantees.
- Anyone who needs an SLA. A free host is a good place to learn, not to promise uptime.
What to verify before you ship
- Read your host's docs on restarts, idle policy, and disk persistence.
- Run the drill at least three times, including one forced restart.
- Check that duplicate work is safe, not just unlikely.
- Confirm any model endpoint shape and limits in the provider's own docs.
The corrected mental model is short: the host is disposable, the state is not.
Design the restart before you design the feature.
If you want a cheap place to run this drill end to end, the free server option and free model access are a reasonable starting point — bring your own verification.
Top comments (0)