DEV Community

Finley Zhu
Finley Zhu

Posted on

Workshop: Prove Your Laptop and a Free Server Run the Same AI Gateway in 90 Minutes

Most "it worked on my machine" failures in a free-tier AI gateway are configuration-parity bugs, not model bugs, and a twelve-line fingerprint plus four checks will surface them before the first prompt is sent. This workshop teaches that habit in 90 minutes: students write one config file, run one preflight script on two machines, and debug a deliberately broken deployment. The artifact they submit is two JSON reports that must agree on a config hash and disagree only where the environment is supposed to differ.

The failure this workshop prevents

A hosted small server and a laptop rarely disagree about Python. They disagree about three things, and each one produces an error message that points at the wrong layer.

  • Bind addresses. A laptop defaults to 127.0.0.1, while a hosted box must bind 0.0.0.0 or the service is unreachable from outside the machine.
  • Environment presence. An API key exported in ~/.zshrc exists on the laptop and nowhere else, so auth fails only after deploy.
  • Egress shape. Some hosts block, proxy, or redirect outbound HTTPS to the model endpoint, so a request that answers instantly locally times out remotely.

None of these are model problems, and none of them require reading provider documentation to detect. The workshop replaces guesswork with a report that names the failing check and the exact host and port involved.

What students build

  1. gateway.toml — a single source of truth for host, port, endpoint URL, model id, and token caps.
  2. preflight.py — a stdlib-only script that validates environment variables, required keys, port availability, and outbound reachability.
  3. preflight.local.json and preflight.server.json — the two reports compared at the end.
  4. One real request routed through the deployed gateway, with its latency recorded by hand.

The 90-minute timetable

Minutes Segment Output
0–10 Framing and repository clone Everyone has the same starting tree
10–25 Write gateway.toml from the blank template Config file committed
25–40 Write and run preflight.py locally preflight.local.json
40–60 Deploy the gateway and run the same script remotely preflight.server.json
60–75 Break parity on purpose and diagnose A written diagnosis per team
75–85 Send one real request and record latency A two-line log excerpt
85–90 Debrief on fingerprint equality Shared board of findings

Step 1: Write the config file (15 min)

The template is intentionally boring. Every value that differs between machines lives in one place, so the fingerprint changes only when a human changes it on purpose.

[server]
host = "0.0.0.0"
port = 8080
max_inflight = 4

[model]
base_url = "https://api.example.invalid/v1"  # replace with your provider base URL
name = "your-model-id"
timeout_s = 30
max_output_tokens = 256

[budget]
request_token_cap = 1024
Enter fullscreen mode Exit fullscreen mode

Instruct students to keep secrets out of this file. The API key comes from the environment, and the preflight script checks presence only, never the value.

Step 2: Write the preflight script (15 min)

Give students the skeleton and let them fill the four check functions. The point of the exercise is that every check returns the same shape, so the report is machine-comparable.

#!/usr/bin/env python3
"""preflight.py - prove two machines agree before the first prompt."""
import hashlib
import json
import os
import socket
import sys
import urllib.error
import urllib.parse
import urllib.request

REQUIRED_ENV = ["GATEWAY_API_KEY"]
REQUIRED_KEYS = [("server", "host"), ("server", "port"),
                 ("model", "base_url"), ("model", "name")]


def load_config(path):
    import tomllib  # python 3.11+
    with open(path, "rb") as fh:
        return tomllib.load(fh)


def fingerprint(cfg):
    payload = json.dumps(cfg, sort_keys=True).encode()
    return hashlib.sha256(payload).hexdigest()[:12]


def check_env():
    missing = [k for k in REQUIRED_ENV if not os.environ.get(k)]
    return {"name": "env", "ok": not missing, "missing": missing}


def check_config(cfg):
    missing = [f"{s}.{k}" for s, k in REQUIRED_KEYS if not cfg.get(s, {}).get(k)]
    return {"name": "config", "ok": not missing, "missing": missing}


def check_port(cfg):
    host = cfg.get("server", {}).get("host", "0.0.0.0")
    port = int(cfg.get("server", {}).get("port", 8080))
    bind_host = "127.0.0.1" if host in ("0.0.0.0", "::") else host
    try:
        with socket.socket() as sock:
            sock.bind((bind_host, port))
        return {"name": "port", "ok": True, "value": f"{bind_host}:{port}"}
    except OSError as exc:
        return {"name": "port", "ok": False,
                "value": f"{bind_host}:{port}", "error": exc.strerror}


