Agentic AI on the Plant Floor - Secure Deployment with OnLogic FR201 & ZeroClaw

Most edge agent setups lean on Node.js runtimes, Python interpreters, or containerized stacks that pull in hundreds of megabytes of dependencies. That works fine on a beefy server, but on an industrial edge device sitting on a plant floor, all that bloat becomes a liability. More dependencies mean more attack surface, more things to patch, and more ways for something to break at 2 AM when nobody’s around.
This guide takes a different approach. We’ll deploy ZeroClaw as a single static Rust binary on an OnLogic FR201, managed by systemd, with no runtime dependencies. The result is a minimal, hardened, production-ready edge agent that boots up and runs without needing Node, Python, Docker, or anything else installed on the device.
What Is the OnLogic FR201?
Section titled “What Is the OnLogic FR201?”If you haven’t come across it before, the OnLogic FR201 (also called the Factor 201) is essentially a Raspberry Pi that’s been built for industrial environments. At its core is a Raspberry Pi Compute Module 4 (CM4) with a Broadcom BCM2711 quad-core Cortex-A72 ARM processor running at 1.5 GHz, paired with up to 8 GB of LPDDR4 memory. OnLogic designed their own carrier board and industrial chassis around the CM4, so what you get is the familiar Raspberry Pi ecosystem wrapped in hardware that can actually survive a factory floor.
Here’s why it’s a solid fit for edge deployments in manufacturing:
- Fanless and sealed. No moving parts means no dust buildup, no fan failures. OnLogic has thermal-validated the FR201 from 40°C to 60°C in a thermal chamber, so it can handle the heat near ovens, motors, or enclosed cabinets without breaking a sweat (literally).
- Industrial I/O. Dual Gigabit Ethernet (with optional Power over Ethernet), RS-232/422/485 serial via terminal block, USB 3.2, HDMI output, and a standard Raspberry Pi GPIO header. That serial port alone opens the door to talking directly to PLCs, sensors, and legacy equipment.
- Compact form factor. At roughly 102mm x 129mm x 38mm, it can be DIN-rail mounted or tucked into a control panel without taking up much space.
- Auto power-on. When power is applied, it boots automatically. No power button to press. This matters in industrial settings where devices need to come back online after a power cycle without human intervention.
- M.2 storage expansion. Beyond the CM4’s onboard eMMC, there’s an M.2 2280 SATA slot for adding a proper SSD, which is what we’ll use for the OS and ZeroClaw.
- Optional TPM 2.0. For environments where hardware-rooted trust is a requirement.
It runs standard Raspberry Pi OS or Ubuntu, so all the ARM64 tooling you’re already familiar with just works.
Why This Setup Is More Secure
Section titled “Why This Setup Is More Secure”Before jumping into the steps, it’s worth understanding why this approach is meaningfully more secure than typical agent deployments. This isn’t security theater; each decision here removes a real category of risk.
Single static binary, zero runtime dependencies. ZeroClaw compiles down to one self-contained executable linked against musl libc. There’s no Node.js runtime with its node_modules tree, no Python interpreter with pip packages, no JVM. Every dependency that isn’t on the device is a dependency that can’t be exploited. You don’t need to worry about a supply-chain attack hiding in a transitive npm package because there are no npm packages.
No package manager attack surface. With no pip, npm, or cargo running on the device, there’s no mechanism for a compromised process to pull down and execute arbitrary code from the internet. The only software that runs is what you explicitly put there.
Dedicated service account with no login shell. The zeroclaw user is created with --shell /usr/sbin/nologin, which means even if an attacker somehow compromised the service, they can’t use that account to get an interactive shell on the device.
Systemd hardening directives. The service unit we’ll configure includes NoNewPrivileges, PrivateTmp, ProtectSystem=strict, ProtectHome, RestrictNamespaces, and more. These aren’t just nice-to-haves. ProtectSystem=strict makes the entire root filesystem read-only from the service’s perspective. NoNewPrivileges prevents privilege escalation through setuid binaries. Together, they create a tight sandbox around the process.
Minimal OS footprint. Raspberry Pi OS Lite is a headless Debian-based image with no desktop environment, no browser, no GUI libraries. Less installed software means fewer things that need patching and fewer potential vulnerabilities.
SSH key authentication with password login disabled. We configure SSH to only accept public key authentication and explicitly disable password login. This eliminates the entire class of brute-force and credential-stuffing attacks. Even if someone discovers the device’s IP, there’s no password to guess.
File permissions on secrets. The API key configuration file is locked down to chmod 600, readable only by the service account. No other user on the system can read it.
Compared to running a Docker container with a Node.js agent (which means you need Docker, containerd, the Node runtime, npm packages, and all their transitive dependencies), this approach has a dramatically smaller attack surface. In an OT environment where these devices sit on or near production networks, that matters.
Prerequisites
Section titled “Prerequisites”Before you begin, make sure you have the following:
- An OnLogic FR201 device w/ SSD
- A power supply for the FR201, or PoE capability if your FR201 and switch support it
- An SSK M.2 NVME SATA SSD adapter (so you can image the SSD from your workstation)
- A Cat5/6 Ethernet cable
- A monitor and keyboard for initial setup
- A small Phillips screwdriver for opening the FR201 case
Installing Raspberry Pi OS Lite
Section titled “Installing Raspberry Pi OS Lite”When you order an FR201 from OnLogic, you can have them pre-install various Linux distributions. For ZeroClaw, we want Raspberry Pi OS Lite (64-bit), the headless variant based on Debian. You can select this during ordering, or you can image the SSD yourself.
To image it yourself:
- Download the Raspberry Pi Imager and install it on your workstation.
- Power down the FR201, open the case, and remove the M.2 SSD. Connect it to your workstation using the SSK adapter.
- Open the Raspberry Pi Imager and select Raspberry Pi OS (other) from the OS list, then choose Raspberry Pi OS Lite (64-bit).
- Set the hostname to
zero(or whatever you prefer for your naming convention). - If your FR201 has Wi-Fi and you want to configure it now, enter your Wi-Fi credentials in the imager settings. Otherwise, skip this and use Ethernet.
- Set the username to
clawand choose a strong password. You’ll need these credentials for initial login. - Under the Services tab in the OS customization settings, check Enable SSH and select Use password authentication. We’ll switch to key-based authentication and disable password login after the first boot, but we need password auth enabled initially so we can get in and set things up.
- Select the SSD as the target storage device.
- Click Write to format the SSD and install the OS.
- Once the write completes, the imager will eject the drive by default. Unplug the adapter and reconnect it so the drive remounts.
- The FR201 requires specific firmware configuration files. Download and extract the appropriate set onto the boot partition of the SSD:
- Safely eject the SSD, reinstall it in the FR201, and close the case.
Initial Boot and Configuration
Section titled “Initial Boot and Configuration”Connect the FR201 to a monitor, Ethernet cable, keyboard, and power supply. Because the FR201 has auto power-on, it will boot as soon as power is applied.
Log in with the claw user and the password you set during imaging, then update the system:
sudo apt updatesudo apt upgrade -yLocale and Keyboard
Section titled “Locale and Keyboard”By default, Raspberry Pi OS ships with the locale set to Great Britain. If you’re in the US (or anywhere else), you’ll want to fix this now so you don’t get surprised by weird character encoding issues later:
# Disable all locales except en_US.UTF-8sudo sed -i 's/^\([^#].*\)/# \1/' /etc/locale.gensudo sed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
# Generate the new localesudo locale-gen en_US.UTF-8
# Set the system localeecho "LANG=en_US.UTF-8" | sudo tee /etc/default/localeSet the keyboard layout to US:
sudo sed -i 's/^XKBLAYOUT=.*/XKBLAYOUT="us"/' /etc/default/keyboardsudo setupconTimezone
Section titled “Timezone”Set the timezone. For manufacturing systems that span multiple plants, UTC is often the safest bet since it avoids daylight saving time headaches and makes log correlation across sites straightforward:
sudo timedatectl set-timezone UTCIf you’d rather use a local timezone:
sudo timedatectl set-timezone US/MountainSSH and Remote Access
Section titled “SSH and Remote Access”Once the FR201 is on the network, you’ll want to manage it headless over SSH. Grab the IP address from the FR201’s console:
hostname -IFor the initial connection, use the password you set during imaging:
ssh claw@<IP_ADDRESS>Setting Up SSH Key Authentication
Section titled “Setting Up SSH Key Authentication”Password authentication works for the initial setup, but you don’t want to leave it enabled on a device that’s going to sit on a plant network. SSH keys are both more secure and more convenient. No passwords to remember, no brute-force risk.
On your workstation, generate an SSH key pair if you don’t already have one:
ssh-keygen -t ed25519 -C "your_email@example.com"When prompted for a file location, the default (~/.ssh/id_ed25519) is fine. You can optionally set a passphrase for an extra layer of protection on the key itself.
Copy your public key to the FR201:
ssh-copy-id claw@<IP_ADDRESS>Enter your password one last time. After this, verify that key-based login works by opening a new SSH session:
ssh claw@<IP_ADDRESS>You should get in without being prompted for a password. If that works, it’s time to disable password authentication entirely.
Disabling Password Authentication
Section titled “Disabling Password Authentication”SSH into the FR201 and edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_configFind and update (or add) the following lines:
PasswordAuthentication noChallengeResponseAuthentication noUsePAM noSave the file and restart the SSH service:
sudo systemctl restart sshdFrom this point on, only someone with the matching private key can SSH into the device. If you try to connect from a machine that doesn’t have your key, the connection will be refused. This is exactly what we want for a device sitting on a production network.
Install rsync
Section titled “Install rsync”We’ll use rsync to deploy the binary later, so install it now along with curl:
sudo apt install -y curl rsyncSetting Up Tailscale for Remote Access
Section titled “Setting Up Tailscale for Remote Access”If these devices are going to be deployed across multiple plants (and you’re not always on the same network), Tailscale is the easiest way to maintain secure remote access. It creates a WireGuard-based mesh VPN, so you can SSH into your FR201 from anywhere without exposing ports to the public internet.
curl -fsSL https://tailscale.com/install.sh | shBring it up and authenticate:
sudo tailscale upThis will print a URL. Open it in your browser, log in to your Tailscale account, and authorize the device. Once it’s connected, you can reach the FR201 by its Tailscale IP from any device on your tailnet.
Cross-Compiling ZeroClaw
Section titled “Cross-Compiling ZeroClaw”ZeroClaw is written in Rust, and we’re going to cross-compile it on your workstation (Mac or Windows) to produce a static ARM64 binary that runs on the FR201 with no shared library dependencies.
Install Rust
Section titled “Install Rust”On macOS, install Homebrew first if you don’t have it:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Then install Rust (works on both macOS and Windows):
# macOScurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh# Windowswinget install --id=Rustlang.Rustup -eRestart your terminal and verify:
rustc --versioncargo --versionAdd the ARM64 musl Target
Section titled “Add the ARM64 musl Target”The musl target produces a fully static binary, no glibc dependency on the target device:
rustup target add aarch64-unknown-linux-muslVerify it’s installed:
rustup target list --installed# You should see aarch64-unknown-linux-musl in the listInstall the musl Cross-Compilation Toolchain
Section titled “Install the musl Cross-Compilation Toolchain”# macOSbrew install filosottile/musl-cross/musl-cross
# Windowswinget install --id=Filosottile.MuslCross -eVerify:
which aarch64-linux-musl-gccClone and Configure the Project
Section titled “Clone and Configure the Project”git clone https://github.com/zeroclaw-labs/zeroclaw.gitcd zeroclawTell Cargo to use the musl linker for the ARM64 target by editing .cargo/config.toml:
[target.aarch64-unknown-linux-musl]linker = "aarch64-linux-musl-gcc"Build the Binary
Section titled “Build the Binary”cargo cleancargo build --release --target aarch64-unknown-linux-muslThis will produce the static binary at target/aarch64-unknown-linux-musl/release/zeroclaw.
Verify it exists and check the architecture:
ls target/aarch64-unknown-linux-musl/release/zeroclawfile target/aarch64-unknown-linux-musl/release/zeroclaw# Should output: ELF 64-bit LSB executable, ARM aarch64Deploying to the FR201
Section titled “Deploying to the FR201”Copy the binary to the FR201 using rsync (use the Tailscale IP if you set it up, otherwise use the local IP):
rsync -avz target/aarch64-unknown-linux-musl/release/zeroclaw claw@<IP_ADDRESS>:/home/claw/SSH into the FR201, make the binary executable, and verify it runs:
ssh claw@<IP_ADDRESS>chmod +x zeroclaw./zeroclaw --versionSetting Up Service Identity and Isolation
Section titled “Setting Up Service Identity and Isolation”We don’t want ZeroClaw running as claw or (worse) as root. Instead, we’ll create a dedicated system user with no login shell and no home directory access. This is standard practice for daemon processes, and it means the ZeroClaw process only has access to exactly what we grant it.
Create the service user:
sudo useradd --system \ --home /var/lib/zeroclaw \ --create-home \ --shell /usr/sbin/nologin \ zeroclawMove the binary to a dedicated directory under /opt and set up a symlink:
sudo mkdir -p /opt/zeroclawsudo mv zeroclaw /opt/zeroclaw/sudo chown -R zeroclaw:zeroclaw /opt/zeroclawsudo chmod 755 /opt/zeroclaw/zeroclawsudo ln -sf /opt/zeroclaw/zeroclaw /usr/local/bin/zeroclawVerify it works under the service account:
sudo -u zeroclaw /usr/local/bin/zeroclaw --versionConfiguring the Systemd Service
Section titled “Configuring the Systemd Service”Now we’ll create a systemd unit file that starts ZeroClaw on boot, restarts it on failure, and applies a set of security hardening directives that lock down what the process can do.
Create the service file:
sudo nano /etc/systemd/system/zeroclaw.serviceAdd the following:
[Unit]Description=ZeroClaw ServiceAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=zeroclawGroup=zeroclawExecStartPre=/usr/bin/test -x /usr/local/bin/zeroclawExecStart=/usr/local/bin/zeroclaw daemonWorkingDirectory=/var/lib/zeroclawRestart=on-failureRestartSec=5StandardOutput=journalStandardError=journal
# HardeningNoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/zeroclawRestrictNamespaces=trueRestrictRealtime=trueLockPersonality=true
[Install]WantedBy=multi-user.targetHere’s what each hardening directive does:
- NoNewPrivileges prevents the process (or any child process) from gaining elevated privileges through setuid/setgid binaries.
- PrivateTmp gives the service its own isolated
/tmpand/var/tmp, so it can’t read or write temp files from other services. - ProtectSystem=strict makes the entire root filesystem read-only from the service’s point of view. Only paths listed in
ReadWritePathsare writable. - ProtectHome blocks access to
/home,/root, and/run/user, so the service can’t snoop on user directories. - RestrictNamespaces prevents the service from creating new Linux namespaces, blocking a common container-escape technique.
- RestrictRealtime prevents the service from acquiring real-time scheduling priority, which could be used to starve other processes.
- LockPersonality prevents changing the execution personality (e.g., switching to 32-bit mode), closing off another obscure attack vector.
Enable and start the service:
sudo systemctl daemon-reloadsudo systemctl enable zeroclawsudo systemctl start zeroclawCheck that it’s running:
systemctl is-active zeroclawjournalctl -u zeroclaw -n 50 --no-pagerReboot the FR201 to confirm the service starts automatically on boot:
sudo rebootAfter it comes back up, verify again:
systemctl is-active zeroclawjournalctl -u zeroclaw -n 50 --no-pagerConfiguring the ZeroClaw Agent
Section titled “Configuring the ZeroClaw Agent”The last step is telling ZeroClaw which model to use. Edit the configuration file:
sudo -u zeroclaw nano /var/lib/zeroclaw/.zeroclaw/config.tomlAdd your configuration:
api_key = "<YOUR-ANTHROPIC-API-KEY>"default_provider = "anthropic"default_model = "claude-haiku-4-5-20251001"Lock down the permissions so only the zeroclaw user can read it:
sudo chmod 600 /var/lib/zeroclaw/.zeroclaw/config.tomlTest it by switching to the zeroclaw user context and running a quick prompt:
sudo -u zeroclaw -szeroclaw agent -m "What is the meaning of life?"If you get a response back, you’re good. The agent is running, the API key works, and ZeroClaw is fully operational on your FR201.
Use Cases in Manufacturing
Section titled “Use Cases in Manufacturing”Now that you have a hardened, self-contained edge agent running on industrial hardware, here are some practical ways this could be put to work on a plant floor:
Equipment health monitoring and predictive alerts. Connect the FR201’s serial port to a PLC or sensor gateway and have ZeroClaw watch for anomalies in temperature, vibration, or pressure readings. Instead of setting static thresholds that generate noise, the agent can analyze patterns over time and generate natural-language alerts when something actually looks off. Think “Pasteurizer 3 discharge temperature has been trending 2°F above baseline for the last 4 hours” rather than “TEMP HIGH.”
Production data collection and summarization. In many plants, operators still log batch data on paper or in disconnected spreadsheets. An FR201 running ZeroClaw can pull data from OPC-UA servers, MQTT brokers, or even serial-connected scales and meters, then compile shift summaries, yield reports, or quality check logs automatically.
Real-time SPC (Statistical Process Control). Feed process measurements into ZeroClaw and let it flag when a process is drifting out of control limits. The agent can contextualize the data, correlate it with upstream variables, and suggest root causes rather than just throwing a red light on a dashboard.
Maintenance work order generation. When a fault code fires or a sensor trips, ZeroClaw can draft a structured maintenance work order with the equipment ID, fault description, suggested remediation steps, and relevant history, then push it to your CMMS via API. This saves the maintenance tech from having to manually create the ticket and gather context.
Line changeover assistance. For plants that run multiple products on the same line, ZeroClaw can provide operators with step-by-step changeover instructions based on the current and next product, pulling from SOPs stored locally on the device. It can also log changeover times and flag if steps are being skipped.
Regulatory and compliance logging. In food, pharma, or any regulated manufacturing environment, having an immutable, timestamped log of process conditions is important. The FR201’s local storage combined with ZeroClaw’s logging capabilities can provide a lightweight audit trail that doesn’t depend on cloud connectivity.
The beauty of this setup is that each FR201 is self-contained. If the network goes down, the device keeps running. If you need to deploy to a new plant, you image an SSD, copy the binary, and you’re up in under an hour. No Docker registries, no package managers, no dependency hell. Just one binary, one config file, and systemd keeping it alive.