The cutover is finished when you can rebuild the host from an empty image in one command, not when your code points at a new endpoint. Most migration plans enumerate prompts, model aliases, and API keys, then quietly assume the machine underneath will keep behaving the way it did last quarter. In this diary entry the leftover is the host itself: the box that holds your key, your retry loop, and your logs. The last week of DEV conversation has been full of large arguments about AI and work; the boring counterpart is that someone still has to keep the server alive.
The workflow below pairs two things the MonkeyCode team currently describes as available: free model access and a free server option. Their own figure for the free model access is 10 million tokens, and you should treat that as a present-day offer rather than a capacity plan you can budget against.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Because I cannot verify your environment, treat every script here as a template. None of it has been executed against your infrastructure, and none of the timings, budgets, or thresholds are measured results.
Why the host is the leftover nobody writes down
Your migration checklist probably lists prompt templates, model alias mappings, and the secret rotation ticket. The host rarely appears, because hosts feel permanent until the day they are not. A free server is worse than a paid one in exactly one respect: your mental model keeps treating it as a managed pet while the billing model treats it as cattle. Write the rebuild path first, and the provider swap becomes a deploy.
Step 1 — Decide what belongs on the box at all
Before you write a single line of provisioning, split your state into two columns. Anything you cannot rebuild from a repository should not live on a node whose lifecycle you do not control.
| Item | On the box | Off the box | Reason |
|---|---|---|---|
| API key | No | Yes (secret store or env file injected at boot) | Rotation must not require a rebuild |
| Prompt templates | No | Yes (repo, versioned) | Diffable and reviewable |
| Model alias map | No | Yes (config file) | Changes without redeploy |
| Logs | Short retention only | Yes (shipped out) | A vanished node takes its disk |
| Retry counters | Yes (in memory) | Snapshot if you bill on them | Cheap to lose, expensive to trust |
| Cached responses | Yes | Optional | Rebuild cost is one request |
If a row lands in the wrong column, you have found the leftover before it finds you.
Step 2 — Write a preflight that fails loudly
The cheapest hour you will spend is the one that makes a bad node refuse to start. This script checks the four things that silently break a cutover: the key, the endpoint, the clock, and the disk.
#!/usr/bin/env bash # deploy/preflight.sh — template, not run against your host set -euo pipefail : "${MC_API_KEY:?set MC_API_KEY via env file, never in the repo}" : "${MC_ENDPOINT:?set MC_ENDPOINT from your account page}" fail=0 ok() { printf 'ok %s\n' "$1"; } bad() { printf 'FAIL %s\n' "$1"; fail=1; } [ "${#MC_API_KEY}" -ge 16 ] && ok 'key present' || bad 'key present' host=$(printf '%s' "$MC_ENDPOINT" | sed -E 's#^https?://##; s#/.*##') if timeout 5 bash -c "</dev/tcp/${host}/443" 2>/dev/null; then ok 'endpoint tcp 443' else bad 'endpoint tcp 443' fi if [ "$(date -u +%Y)" -ge 2026 ]; then ok 'clock sane'; else bad 'clock sane'; fi free_kb=$(df -Pk /srv | awk 'NR==2{print $4}') [ "$free_kb" -gt 2097152 ] && ok 'disk > 2G' || bad 'disk > 2G' exit "$fail" Run preflight in CI as well as on the node, because a green local build tells you nothing about egress rules. A failing preflight should stop the deploy, not print a warning nobody reads.
Step 3 — Make bootstrap idempotent and boring
Your first bootstrap will work; your second one after a failed run is where migrations go wrong. Every directory creation, user creation, and service start needs to tolerate already-existing state.
#!/usr/bin/env bash # deploy/bootstrap.sh — safe to run repeatedly set -euo pipefail APP_DIR=/srv/mc-gateway id mcgateway >/dev/null 2>&1 || useradd --system --home "$APP_DIR" --shell /usr/sbin/nologin mcgateway install -d -o mcgateway -g mcgateway -m 0750 "$APP_DIR" "$APP_DIR/logs" if [ ! -x "$APP_DIR/venv/bin/python" ]; then python3 -m venv "$APP_DIR/venv" "$APP_DIR/venv/bin/pip" install --quiet -r requirements.txt fi [ -f /etc/mc-gateway.env ] || install -m 0640 -o root -g mcgateway deploy/gateway.env.example /etc/mc-gateway.env systemctl daemon-reload systemctl enable --now mc-gateway.service Note what the script refuses to do: it never overwrites an existing env file. That single [ -f ] || guard is the difference between a rebuild and an outage.
Step 4 — Give the service an explicit restart budget
A restart loop is an expensive way to discover that your new endpoint rejects malformed requests. Each retry can cost real tokens from a finite free allowance, so cap the loop instead of hoping it stabilizes.
# /etc/systemd/system/mc-gateway.service [Unit] Description=Model gateway shim After=network-online.target Wants=network-online.target [Service] User=mcgateway EnvironmentFile=/etc/mc-gateway.env ExecStart=/srv/mc-gateway/venv/bin/python -m gateway.app Restart=on-failure RestartSec=5 StartLimitBurst=5 StartLimitIntervalSec=120 NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ReadWritePaths=/srv/mc-gateway/logs [Install] WantedBy=multi-user.target When StartLimitBurst trips, systemd stops trying and you get a readable failure instead of a silent token drain. Wire that state into your alerting, because failed is the signal you actually want.
Step 5 — Rehearse the reclaim before you need it
Free capacity can disappear, change shape, or be recreated from a base image without warning to you. Your planning assumption should be that the node is disposable, and a drill is how you make that assumption testable.
#!/usr/bin/env bash # deploy/reclaim-drill.sh — destroy and rebuild, then measure set -euo pipefail start=$(date +%s) ./teardown.sh # snapshot what you need, then destroy or reset ./provision.sh # new empty node, same base image, same bootstrap ./bootstrap.sh until curl -fsS --max-time 3 http://127.0.0.1:8080/healthz >/dev/null 2>&1; do sleep 2 if [ $(( $(date +%s) - start )) -gt 600 ]; then echo 'drill failed: exceeded 10 minute budget'; exit 1 fi done echo "rebuilt and healthy in $(( $(date +%s) - start ))s" Run this drill at least once before cutover and once after, and record the wall-clock number somewhere your team reads. If the drill needs a manual step, the manual step is the incident waiting to be scheduled.
Step 6 — Cap retries so a bad afternoon cannot eat the allowance
Retry storms are the classic way to burn a fixed free allowance in minutes. Keep the counter in your own code so the limit does not depend on provider-side behaviour you cannot inspect.
# gateway/budget.py — illustrative guard, adapt numbers to your own plan from dataclasses import dataclass, field @dataclass class Spend: tokens: int = 0 limit: int = 10_000_000 # operator-supplied figure, review it periodically def charge(self, estimate: int) -> None: if self.tokens + estimate > self.limit: raise RuntimeError('local token budget exhausted; stop the loop') self.tokens += estimate Call charge before every request, not after, so an estimate over the limit fails fast without spending anything. Track the estimate accuracy in your logs, because a guard that underestimates is decoration.
Limitations and who should skip this
This approach assumes you can script the node lifecycle and that configuration lives in a repository you control. If your workload needs a persistent local disk, licensed GPU drivers, or a support contract with an SLA, a free server is the wrong target and the drill above proves it quickly rather than quietly. If you cannot run a destroy-and-rebuild safely yet, stop at Step 2 and write the inventory first. Teams without outbound log shipping should not adopt this pattern at all, because a reclaimed node will take your only evidence with it.
What to take away
The pattern is not about any single provider; it is about writing down the rebuild path before the endpoint changes. Start with the decision table, add preflight, and let the reclaim drill tell you the truth about your assumptions. The free model access and free server option are a reasonable place to practise that drill on a weekend project, and a poor place to host anything you cannot rebuild from scratch.