def check_egress(cfg):
    url = cfg.get("model", {}).get("base_url", "")
    host = urllib.parse.urlparse(url).netloc
    try:
        with urllib.request.urlopen(urllib.request.Request(url), timeout=5) as resp:
            return {"name": "egress", "ok": True, "host": host, "status": resp.status}
    except urllib.error.HTTPError as exc:
        # 401 or 404 still proves the path is open; auth is a separate concern
        return {"name": "egress", "ok": True, "host": host, "status": exc.code}
    except Exception as exc:
        return {"name": "egress", "ok": False, "host": host,
                "error": type(exc).__name__}


def main():
    cfg = load_config(sys.argv[1] if len(sys.argv) > 1 else "gateway.toml")
    checks = [check_env(), check_config(cfg), check_port(cfg), check_egress(cfg)]
    report = {"fingerprint": fingerprint(cfg), "checks": checks,
              "ok": all(c["ok"] for c in checks)}
    print(json.dumps(report))
    raise SystemExit(0 if report["ok"] else 1)


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it identically on both machines and capture the output: python3 preflight.py | tee preflight.local.json.

Step 3: Run it on the free server (20 min)

For the deploy half, teams need a host and a model endpoint. A free server option and free model access remove the budget negotiation from the exercise, so students spend their time on parity instead of procurement. The operator states that the free tier includes 10 million tokens of model access, and that a free server option is available; treat those numbers as a starting budget rather than a guarantee, because quotas and hardware are subject to change and are not audited in this workshop. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The deploy sequence is deliberately short. Copy the config, set the key as an environment variable on the host, run the same script, and compare fingerprints. If fingerprint differs between the two reports, a human edited a file during transit and the team must reconcile before debugging anything else.

Worked example: reading a failed report

One team's server-side report returned exit code 1 with this body, trimmed for length:

{"fingerprint":"9c1a7f0d3b2e",
 "checks":[{"name":"env","ok":true,"missing":[]},
           {"name":"config","ok":true,"missing":[]},
           {"name":"port","ok":false,"value":"127.0.0.1:8080","error":"Address already in use"},
           {"name":"egress","ok":true,"host":"api.example.invalid","status":404}],
 "ok":false}
Enter fullscreen mode Exit fullscreen mode

Three facts follow directly from the report. The fingerprint matches the laptop's, so the config is identical and the config hash is not the culprit. Port 8080 is already occupied on the host, which is why the gateway never started and the deploy log looked empty. Egress returns 404, which means DNS and TCP are fine and the path is open, so there is nothing to fix at the network layer.

The fix is a one-line config change to port 8081, a re-run, and an updated fingerprint recorded in both reports. The teaching moment is that the error message from the process manager never mentioned the port, while the preflight did.

Step 4: Break parity on purpose (15 min)

Each team applies one mutation from a sealed list and predicts which check fails before running anything. Predictions are written on the whiteboard, then tested.

  1. Change host to 127.0.0.1 on the server only.
  2. Move GATEWAY_API_KEY from the host environment into gateway.toml.
  3. Replace the endpoint hostname with a non-resolving name.
  4. Set port to a value below 1024 to see the permission error surface.

Most teams get two of four right. The recurring miss is mutation 2, because a hardcoded key passes the config check and fails the env check only if the script enforces separation, which is why the exercise exists.

What the report does not prove

  • It does not validate model output quality, prompt behavior, or token accounting.
  • A reachable endpoint with status 404 is counted as ok: true; authentication is out of scope.
  • Fingerprints cover the config file only, not installed package versions or system libraries.
  • Nothing here measures throughput, so it is not a load test and should not be sold as one.

Skip this approach if your deployment already runs behind a platform that injects configuration at boot, if your secrets live in a manager that forbids environment variables, or if you need production-grade tracing on day one. The workshop is for teams whose first deploy keeps failing for reasons nobody can name.

Debrief questions

  1. Which check caught a real problem in your setup, and which one was pure ceremony?
  2. What else belongs in the fingerprint without making it unstable across machines?
  3. How would you extend the report so a CI job can block a deploy on a failing check?

Keep both JSON reports in the repository next to the config. If your team needs a key and a host to run the exercise, the free model access and free server option are a reasonable place to start, and the reports will tell you the truth about whether the two machines agree.

Top comments (0)