Skip to content

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

ZeroClaw on an FR201

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.

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.

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.

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

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:

  1. Download the Raspberry Pi Imager and install it on your workstation.
  2. Power down the FR201, open the case, and remove the M.2 SSD. Connect it to your workstation using the SSK adapter.
  3. Open the Raspberry Pi Imager and select Raspberry Pi OS (other) from the OS list, then choose Raspberry Pi OS Lite (64-bit).
  4. Set the hostname to zero (or whatever you prefer for your naming convention).
  5. 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.
  6. Set the username to claw and choose a strong password. You’ll need these credentials for initial login.
  7. 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.
  8. Select the SSD as the target storage device.
  9. Click Write to format the SSD and install the OS.
  10. Once the write completes, the imager will eject the drive by default. Unplug the adapter and reconnect it so the drive remounts.
  11. The FR201 requires specific firmware configuration files. Download and extract the appropriate set onto the boot partition of the SSD:
  12. Safely eject the SSD, reinstall it in the FR201, and close the case.

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:

Terminal window
sudo apt update
sudo apt upgrade -y

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:

Terminal window
# Disable all locales except en_US.UTF-8
sudo sed -i 's/^\([^#].*\)/# \1/' /etc/locale.gen
sudo sed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
# Generate the new locale
sudo locale-gen en_US.UTF-8
# Set the system locale
echo "LANG=en_US.UTF-8" | sudo tee /etc/default/locale

Set the keyboard layout to US:

Terminal window
sudo sed -i 's/^XKBLAYOUT=.*/XKBLAYOUT="us"/' /etc/default/keyboard
sudo setupcon

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:

Terminal window
sudo timedatectl set-timezone UTC

If you’d rather use a local timezone:

Terminal window
sudo timedatectl set-timezone US/Mountain

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:

Terminal window
hostname -I

For the initial connection, use the password you set during imaging:

Terminal window
ssh claw@<IP_ADDRESS>

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:

Terminal window
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:

Terminal window
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:

Terminal window
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.

SSH into the FR201 and edit the SSH daemon configuration:

Terminal window
sudo nano /etc/ssh/sshd_config

Find and update (or add) the following lines:

PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no

Save the file and restart the SSH service:

Terminal window
sudo systemctl restart sshd

From 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.

We’ll use rsync to deploy the binary later, so install it now along with curl:

Terminal window
sudo apt install -y curl rsync

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.

Terminal window
curl -fsSL https://tailscale.com/install.sh | sh

Bring it up and authenticate:

Terminal window
sudo tailscale up

This 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.

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.

On macOS, install Homebrew first if you don’t have it:

Terminal window
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Then install Rust (works on both macOS and Windows):

Terminal window
# macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Terminal window
# Windows
winget install --id=Rustlang.Rustup -e

Restart your terminal and verify:

Terminal window
rustc --version
cargo --version

The musl target produces a fully static binary, no glibc dependency on the target device:

Terminal window
rustup target add aarch64-unknown-linux-musl

Verify it’s installed:

Terminal window
rustup target list --installed
# You should see aarch64-unknown-linux-musl in the list

Install the musl Cross-Compilation Toolchain

Section titled “Install the musl Cross-Compilation Toolchain”
Terminal window
# macOS
brew install filosottile/musl-cross/musl-cross
# Windows
winget install --id=Filosottile.MuslCross -e

Verify:

Terminal window
which aarch64-linux-musl-gcc
Terminal window
git clone https://github.com/zeroclaw-labs/zeroclaw.git
cd zeroclaw

Tell 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"
Terminal window
cargo clean
cargo build --release --target aarch64-unknown-linux-musl

This will produce the static binary at target/aarch64-unknown-linux-musl/release/zeroclaw.

Verify it exists and check the architecture:

Terminal window
ls target/aarch64-unknown-linux-musl/release/zeroclaw
file target/aarch64-unknown-linux-musl/release/zeroclaw
# Should output: ELF 64-bit LSB executable, ARM aarch64

Copy the binary to the FR201 using rsync (use the Tailscale IP if you set it up, otherwise use the local IP):

Terminal window
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:

Terminal window
ssh claw@<IP_ADDRESS>
chmod +x zeroclaw
./zeroclaw --version

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:

Terminal window
sudo useradd --system \
--home /var/lib/zeroclaw \
--create-home \
--shell /usr/sbin/nologin \
zeroclaw

Move the binary to a dedicated directory under /opt and set up a symlink:

Terminal window
sudo mkdir -p /opt/zeroclaw
sudo mv zeroclaw /opt/zeroclaw/
sudo chown -R zeroclaw:zeroclaw /opt/zeroclaw
sudo chmod 755 /opt/zeroclaw/zeroclaw
sudo ln -sf /opt/zeroclaw/zeroclaw /usr/local/bin/zeroclaw

Verify it works under the service account:

Terminal window
sudo -u zeroclaw /usr/local/bin/zeroclaw --version

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:

Terminal window
sudo nano /etc/systemd/system/zeroclaw.service

Add the following:

[Unit]
Description=ZeroClaw Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=zeroclaw
Group=zeroclaw
ExecStartPre=/usr/bin/test -x /usr/local/bin/zeroclaw
ExecStart=/usr/local/bin/zeroclaw daemon
WorkingDirectory=/var/lib/zeroclaw
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/zeroclaw
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
[Install]
WantedBy=multi-user.target

Here’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 /tmp and /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 ReadWritePaths are 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:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable zeroclaw
sudo systemctl start zeroclaw

Check that it’s running:

Terminal window
systemctl is-active zeroclaw
journalctl -u zeroclaw -n 50 --no-pager

Reboot the FR201 to confirm the service starts automatically on boot:

Terminal window
sudo reboot

After it comes back up, verify again:

Terminal window
systemctl is-active zeroclaw
journalctl -u zeroclaw -n 50 --no-pager

The last step is telling ZeroClaw which model to use. Edit the configuration file:

Terminal window
sudo -u zeroclaw nano /var/lib/zeroclaw/.zeroclaw/config.toml

Add 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:

Terminal window
sudo chmod 600 /var/lib/zeroclaw/.zeroclaw/config.toml

Test it by switching to the zeroclaw user context and running a quick prompt:

Terminal window
sudo -u zeroclaw -s
zeroclaw 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.

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.