# Deploying OpenHTF to a Production Test Station URL: /guides/production-deployment Take an OpenHTF test from a developer laptop to a factory station — project layout, pinned dependencies, per-station YAML configuration, launcher scripts and autostart on Windows and Linux, kiosk browser for the Operator UI, record storage and upload, updates and rollback. OpenHTF has no deployment tool of its own; a station is a Python environment, your test package, a config file and something that starts it at boot. This page is the checklist that works. Keep phases and plugs importable as a package so the same code runs from `main.py`, from pytest and from a REPL. Recreate the environment from this file on every station; never `pip install` interactively on a station. `pip freeze > requirements.lock` on the reference machine gives you the transitive pins for an audit trail. See Installation. Everything that differs between stations lives in YAML loaded with `--config-file`; the code is identical everywhere. Declare each key with `CONF.declare(...)` and bind plug constructors with `bind_init_args` — Configuration, PyVISA guide. `python -m pcb01_eol.main --config-help` prints every key a station needs. Reading `CONF.station_server_port` after the flags are parsed works because `execute()`/`configure()` parse them; if you need the value earlier, call `test.configure()` first or parse with `configuration.ARG_PARSER` yourself (CLI flags). Naming config files after the hostname means one launcher for the whole line. `Restart=always` brings the station back after a crash or an operator Ctrl-C. Put secrets in an `EnvironmentFile=` rather than the unit. Create a task that runs `powershell -File C:\pcb01-eol\scripts\run.ps1` **At log on** of the operator account, with "Run whether user is logged on or not" off (the Operator UI needs a desktop session for the browser) and "If the task fails, restart every 1 minute". Set `TOFUPILOT_API_KEY` as a user environment variable for the operator account. Alternatively wrap the script with NSSM to run it as a service and start the kiosk browser separately at logon. `web_launcher.launch()` opens the default browser; on a station you want a full-screen kiosk pointed at the fixed port instead: Start it from the same launcher (after the server is up) or from the desktop session's autostart, and remove `web_launcher.launch()` from the code. See Operator UI. Write JSON to a local directory first (`records_dir`); local disk survives network outages. Ship them onward with an output callback — a database insert, an S3 sync, or TofuPilot's `upload()` which queues and retries. Output Callbacks, Manufacturing Test Analytics. Rotate or archive the local directory; a busy station produces thousands of files a month. Do not inline large attachments (`inline_attachments=False`) if the records are indexed. Tag releases of the test package (`v2.1.4`) and record the tag in metadata: `htf.Test(..., test_version="2.1.4")`. It lands in every record, so a yield change can be correlated with a test change. Deploy with `git pull && pip install -r requirements.txt` in the launcher's directory, then restart the service. Rollback is `git checkout v2.1.3` and restart. Never change limits on a station by hand; change `limits.yaml` in git and redeploy. `--config-help` on a fresh station lists every key; a missing one fails loudly at start rather than mid-shift. A `self_test` phase group that talks to every instrument and stops on failure catches unplugged cables before the first unit. Log to a file (`-v` plus `StandardOutput=append:` or `Start-Transcript`) so the console is not the only record of a crash.